From ae684b945af66379f8b49059e6b780f9fbb480c8 Mon Sep 17 00:00:00 2001 From: md_5 Date: Mon, 3 Feb 2014 19:43:32 -0500 Subject: [PATCH] Spigot --- README.md | 14 +- pom.xml | 79 +-- .../java/net/minecraft/server/BiomeDecorator.java | 286 +++++++++ src/main/java/net/minecraft/server/Block.java | 20 +- src/main/java/net/minecraft/server/BlockAnvil.java | 88 +++ .../net/minecraft/server/BlockBrewingStand.java | 115 ++++ .../java/net/minecraft/server/BlockCactus.java | 2 +- src/main/java/net/minecraft/server/BlockCrops.java | 5 +- .../java/net/minecraft/server/BlockFlowerPot.java | 197 +++++++ src/main/java/net/minecraft/server/BlockGrass.java | 3 +- .../java/net/minecraft/server/BlockMushroom.java | 2 +- src/main/java/net/minecraft/server/BlockMycel.java | 3 +- .../java/net/minecraft/server/BlockPortal.java | 5 +- .../net/minecraft/server/BlockRedstoneTorch.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 +- src/main/java/net/minecraft/server/BlockTNT.java | 85 +++ src/main/java/net/minecraft/server/Chunk.java | 83 ++- .../net/minecraft/server/ChunkProviderServer.java | 40 +- .../net/minecraft/server/ChunkRegionLoader.java | 57 +- .../java/net/minecraft/server/ChunkSection.java | 26 +- .../server/CommandBlockListenerAbstract.java | 2 +- .../net/minecraft/server/CommandDispatcher.java | 93 +++ .../java/net/minecraft/server/DataWatcher.java | 306 ++++++++++ .../java/net/minecraft/server/DedicatedServer.java | 39 +- .../server/DispenseBehaviorMonsterEgg.java | 2 +- src/main/java/net/minecraft/server/Entity.java | 38 +- .../java/net/minecraft/server/EntityAgeable.java | 25 + .../java/net/minecraft/server/EntityArrow.java | 4 +- .../net/minecraft/server/EntityFallingBlock.java | 2 + .../java/net/minecraft/server/EntityFireball.java | 9 + .../java/net/minecraft/server/EntityHuman.java | 7 +- .../net/minecraft/server/EntityInsentient.java | 6 + src/main/java/net/minecraft/server/EntityItem.java | 19 +- .../java/net/minecraft/server/EntityLiving.java | 14 +- .../minecraft/server/EntityMinecartContainer.java | 6 + .../java/net/minecraft/server/EntityPlayer.java | 60 +- .../java/net/minecraft/server/EntityTracker.java | 3 + .../net/minecraft/server/EntityTrackerEntry.java | 2 + .../java/net/minecraft/server/EntityZombie.java | 4 +- src/main/java/net/minecraft/server/Explosion.java | 1 + .../net/minecraft/server/HandshakeListener.java | 17 +- src/main/java/net/minecraft/server/IRecipe.java | 2 + .../java/net/minecraft/server/ItemMonsterEgg.java | 8 +- src/main/java/net/minecraft/server/ItemStack.java | 27 +- .../java/net/minecraft/server/LoginListener.java | 17 +- .../java/net/minecraft/server/MinecraftServer.java | 115 +++- .../net/minecraft/server/MobSpawnerAbstract.java | 37 +- src/main/java/net/minecraft/server/NBTBase.java | 129 +++++ .../minecraft/server/NBTCompressedStreamTools.java | 113 ++++ .../java/net/minecraft/server/NBTTagByteArray.java | 57 ++ .../java/net/minecraft/server/NBTTagIntArray.java | 73 +++ src/main/java/net/minecraft/server/NBTTagList.java | 166 ++++++ .../java/net/minecraft/server/NetworkManager.java | 10 +- .../net/minecraft/server/NextTickListEntry.java | 60 ++ .../java/net/minecraft/server/NibbleArray.java | 158 +++++ .../java/net/minecraft/server/OldChunkLoader.java | 122 ++++ .../net/minecraft/server/PacketPlayInChat.java | 20 +- .../minecraft/server/PacketPlayOutMapChunk.java | 191 ++++++ .../server/PacketPlayOutMapChunkBulk.java | 3 +- .../net/minecraft/server/PacketStatusListener.java | 7 + .../java/net/minecraft/server/PlayerChunkMap.java | 2 +- .../net/minecraft/server/PlayerConnection.java | 51 +- .../minecraft/server/PlayerInteractManager.java | 1 + src/main/java/net/minecraft/server/PlayerList.java | 59 +- .../java/net/minecraft/server/PropertyManager.java | 2 +- .../java/net/minecraft/server/RegionFileCache.java | 67 +++ .../minecraft/server/RemoteControlListener.java | 107 ++++ .../net/minecraft/server/ServerConnection.java | 109 ++++ .../minecraft/server/ServerConnectionChannel.java | 37 ++ .../minecraft/server/ServerStatisticManager.java | 211 +++++++ .../java/net/minecraft/server/ShapedRecipes.java | 9 +- .../net/minecraft/server/ShapelessRecipes.java | 9 +- .../java/net/minecraft/server/SpawnerCreature.java | 35 +- .../net/minecraft/server/StructureGenerator.java | 225 +++++++ .../minecraft/server/ThreadPlayerLookupUUID.java | 88 +-- src/main/java/net/minecraft/server/TileEntity.java | 2 + .../net/minecraft/server/TileEntityHopper.java | 17 +- .../java/net/minecraft/server/WatchableObject.java | 44 ++ src/main/java/net/minecraft/server/World.java | 250 ++++++-- .../net/minecraft/server/WorldGenForestTree.java | 7 +- .../net/minecraft/server/WorldGenPackedIce2.java | 101 ++++ .../net/minecraft/server/WorldMapHumanTracker.java | 24 +- .../java/net/minecraft/server/WorldServer.java | 46 +- .../java/org/bukkit/craftbukkit/CraftChunk.java | 44 +- .../java/org/bukkit/craftbukkit/CraftEffect.java | 2 + .../java/org/bukkit/craftbukkit/CraftServer.java | 76 ++- .../java/org/bukkit/craftbukkit/CraftWorld.java | 91 ++- src/main/java/org/bukkit/craftbukkit/Main.java | 16 + .../java/org/bukkit/craftbukkit/SpigotTimings.java | 128 ++++ .../org/bukkit/craftbukkit/block/CraftSign.java | 8 +- .../craftbukkit/chunkio/ChunkIOExecutor.java | 7 +- .../craftbukkit/chunkio/ChunkIOProvider.java | 20 +- .../bukkit/craftbukkit/chunkio/QueuedChunk.java | 12 +- .../org/bukkit/craftbukkit/entity/CraftArrow.java | 22 + .../org/bukkit/craftbukkit/entity/CraftEntity.java | 16 + .../bukkit/craftbukkit/entity/CraftItemFrame.java | 14 - .../org/bukkit/craftbukkit/entity/CraftPlayer.java | 67 ++- .../craftbukkit/event/CraftEventFactory.java | 17 + .../craftbukkit/inventory/CraftInventoryAnvil.java | 22 + .../craftbukkit/inventory/CraftMetaFirework.java | 4 +- .../org/bukkit/craftbukkit/map/CraftMapView.java | 2 +- .../craftbukkit/scheduler/CraftScheduler.java | 2 +- .../bukkit/craftbukkit/scheduler/CraftTask.java | 22 +- .../scoreboard/CraftScoreboardManager.java | 1 + .../craftbukkit/util/AsynchronousExecutor.java | 18 +- .../bukkit/craftbukkit/util/CraftChatMessage.java | 7 +- .../java/org/bukkit/craftbukkit/util/LongHash.java | 2 +- .../org/bukkit/craftbukkit/util/LongHashSet.java | 33 +- .../bukkit/craftbukkit/util/LongObjectHashMap.java | 15 + .../org/bukkit/craftbukkit/util/Versioning.java | 2 +- src/main/java/org/spigotmc/ActivationRange.java | 296 ++++++++++ src/main/java/org/spigotmc/AntiXray.java | 189 ++++++ src/main/java/org/spigotmc/FlatMap.java | 64 ++ src/main/java/org/spigotmc/Metrics.java | 645 +++++++++++++++++++++ src/main/java/org/spigotmc/RestartCommand.java | 113 ++++ src/main/java/org/spigotmc/SpigotConfig.java | 269 +++++++++ src/main/java/org/spigotmc/SpigotWorldConfig.java | 281 +++++++++ .../java/org/spigotmc/TicksPerSecondCommand.java | 45 ++ src/main/java/org/spigotmc/TrackingRange.java | 45 ++ .../java/org/spigotmc/VanillaCommandWrapper.java | 194 +++++++ src/main/java/org/spigotmc/WatchdogThread.java | 117 ++++ src/main/resources/configurations/bukkit.yml | 9 +- 124 files changed, 7053 insertions(+), 479 deletions(-) create mode 100644 src/main/java/net/minecraft/server/BiomeDecorator.java create mode 100644 src/main/java/net/minecraft/server/BlockAnvil.java create mode 100644 src/main/java/net/minecraft/server/BlockBrewingStand.java create mode 100644 src/main/java/net/minecraft/server/BlockFlowerPot.java create mode 100644 src/main/java/net/minecraft/server/BlockTNT.java create mode 100644 src/main/java/net/minecraft/server/CommandDispatcher.java create mode 100644 src/main/java/net/minecraft/server/DataWatcher.java mode change 100644 => 100755 src/main/java/net/minecraft/server/DedicatedServer.java create mode 100644 src/main/java/net/minecraft/server/NBTBase.java create mode 100644 src/main/java/net/minecraft/server/NBTCompressedStreamTools.java create mode 100644 src/main/java/net/minecraft/server/NBTTagByteArray.java create mode 100644 src/main/java/net/minecraft/server/NBTTagIntArray.java create mode 100644 src/main/java/net/minecraft/server/NBTTagList.java create mode 100644 src/main/java/net/minecraft/server/NextTickListEntry.java create mode 100644 src/main/java/net/minecraft/server/NibbleArray.java create mode 100644 src/main/java/net/minecraft/server/OldChunkLoader.java create mode 100644 src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java create mode 100644 src/main/java/net/minecraft/server/RegionFileCache.java create mode 100644 src/main/java/net/minecraft/server/RemoteControlListener.java create mode 100644 src/main/java/net/minecraft/server/ServerConnection.java create mode 100644 src/main/java/net/minecraft/server/ServerConnectionChannel.java create mode 100644 src/main/java/net/minecraft/server/ServerStatisticManager.java create mode 100644 src/main/java/net/minecraft/server/StructureGenerator.java create mode 100644 src/main/java/net/minecraft/server/WatchableObject.java create mode 100644 src/main/java/net/minecraft/server/WorldGenPackedIce2.java create mode 100644 src/main/java/org/bukkit/craftbukkit/SpigotTimings.java create mode 100644 src/main/java/org/spigotmc/ActivationRange.java create mode 100644 src/main/java/org/spigotmc/AntiXray.java create mode 100644 src/main/java/org/spigotmc/FlatMap.java create mode 100644 src/main/java/org/spigotmc/Metrics.java create mode 100644 src/main/java/org/spigotmc/RestartCommand.java create mode 100755 src/main/java/org/spigotmc/SpigotConfig.java create mode 100644 src/main/java/org/spigotmc/SpigotWorldConfig.java create mode 100644 src/main/java/org/spigotmc/TicksPerSecondCommand.java create mode 100644 src/main/java/org/spigotmc/TrackingRange.java create mode 100644 src/main/java/org/spigotmc/VanillaCommandWrapper.java create mode 100644 src/main/java/org/spigotmc/WatchdogThread.java diff --git a/README.md b/README.md index cda766b..1f12a75 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -CraftBukkit +Spigot =========== -A Bukkit (Minecraft Server API) implementation +A Spigot-API and Bukkit implementation -Website: [http://bukkit.org](http://bukkit.org) -Bugs/Suggestions: [http://leaky.bukkit.org](http://leaky.bukkit.org) -Contributing Guidelines: [CONTRIBUTING.md](https://github.com/Bukkit/CraftBukkit/blob/master/CONTRIBUTING.md) +Website: [http://spigotmc.org](http://spigotmc.org) +Bugs/Suggestions: [http://www.spigotmc.org/forums/bugs-feature-requests.8/](http://www.spigotmc.org/forums/bugs-feature-requests.8/) +Contributing Guidelines: [CONTRIBUTING.md](https://github.com/SpigotMC/Spigot-API/blob/master/CONTRIBUTING.md) Compilation ----------- @@ -13,6 +13,6 @@ Compilation We use maven to handle our dependencies. * Install [Maven 3](http://maven.apache.org/download.html) -* Check out and install [Bukkit](http://github.com/Bukkit/Bukkit) - * *Note*: this is not needed as the repository we use has Bukkit too, but you might have a newer one (with your own changes :D) +* Check out and install [Spigot-API](http://github.com/SpigotMC/Spigot) + * *Note*: this is not needed as the repository we use has Spigot-API too, but you might have a newer one (with your own changes :D) * Check out this repo and: `mvn clean package` diff --git a/pom.xml b/pom.xml index 2f06c8e..bec994f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,20 @@ 4.0.0 - org.bukkit - craftbukkit + + + org.spigotmc + spigot-parent + dev-SNAPSHOT + ../pom.xml + + + org.spigotmc + spigot jar 1.7.2-R0.3-SNAPSHOT - CraftBukkit - http://www.bukkit.org + Spigot + http://www.spigotmc.org UTF-8 @@ -18,25 +26,6 @@ - - scm:git:git://github.com/Bukkit/CraftBukkit.git - scm:git:ssh://git@github.com/Bukkit/CraftBukkit.git - https://github.com/Bukkit/CraftBukkit - - - - - repobo-rel - repo.bukkit.org Releases - http://repo.bukkit.org/content/repositories/releases/ - - - repobo-snap - repo.bukkit.org Snapshots - http://repo.bukkit.org/content/repositories/snapshots/ - - - repobo-snap @@ -53,8 +42,8 @@ - org.bukkit - bukkit + org.spigotmc + spigot-api ${project.version} jar compile @@ -107,33 +96,6 @@ gson 2.1 - - org.avaje - ebean - 2.7.3 - jar - provided - - - org.yaml - snakeyaml - 1.9 - jar - provided - - - com.google.guava - guava - 10.0 - jar - provided - - - commons-lang - commons-lang - 2.3 - provided - junit @@ -147,19 +109,24 @@ 1.3 test + + net.sf.trove4j + trove4j + 3.0.3 + - clean install + install com.lukegb.mojo gitdescribe-maven-plugin 1.3 - ${buildtag.prefix} - ${buildtag.suffix} + git-Spigot- + @@ -212,7 +179,7 @@ org.apache.maven.plugins maven-shade-plugin - 1.4 + 2.1 package diff --git a/src/main/java/net/minecraft/server/BiomeDecorator.java b/src/main/java/net/minecraft/server/BiomeDecorator.java new file mode 100644 index 0000000..962d719 --- /dev/null +++ b/src/main/java/net/minecraft/server/BiomeDecorator.java @@ -0,0 +1,286 @@ +package net.minecraft.server; + +import java.util.Random; + +public class BiomeDecorator { + + protected World a; + protected Random b; + protected int c; + protected int d; + protected WorldGenerator e = new WorldGenClay(4); + protected WorldGenerator f; + protected WorldGenerator g; + protected WorldGenerator h; + protected WorldGenerator i; + protected WorldGenerator j; + protected WorldGenerator k; + protected WorldGenerator l; + protected WorldGenerator m; + protected WorldGenerator n; + protected WorldGenerator o; + protected WorldGenFlowers p; + protected WorldGenerator q; + protected WorldGenerator r; + protected WorldGenerator s; + protected WorldGenerator t; + protected WorldGenerator u; + protected WorldGenerator v; + protected int w; + protected int x; + protected int y; + protected int z; + protected int A; + protected int B; + protected int C; + protected int D; + protected int E; + protected int F; + protected int G; + protected int H; + public boolean I; + + public BiomeDecorator() { + this.f = new WorldGenSand(Blocks.SAND, 7); + this.g = new WorldGenSand(Blocks.GRAVEL, 6); + this.h = new WorldGenMinable(Blocks.DIRT, 32); + this.i = new WorldGenMinable(Blocks.GRAVEL, 32); + this.j = new WorldGenMinable(Blocks.COAL_ORE, 16); + this.k = new WorldGenMinable(Blocks.IRON_ORE, 8); + this.l = new WorldGenMinable(Blocks.GOLD_ORE, 8); + this.m = new WorldGenMinable(Blocks.REDSTONE_ORE, 7); + this.n = new WorldGenMinable(Blocks.DIAMOND_ORE, 7); + this.o = new WorldGenMinable(Blocks.LAPIS_ORE, 6); + this.p = new WorldGenFlowers(Blocks.YELLOW_FLOWER); + this.q = new WorldGenFlowers(Blocks.BROWN_MUSHROOM); + this.r = new WorldGenFlowers(Blocks.RED_MUSHROOM); + this.s = new WorldGenHugeMushroom(); + this.t = new WorldGenReed(); + this.u = new WorldGenCactus(); + this.v = new WorldGenWaterLily(); + this.y = 2; + this.z = 1; + this.E = 1; + this.F = 3; + this.G = 1; + this.I = true; + } + + public void a(World world, Random random, BiomeBase biomebase, int i, int j) { + if (this.a != null) { + throw new RuntimeException("Already decorating!!"); + } else { + this.a = world; + this.b = random; + this.c = i; + this.d = j; + this.a(biomebase); + this.a = null; + this.b = null; + } + } + + protected void a(BiomeBase biomebase) { + this.a(); + + int i; + int j; + int k; + + for (i = 0; i < this.F; ++i) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + this.f.a(this.a, this.b, j, this.a.i(j, k), k); + } + + for (i = 0; i < this.G; ++i) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + this.e.a(this.a, this.b, j, this.a.i(j, k), k); + } + + for (i = 0; i < this.E; ++i) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + this.g.a(this.a, this.b, j, this.a.i(j, k), k); + } + + i = this.x; + if (this.b.nextInt(10) == 0) { + ++i; + } + + int l; + int i1; + + for (j = 0; j < i; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.a.getHighestBlockYAt(k, l); + WorldGenTreeAbstract worldgentreeabstract = biomebase.a(this.b); + + worldgentreeabstract.a(1.0D, 1.0D, 1.0D); + if (worldgentreeabstract.a(this.a, this.b, k, i1, l)) { + worldgentreeabstract.b(this.a, this.b, k, i1, l); + } + } + + for (j = 0; j < this.H; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + this.s.a(this.a, this.b, k, this.a.getHighestBlockYAt(k, l), l); + } + + for (j = 0; j < this.y; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.a.getHighestBlockYAt(k, l) + 32); + String s = biomebase.a(this.b, k, i1, l); + BlockFlowers blockflowers = BlockFlowers.e(s); + + if (blockflowers.getMaterial() != Material.AIR) { + this.p.a(blockflowers, BlockFlowers.f(s)); + this.p.a(this.a, this.b, k, i1, l); + } + } + + for (j = 0; j < this.z; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + WorldGenerator worldgenerator = biomebase.b(this.b); + + worldgenerator.a(this.a, this.b, k, i1, l); + } + + for (j = 0; j < this.A; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + (new WorldGenDeadBush(Blocks.DEAD_BUSH)).a(this.a, this.b, k, i1, l); + } + + for (j = 0; j < this.w; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + + for (i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); i1 > 0 && this.a.isEmpty(k, i1 - 1, l); --i1) { // Spigot + ; + } + + this.v.a(this.a, this.b, k, i1, l); + } + + for (j = 0; j < this.B; ++j) { + if (this.b.nextInt(4) == 0) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.a.getHighestBlockYAt(k, l); + this.q.a(this.a, this.b, k, i1, l); + } + + if (this.b.nextInt(8) == 0) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + this.r.a(this.a, this.b, k, i1, l); + } + } + + if (this.b.nextInt(4) == 0) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + l = this.b.nextInt(this.getHighestBlockYAt(j, k) * 2); // Spigot + this.q.a(this.a, this.b, j, l, k); + } + + if (this.b.nextInt(8) == 0) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + l = this.b.nextInt(this.getHighestBlockYAt(j, k) * 2); // Spigot + this.r.a(this.a, this.b, j, l, k); + } + + for (j = 0; j < this.C; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + this.t.a(this.a, this.b, k, i1, l); + } + + for (j = 0; j < 10; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + this.t.a(this.a, this.b, k, i1, l); + } + + if (this.b.nextInt(32) == 0) { + j = this.c + this.b.nextInt(16) + 8; + k = this.d + this.b.nextInt(16) + 8; + l = this.b.nextInt(this.getHighestBlockYAt(j, k) * 2); // Spigot + (new WorldGenPumpkin()).a(this.a, this.b, j, l, k); + } + + for (j = 0; j < this.D; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.d + this.b.nextInt(16) + 8; + i1 = this.b.nextInt(this.getHighestBlockYAt(k, l) * 2); // Spigot + this.u.a(this.a, this.b, k, i1, l); + } + + if (this.I) { + for (j = 0; j < 50; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.b.nextInt(this.b.nextInt(248) + 8); + i1 = this.d + this.b.nextInt(16) + 8; + (new WorldGenLiquids(Blocks.WATER)).a(this.a, this.b, k, l, i1); + } + + for (j = 0; j < 20; ++j) { + k = this.c + this.b.nextInt(16) + 8; + l = this.b.nextInt(this.b.nextInt(this.b.nextInt(240) + 8) + 8); + i1 = this.d + this.b.nextInt(16) + 8; + (new WorldGenLiquids(Blocks.LAVA)).a(this.a, this.b, k, l, i1); + } + } + } + + protected void a(int i, WorldGenerator worldgenerator, int j, int k) { + for (int l = 0; l < i; ++l) { + int i1 = this.c + this.b.nextInt(16); + int j1 = this.b.nextInt(k - j) + j; + int k1 = this.d + this.b.nextInt(16); + + worldgenerator.a(this.a, this.b, i1, j1, k1); + } + } + + protected void b(int i, WorldGenerator worldgenerator, int j, int k) { + for (int l = 0; l < i; ++l) { + int i1 = this.c + this.b.nextInt(16); + int j1 = this.b.nextInt(k) + this.b.nextInt(k) + (j - k); + int k1 = this.d + this.b.nextInt(16); + + worldgenerator.a(this.a, this.b, i1, j1, k1); + } + } + + protected void a() { + this.a(20, this.h, 0, 256); + this.a(10, this.i, 0, 256); + this.a(20, this.j, 0, 128); + this.a(20, this.k, 0, 64); + this.a(2, this.l, 0, 32); + this.a(8, this.m, 0, 16); + this.a(1, this.n, 0, 16); + this.b(1, this.o, 16, 16); + } + + // Spigot Start + private int getHighestBlockYAt(int x, int z) + { + return Math.max( 1, this.a.getHighestBlockYAt( x, z ) ); + } + // Spigot End +} diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java index dca832f..c063ca1 100644 --- a/src/main/java/net/minecraft/server/Block.java +++ b/src/main/java/net/minecraft/server/Block.java @@ -433,9 +433,13 @@ public class Block { return 10; } - public void onPlace(World world, int i, int j, int k) {} + public void onPlace(World world, int i, int j, int k) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous block onPlace!"); // Spigot + } - public void remove(World world, int i, int j, int k, Block block, int l) {} + public void remove(World world, int i, int j, int k, Block block, int l) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous block remove!"); // Spigot + } public int a(Random random) { return 1; @@ -804,4 +808,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/BlockAnvil.java b/src/main/java/net/minecraft/server/BlockAnvil.java new file mode 100644 index 0000000..1fa14c5 --- /dev/null +++ b/src/main/java/net/minecraft/server/BlockAnvil.java @@ -0,0 +1,88 @@ +package net.minecraft.server; + +public class BlockAnvil extends BlockFalling { + + public static final String[] a = new String[] { "intact", "slightlyDamaged", "veryDamaged"}; + private static final String[] N = new String[] { "anvil_top_damaged_0", "anvil_top_damaged_1", "anvil_top_damaged_2"}; + + protected BlockAnvil() { + super(Material.HEAVY); + this.g(0); + this.a(CreativeModeTab.c); + } + + // Spigot start + @Override + public AxisAlignedBB a( World world, int i, int j, int k ) + { + updateShape( world, i, j, k ); + return super.a( world, i, j, k ); + } + // Spigot end + + public boolean d() { + return false; + } + + public boolean c() { + return false; + } + + public void postPlace(World world, int i, int j, int k, EntityLiving entityliving, ItemStack itemstack) { + int l = MathHelper.floor((double) (entityliving.yaw * 4.0F / 360.0F) + 0.5D) & 3; + int i1 = world.getData(i, j, k) >> 2; + + ++l; + l %= 4; + if (l == 0) { + world.setData(i, j, k, 2 | i1 << 2, 2); + } + + if (l == 1) { + world.setData(i, j, k, 3 | i1 << 2, 2); + } + + if (l == 2) { + world.setData(i, j, k, 0 | i1 << 2, 2); + } + + if (l == 3) { + world.setData(i, j, k, 1 | i1 << 2, 2); + } + } + + public boolean interact(World world, int i, int j, int k, EntityHuman entityhuman, int l, float f, float f1, float f2) { + if (world.isStatic) { + return true; + } else { + entityhuman.openAnvil(i, j, k); + return true; + } + } + + public int b() { + return 35; + } + + public int getDropData(int i) { + return i >> 2; + } + + public void updateShape(IBlockAccess iblockaccess, int i, int j, int k) { + int l = iblockaccess.getData(i, j, k) & 3; + + if (l != 3 && l != 1) { + this.a(0.125F, 0.0F, 0.0F, 0.875F, 1.0F, 1.0F); + } else { + this.a(0.0F, 0.0F, 0.125F, 1.0F, 1.0F, 0.875F); + } + } + + protected void a(EntityFallingBlock entityfallingblock) { + entityfallingblock.a(true); + } + + public void a(World world, int i, int j, int k, int l) { + world.triggerEffect(1022, i, j, k, 0); + } +} diff --git a/src/main/java/net/minecraft/server/BlockBrewingStand.java b/src/main/java/net/minecraft/server/BlockBrewingStand.java new file mode 100644 index 0000000..eabea5a --- /dev/null +++ b/src/main/java/net/minecraft/server/BlockBrewingStand.java @@ -0,0 +1,115 @@ +package net.minecraft.server; + +import java.util.List; +import java.util.Random; + +public class BlockBrewingStand extends BlockContainer { + + private Random a = new Random(); + + public BlockBrewingStand() { + super(Material.ORE); + } + + public boolean c() { + return false; + } + + public int b() { + return 25; + } + + public TileEntity a(World world, int i) { + return new TileEntityBrewingStand(); + } + + public boolean d() { + return false; + } + + public void a(World world, int i, int j, int k, AxisAlignedBB axisalignedbb, List list, Entity entity) { + this.a(0.4375F, 0.0F, 0.4375F, 0.5625F, 0.875F, 0.5625F); + super.a(world, i, j, k, axisalignedbb, list, entity); + this.g(); + super.a(world, i, j, k, axisalignedbb, list, entity); + } + + public void g() { + this.a(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F); + } + + public boolean interact(World world, int i, int j, int k, EntityHuman entityhuman, int l, float f, float f1, float f2) { + if (world.isStatic) { + return true; + } else { + TileEntityBrewingStand tileentitybrewingstand = (TileEntityBrewingStand) world.getTileEntity(i, j, k); + + if (tileentitybrewingstand != null) { + entityhuman.openBrewingStand(tileentitybrewingstand); + } + + return true; + } + } + + public void postPlace(World world, int i, int j, int k, EntityLiving entityliving, ItemStack itemstack) { + if (itemstack.hasName()) { + ((TileEntityBrewingStand) world.getTileEntity(i, j, k)).a(itemstack.getName()); + } + } + + public void remove(World world, int i, int j, int k, Block block, int l) { + TileEntity tileentity = world.getTileEntity(i, j, k); + + if (tileentity instanceof TileEntityBrewingStand) { + TileEntityBrewingStand tileentitybrewingstand = (TileEntityBrewingStand) tileentity; + + for (int i1 = 0; i1 < tileentitybrewingstand.getSize(); ++i1) { + ItemStack itemstack = tileentitybrewingstand.getItem(i1); + + if (itemstack != null) { + float f = this.a.nextFloat() * 0.8F + 0.1F; + float f1 = this.a.nextFloat() * 0.8F + 0.1F; + float f2 = this.a.nextFloat() * 0.8F + 0.1F; + + while (itemstack.count > 0) { + int j1 = this.a.nextInt(21) + 10; + + if (j1 > itemstack.count) { + j1 = itemstack.count; + } + + itemstack.count -= j1; + EntityItem entityitem = new EntityItem(world, (double) ((float) i + f), (double) ((float) j + f1), (double) ((float) k + f2), new ItemStack(itemstack.getItem(), j1, itemstack.getData())); + float f3 = 0.05F; + + entityitem.motX = (double) ((float) this.a.nextGaussian() * f3); + entityitem.motY = (double) ((float) this.a.nextGaussian() * f3 + 0.2F); + entityitem.motZ = (double) ((float) this.a.nextGaussian() * f3); + // Spigot Start + if ( itemstack.hasTag() ) + { + entityitem.getItemStack().setTag( (NBTTagCompound) itemstack.getTag().clone() ); + } + // Spigot End + world.addEntity( entityitem ); + } + } + } + } + + super.remove(world, i, j, k, block, l); + } + + public Item getDropType(int i, Random random, int j) { + return Items.BREWING_STAND; + } + + public boolean M() { + return true; + } + + public int g(World world, int i, int j, int k, int l) { + return Container.b((IInventory) world.getTileEntity(i, j, k)); + } +} diff --git a/src/main/java/net/minecraft/server/BlockCactus.java b/src/main/java/net/minecraft/server/BlockCactus.java index 421af04..183fb43 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 / world.spigotConfig.cactusModifier * 15) + 0.5F, 15)) { // Spigot org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j + 1, k, this, 0); // CraftBukkit world.setData(i, j, k, 0, 4); this.doPhysics(world, i, j + 1, k, this); diff --git a/src/main/java/net/minecraft/server/BlockCrops.java b/src/main/java/net/minecraft/server/BlockCrops.java index 942d6ab..c46879c 100644 --- a/src/main/java/net/minecraft/server/BlockCrops.java +++ b/src/main/java/net/minecraft/server/BlockCrops.java @@ -27,9 +27,8 @@ public class BlockCrops extends BlockPlant implements IBlockFragilePlantElement if (l < 7) { float f = this.n(world, i, j, k); - if (random.nextInt((int) (25.0F / f) + 1) == 0) { - ++l; - org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j, k, this, l); // CraftBukkit + if (random.nextInt((int) (world.growthOdds / world.spigotConfig.wheatModifier * (25.0F / f)) + 1) == 0) { // Spigot + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j, k, this, ++l); // CraftBukkit } } } diff --git a/src/main/java/net/minecraft/server/BlockFlowerPot.java b/src/main/java/net/minecraft/server/BlockFlowerPot.java new file mode 100644 index 0000000..734dcb4 --- /dev/null +++ b/src/main/java/net/minecraft/server/BlockFlowerPot.java @@ -0,0 +1,197 @@ +package net.minecraft.server; + +import java.util.Random; + +public class BlockFlowerPot extends BlockContainer { + + public BlockFlowerPot() { + super(Material.ORIENTABLE); + this.g(); + } + + public void g() { + float f = 0.375F; + float f1 = f / 2.0F; + + this.a(0.5F - f1, 0.0F, 0.5F - f1, 0.5F + f1, f, 0.5F + f1); + } + + public boolean c() { + return false; + } + + public int b() { + return 33; + } + + public boolean d() { + return false; + } + + public boolean interact(World world, int i, int j, int k, EntityHuman entityhuman, int l, float f, float f1, float f2) { + ItemStack itemstack = entityhuman.inventory.getItemInHand(); + + if (itemstack != null && itemstack.getItem() instanceof ItemBlock) { + if (world.getData(i, j, k) != 0) { + return false; + } else { + TileEntityFlowerPot tileentityflowerpot = this.e(world, i, j, k); + + if (tileentityflowerpot != null) { + Block block = Block.a(itemstack.getItem()); + + if (!this.a(block, itemstack.getData())) { + return false; + } else { + tileentityflowerpot.a(itemstack.getItem(), itemstack.getData()); + tileentityflowerpot.update(); + if (!world.setData(i, j, k, itemstack.getData(), 2)) { + world.notify(i, j, k); + } + + if (!entityhuman.abilities.canInstantlyBuild && --itemstack.count <= 0) { + entityhuman.inventory.setItem(entityhuman.inventory.itemInHandIndex, (ItemStack) null); + } + + return true; + } + } else { + return false; + } + } + } else { + return false; + } + } + + private boolean a(Block block, int i) { + return block != Blocks.YELLOW_FLOWER && block != Blocks.RED_ROSE && block != Blocks.CACTUS && block != Blocks.BROWN_MUSHROOM && block != Blocks.RED_MUSHROOM && block != Blocks.SAPLING && block != Blocks.DEAD_BUSH ? block == Blocks.LONG_GRASS && i == 2 : true; + } + + public int getDropData(World world, int i, int j, int k) { + TileEntityFlowerPot tileentityflowerpot = this.e(world, i, j, k); + + return tileentityflowerpot != null && tileentityflowerpot.a() != null ? tileentityflowerpot.b() : 0; + } + + public boolean canPlace(World world, int i, int j, int k) { + return super.canPlace(world, i, j, k) && World.a((IBlockAccess) world, i, j - 1, k); + } + + public void doPhysics(World world, int i, int j, int k, Block block) { + if (!World.a((IBlockAccess) world, i, j - 1, k)) { + this.b(world, i, j, k, world.getData(i, j, k), 0); + world.setAir(i, j, k); + } + } + + public void dropNaturally(World world, int i, int j, int k, int l, float f, int i1) { + super.dropNaturally(world, i, j, k, l, f, i1); + TileEntityFlowerPot tileentityflowerpot = this.e(world, i, j, k); + + if (tileentityflowerpot != null && tileentityflowerpot.a() != null) { + this.a(world, i, j, k, new ItemStack(tileentityflowerpot.a(), 1, tileentityflowerpot.b())); + tileentityflowerpot.a( null, 0 ); // Spigot + } + } + + public void remove(World world, int i, int j, int k, Block block, int l) { + TileEntityFlowerPot tileentityflowerpot = this.e(world, i, j, k); + + if (tileentityflowerpot != null && tileentityflowerpot.a() != null) { + this.a(world, i, j, k, new ItemStack(tileentityflowerpot.a(), 1, tileentityflowerpot.b())); + tileentityflowerpot.a( null, 0 ); // Spigot + } + + super.remove(world, i, j, k, block, l); + } + + public void a(World world, int i, int j, int k, int l, EntityHuman entityhuman) { + super.a(world, i, j, k, l, entityhuman); + if (entityhuman.abilities.canInstantlyBuild) { + TileEntityFlowerPot tileentityflowerpot = this.e(world, i, j, k); + + if (tileentityflowerpot != null) { + tileentityflowerpot.a(Item.d(0), 0); + } + } + } + + public Item getDropType(int i, Random random, int j) { + return Items.FLOWER_POT; + } + + private TileEntityFlowerPot e(World world, int i, int j, int k) { + TileEntity tileentity = world.getTileEntity(i, j, k); + + return tileentity != null && tileentity instanceof TileEntityFlowerPot ? (TileEntityFlowerPot) tileentity : null; + } + + public TileEntity a(World world, int i) { + Object object = null; + byte b0 = 0; + + switch (i) { + case 1: + object = Blocks.RED_ROSE; + b0 = 0; + break; + + case 2: + object = Blocks.YELLOW_FLOWER; + break; + + case 3: + object = Blocks.SAPLING; + b0 = 0; + break; + + case 4: + object = Blocks.SAPLING; + b0 = 1; + break; + + case 5: + object = Blocks.SAPLING; + b0 = 2; + break; + + case 6: + object = Blocks.SAPLING; + b0 = 3; + break; + + case 7: + object = Blocks.RED_MUSHROOM; + break; + + case 8: + object = Blocks.BROWN_MUSHROOM; + break; + + case 9: + object = Blocks.CACTUS; + break; + + case 10: + object = Blocks.DEAD_BUSH; + break; + + case 11: + object = Blocks.LONG_GRASS; + b0 = 2; + break; + + case 12: + object = Blocks.SAPLING; + b0 = 4; + break; + + case 13: + object = Blocks.SAPLING; + b0 = 5; + } + + return new TileEntityFlowerPot(Item.getItemOf((Block) object), b0); + } +} diff --git a/src/main/java/net/minecraft/server/BlockGrass.java b/src/main/java/net/minecraft/server/BlockGrass.java index 7b78579..bdf3172 100644 --- a/src/main/java/net/minecraft/server/BlockGrass.java +++ b/src/main/java/net/minecraft/server/BlockGrass.java @@ -37,7 +37,8 @@ public class BlockGrass extends Block implements IBlockFragilePlantElement { } // CraftBukkit end } else if (world.getLightLevel(i, j + 1, k) >= 9) { - for (int l = 0; l < 4; ++l) { + int numGrowth = Math.min(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); // Spigot + for (int l = 0; l < numGrowth; ++l) { // Spigot int i1 = i + random.nextInt(3) - 1; int j1 = j + random.nextInt(5) - 3; int k1 = k + random.nextInt(3) - 1; diff --git a/src/main/java/net/minecraft/server/BlockMushroom.java b/src/main/java/net/minecraft/server/BlockMushroom.java index 54a399f..4cab3eb 100644 --- a/src/main/java/net/minecraft/server/BlockMushroom.java +++ b/src/main/java/net/minecraft/server/BlockMushroom.java @@ -23,7 +23,7 @@ public class BlockMushroom extends BlockPlant implements IBlockFragilePlantEleme public void a(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(Math.max(1, (int) world.growthOdds / world.spigotConfig.mushroomModifier * 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 6ac6d94..a401f65 100644 --- a/src/main/java/net/minecraft/server/BlockMycel.java +++ b/src/main/java/net/minecraft/server/BlockMycel.java @@ -32,7 +32,8 @@ public class BlockMycel extends Block { } // CraftBukkit end } else if (world.getLightLevel(i, j + 1, k) >= 9) { - for (int l = 0; l < 4; ++l) { + int numGrowth = Math.min(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); // Spigot + for (int l = 0; l < numGrowth; ++l) { // Spigot int i1 = i + random.nextInt(3) - 1; int j1 = j + random.nextInt(5) - 3; int k1 = k + random.nextInt(3) - 1; diff --git a/src/main/java/net/minecraft/server/BlockPortal.java b/src/main/java/net/minecraft/server/BlockPortal.java index bec5aa8..4f5bea9 100644 --- a/src/main/java/net/minecraft/server/BlockPortal.java +++ b/src/main/java/net/minecraft/server/BlockPortal.java @@ -15,7 +15,7 @@ public class BlockPortal extends BlockHalfTransparent { public void a(World world, int i, int j, int k, Random random) { super.a(world, i, j, k, random); - if (world.worldProvider.d() && world.getGameRules().getBoolean("doMobSpawning") && random.nextInt(2000) < world.difficulty.a()) { + if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.worldProvider.d() && world.getGameRules().getBoolean("doMobSpawning") && random.nextInt(2000) < world.difficulty.a()) { // Spigot int l; for (l = j; !World.a((IBlockAccess) world, i, l, k) && l > 0; --l) { @@ -23,8 +23,7 @@ public class BlockPortal extends BlockHalfTransparent { } if (l > 0 && !world.getType(i, l + 1, k).r()) { - // CraftBukkit - set spawn reason to NETHER_PORTAL - Entity entity = ItemMonsterEgg.spawnCreature(world, 57, (double) i + 0.5D, (double) l + 1.1D, (double) k + 0.5D, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL); + Entity entity = ItemMonsterEgg.a(world, 57, (double) i + 0.5D, (double) l + 1.1D, (double) k + 0.5D); if (entity != null) { entity.portalCooldown = entity.ai(); diff --git a/src/main/java/net/minecraft/server/BlockRedstoneTorch.java b/src/main/java/net/minecraft/server/BlockRedstoneTorch.java index 8e01414..e0469bb 100644 --- a/src/main/java/net/minecraft/server/BlockRedstoneTorch.java +++ b/src/main/java/net/minecraft/server/BlockRedstoneTorch.java @@ -11,7 +11,7 @@ import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit public class BlockRedstoneTorch extends BlockTorch { private boolean isOn; - private static Map b = new HashMap(); + private static Map b = new java.util.WeakHashMap(); // Spigot private boolean a(World world, int i, int j, int k, boolean flag) { if (!b.containsKey(world)) { diff --git a/src/main/java/net/minecraft/server/BlockReed.java b/src/main/java/net/minecraft/server/BlockReed.java index a1350f6..6c04ad2 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 / world.spigotConfig.caneModifier * 15) + 0.5F, 15)) { // Spigot org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j + 1, k, this, 0); // CraftBukkit world.setData(i, j, k, 0, 4); } else { diff --git a/src/main/java/net/minecraft/server/BlockSapling.java b/src/main/java/net/minecraft/server/BlockSapling.java index 830eb65..24c95ef 100644 --- a/src/main/java/net/minecraft/server/BlockSapling.java +++ b/src/main/java/net/minecraft/server/BlockSapling.java @@ -26,7 +26,7 @@ public class BlockSapling extends BlockPlant implements IBlockFragilePlantElemen public void a(World world, int i, int j, int k, Random random) { if (!world.isStatic) { super.a(world, i, j, k, random); - if (world.getLightLevel(i, j + 1, k) >= 9 && random.nextInt(7) == 0) { + if (world.getLightLevel(i, j + 1, k) >= 9 && (random.nextInt(Math.max(2, (int) ((world.growthOdds / world.spigotConfig.saplingModifier * 7) + 0.5F))) == 0)) { // Spigot 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 4fae805..a90fab1 100644 --- a/src/main/java/net/minecraft/server/BlockStem.java +++ b/src/main/java/net/minecraft/server/BlockStem.java @@ -26,7 +26,7 @@ public class BlockStem extends BlockPlant implements IBlockFragilePlantElement { 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 / (this == Blocks.PUMPKIN_STEM? world.spigotConfig.pumpkinModifier : world.spigotConfig.melonModifier) * (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/BlockTNT.java b/src/main/java/net/minecraft/server/BlockTNT.java new file mode 100644 index 0000000..50e2fa0 --- /dev/null +++ b/src/main/java/net/minecraft/server/BlockTNT.java @@ -0,0 +1,85 @@ +package net.minecraft.server; + +import java.util.Random; + +public class BlockTNT extends Block { + + public BlockTNT() { + super(Material.TNT); + this.a(CreativeModeTab.d); + } + + public void onPlace(World world, int i, int j, int k) { + super.onPlace(world, i, j, k); + if (world.isBlockIndirectlyPowered(i, j, k)) { + this.postBreak(world, i, j, k, 1); + world.setAir(i, j, k); + } + } + + public void doPhysics(World world, int i, int j, int k, Block block) { + if (world.isBlockIndirectlyPowered(i, j, k)) { + this.postBreak(world, i, j, k, 1); + world.setAir(i, j, k); + } + } + + public int a(Random random) { + return 1; + } + + public void wasExploded(World world, int i, int j, int k, Explosion explosion) { + if (!world.isStatic) { + EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) ((float) i + 0.5F), (double) ((float) j + 0.5F), (double) ((float) k + 0.5F), explosion.c()); + + entitytntprimed.fuseTicks = world.random.nextInt(entitytntprimed.fuseTicks / 4) + entitytntprimed.fuseTicks / 8; + world.addEntity(entitytntprimed); + } + } + + public void postBreak(World world, int i, int j, int k, int l) { + this.a(world, i, j, k, l, (EntityLiving) null); + } + + public void a(World world, int i, int j, int k, int l, EntityLiving entityliving) { + if (!world.isStatic) { + if ((l & 1) == 1) { + EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) ((float) i + 0.5F), (double) ((float) j + 0.5F), (double) ((float) k + 0.5F), entityliving); + + world.addEntity(entitytntprimed); + world.makeSound(entitytntprimed, "game.tnt.primed", 1.0F, 1.0F); + } + } + } + + public boolean interact(World world, int i, int j, int k, EntityHuman entityhuman, int l, float f, float f1, float f2) { + if (entityhuman.bD() != null && entityhuman.bD().getItem() == Items.FLINT_AND_STEEL) { + this.a(world, i, j, k, 1, (EntityLiving) entityhuman); // Spigot - Fix decompile error! + world.setAir(i, j, k); + entityhuman.bD().damage(1, entityhuman); + return true; + } else { + return super.interact(world, i, j, k, entityhuman, l, f, f1, f2); + } + } + + public void a(World world, int i, int j, int k, Entity entity) { + if (entity instanceof EntityArrow && !world.isStatic) { + EntityArrow entityarrow = (EntityArrow) entity; + + if (entityarrow.isBurning()) { + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityarrow, i, j, k, Blocks.AIR, 0).isCancelled()) { + return; + } + // CraftBukkit end + this.a(world, i, j, k, 1, entityarrow.shooter instanceof EntityLiving ? (EntityLiving) entityarrow.shooter : null); + world.setAir(i, j, k); + } + } + } + + public boolean a(Explosion explosion) { + return false; + } +} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index bc3dd41..944170b 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -40,6 +40,7 @@ public class Chunk { public int r; public long s; private int x; + protected gnu.trove.map.hash.TObjectIntHashMap entityCount = new gnu.trove.map.hash.TObjectIntHashMap(); // Spigot public Chunk(World world, int i, int j) { this.sections = new ChunkSection[16]; @@ -114,7 +115,7 @@ public class Chunk { } this.sections[l1].setTypeId(l, j1 & 15, i1, block); - this.sections[l1].setData(l, j1 & 15, i1, abyte[k1]); + this.sections[l1].setData(l, j1 & 15, i1, checkData( block, abyte[k1] ) ); } } } @@ -397,6 +398,17 @@ public class Chunk { } } + // Spigot start - prevent invalid data values + private static int checkData( Block block, int l ) + { + if (block == Block.b( "minecraft:double_plant" ) ) + { + return l == 7 ? 0 : l; + } + return l; + } + // Spigot end + public boolean a(int i, int j, int k, Block block, int l) { int i1 = k << 4 | i; @@ -451,7 +463,7 @@ public class Chunk { if (chunksection.getTypeId(i, j & 15, k) != block) { return false; } else { - chunksection.setData(i, j & 15, k, l); + chunksection.setData(i, j & 15, k, checkData( block, l ) ); if (flag) { this.initLighting(); } else { @@ -521,8 +533,9 @@ public class Chunk { return false; } else { this.n = true; - chunksection.setData(i, j & 15, k, l); - if (chunksection.getTypeId(i, j & 15, k) instanceof IContainer) { + Block block = chunksection.getTypeId( i, j & 15, k ); + chunksection.setData(i, j & 15, k, checkData( block, l ) ); + if (block instanceof IContainer) { TileEntity tileentity = this.e(i, j, k); if (tileentity != null) { @@ -612,6 +625,22 @@ public class Chunk { entity.aj = k; entity.ak = this.locZ; this.entitySlices[k].add(entity); + // Spigot start - increment creature type count + // Keep this synced up with World.a(Class) + if (entity instanceof EntityInsentient) { + EntityInsentient entityinsentient = (EntityInsentient) entity; + if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) { + return; + } + } + for ( EnumCreatureType creatureType : EnumCreatureType.values() ) + { + if ( creatureType.a().isAssignableFrom( entity.getClass() ) ) + { + this.entityCount.adjustOrPutValue( creatureType.a(), 1, 1 ); + } + } + // Spigot end } public void b(Entity entity) { @@ -628,6 +657,22 @@ public class Chunk { } this.entitySlices[i].remove(entity); + // Spigot start - decrement creature type count + // Keep this synced up with World.a(Class) + if (entity instanceof EntityInsentient) { + EntityInsentient entityinsentient = (EntityInsentient) entity; + if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) { + return; + } + } + for ( EnumCreatureType creatureType : EnumCreatureType.values() ) + { + if ( creatureType.a().isAssignableFrom( entity.getClass() ) ) + { + this.entityCount.adjustValue( creatureType.a(), -1 ); + } + } + // Spigot end } public boolean d(int i, int j, int k) { @@ -727,6 +772,15 @@ public class Chunk { while (iterator.hasNext()) { TileEntity tileentity = (TileEntity) iterator.next(); + // Spigot Start + if ( tileentity instanceof IInventory ) + { + for ( org.bukkit.craftbukkit.entity.CraftHumanEntity h : new ArrayList( (List) ( (IInventory) tileentity ).getViewers() ) ) + { + h.getHandle().closeInventory(); + } + } + // Spigot End this.world.a(tileentity); } @@ -736,6 +790,15 @@ public class Chunk { java.util.Iterator iter = this.entitySlices[i].iterator(); while (iter.hasNext()) { Entity entity = (Entity) iter.next(); + // Spigot Start + if ( entity instanceof IInventory ) + { + for ( org.bukkit.craftbukkit.entity.CraftHumanEntity h : new ArrayList( (List) ( (IInventory) entity ).getViewers() ) ) + { + h.getHandle().closeInventory(); + } + } + // Spigot End // Do not pass along players, as doing so can get them stuck outside of time. // (which for example disables inventory icon updates and prevents block breaking) @@ -873,13 +936,21 @@ public class Chunk { } this.m = true; - if (!this.lit && this.done) { + if (!this.lit && this.done && this.world.spigotConfig.randomLightUpdates) { // Spigot - also use random light updates setting to determine if we should relight this.p(); } } public boolean k() { - return this.m && this.done && this.lit; + // Spigot Start + /* + * As of 1.7, Mojang added a check to make sure that only chunks which have been lit are sent to the client. + * Unfortunately this interferes with our modified chunk ticking algorithm, which will only tick chunks distant from the player on a very infrequent basis. + * We cannot unfortunately do this lighting stage during chunk gen as it appears to put a lot more noticeable load on the server, than when it is done at play time. + * For now at least we will simply send all chunks, in accordance with pre 1.7 behaviour. + */ + return true; + // Spigot End } public ChunkCoordIntPair l() { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index bf60f4e..b88f75c 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -14,7 +14,6 @@ import org.apache.logging.log4j.Logger; import java.util.Random; import org.bukkit.Server; -import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor; import org.bukkit.craftbukkit.util.LongHash; import org.bukkit.craftbukkit.util.LongHashSet; import org.bukkit.craftbukkit.util.LongObjectHashMap; @@ -91,39 +90,23 @@ public class ChunkProviderServer implements IChunkProvider { public Chunk getChunkAt(int i, int j, Runnable runnable) { this.unloadQueue.remove(i, j); - Chunk chunk = this.chunks.get(LongHash.toLong(i, j)); + Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j)); + boolean newChunk = false; ChunkRegionLoader loader = null; if (this.f instanceof ChunkRegionLoader) { loader = (ChunkRegionLoader) this.f; } - // We can only use the queue for already generated chunks - if (chunk == null && loader != null && loader.chunkExists(this.world, i, j)) { - if (runnable != null) { - ChunkIOExecutor.queueChunkLoad(this.world, loader, this, i, j, runnable); - return null; - } else { - chunk = ChunkIOExecutor.syncChunkLoad(this.world, loader, this, i, j); - } - } else if (chunk == null) { - chunk = this.originalGetChunkAt(i, j); - } - - // If we didn't load the chunk async and have a callback run it now - if (runnable != null) { - runnable.run(); + // If the chunk exists but isn't loaded do it async + if (chunk == null && runnable != null && loader != null && loader.chunkExists(this.world, i, j)) { + org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.queueChunkLoad(this.world, loader, this, i, j, runnable); + return null; } - - return chunk; - } - - public Chunk originalGetChunkAt(int i, int j) { - this.unloadQueue.remove(i, j); - Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j)); - boolean newChunk = false; + // CraftBukkit end if (chunk == null) { + org.bukkit.craftbukkit.SpigotTimings.syncChunkLoadTimer.startTiming(); // Spigot chunk = this.loadChunk(i, j); if (chunk == null) { if (this.chunkProvider == null) { @@ -159,8 +142,15 @@ public class ChunkProviderServer implements IChunkProvider { } // CraftBukkit end chunk.a(this, this, i, j); + org.bukkit.craftbukkit.SpigotTimings.syncChunkLoadTimer.stopTiming(); // Spigot } + // CraftBukkit start - If we didn't need to load the chunk run the callback now + if (runnable != null) { + runnable.run(); + } + // CraftBukkit end + return chunk; } diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index d5cf88d..76b081a 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -16,6 +16,7 @@ import org.apache.logging.log4j.Logger; public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { + private java.util.LinkedHashMap pendingSaves = new java.util.LinkedHashMap(); // Spigot private static final Logger a = LogManager.getLogger(); private List b = new ArrayList(); private Set c = new HashSet(); @@ -31,13 +32,11 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j); synchronized (this.d) { - if (this.c.contains(chunkcoordintpair)) { - for (int k = 0; k < this.b.size(); ++k) { - if (((PendingChunkToSave) this.b.get(k)).a.equals(chunkcoordintpair)) { - return true; - } - } - } + // Spigot start + if (pendingSaves.containsKey(chunkcoordintpair)) { + return true; + } + // Spigot end } return RegionFileCache.a(this.e, i, j).chunkExists(i & 31, j & 31); @@ -64,14 +63,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { Object object = this.d; synchronized (this.d) { - if (this.c.contains(chunkcoordintpair)) { - for (int k = 0; k < this.b.size(); ++k) { - if (((PendingChunkToSave) this.b.get(k)).a.equals(chunkcoordintpair)) { - nbttagcompound = ((PendingChunkToSave) this.b.get(k)).b; - break; - } - } + // Spigot start + PendingChunkToSave pendingchunktosave = pendingSaves.get(chunkcoordintpair); + if (pendingchunktosave != null) { + nbttagcompound = pendingchunktosave.b; } + // Spigot end } if (nbttagcompound == null) { @@ -151,17 +148,11 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { Object object = this.d; synchronized (this.d) { - if (this.c.contains(chunkcoordintpair)) { - for (int i = 0; i < this.b.size(); ++i) { - if (((PendingChunkToSave) this.b.get(i)).a.equals(chunkcoordintpair)) { - this.b.set(i, new PendingChunkToSave(chunkcoordintpair, nbttagcompound)); - return; - } - } + // Spigot start + if (this.pendingSaves.put(chunkcoordintpair, new PendingChunkToSave(chunkcoordintpair, nbttagcompound)) != null) { + return; } - - this.b.add(new PendingChunkToSave(chunkcoordintpair, nbttagcompound)); - this.c.add(chunkcoordintpair); + // Spigot end FileIOThread.a.a(this); } } @@ -171,12 +162,14 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { Object object = this.d; synchronized (this.d) { - if (this.b.isEmpty()) { + // Spigot start + if (this.pendingSaves.isEmpty()) { return false; } - pendingchunktosave = (PendingChunkToSave) this.b.remove(0); - this.c.remove(pendingchunktosave.a); + pendingchunktosave = this.pendingSaves.values().iterator().next(); + this.pendingSaves.remove(pendingchunktosave.a); + // Spigot end } if (pendingchunktosave != null) { @@ -232,15 +225,15 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255)); nbttagcompound1.setByteArray("Blocks", chunksection.getIdArray()); if (chunksection.getExtendedIdArray() != null) { - nbttagcompound1.setByteArray("Add", chunksection.getExtendedIdArray().a); + nbttagcompound1.setByteArray("Add", chunksection.getExtendedIdArray().getValueArray()); // Spigot } - nbttagcompound1.setByteArray("Data", chunksection.getDataArray().a); - nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().a); + nbttagcompound1.setByteArray("Data", chunksection.getDataArray().getValueArray()); // Spigot + nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().getValueArray()); // Spigot if (flag) { - nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().a); + nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().getValueArray()); // Spigot } else { - nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().a.length]); + nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().getValueArray().length]); // Spigot } nbttaglist.add(nbttagcompound1); diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java index a05efa0..360de1a 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -140,7 +140,8 @@ public class ChunkSection { } } } else { - byte[] ext = this.extBlockIds.a; + this.extBlockIds.forceToNonTrivialArray(); // Spigot + byte[] ext = this.extBlockIds.getValueArray(); for (int off = 0, off2 = 0; off < blkIds.length;) { byte extid = ext[off2]; int l = (blkIds[off] & 0xFF) | ((extid & 0xF) << 8); // Even data @@ -171,6 +172,12 @@ public class ChunkSection { off++; off2++; } + // Spigot start + this.extBlockIds.detectAndProcessTrivialArray(); + if (this.extBlockIds.isTrivialArray() && (this.extBlockIds.getTrivialArrayValue() == 0)) { + this.extBlockIds = null; + } + // Spigot end } this.nonEmptyBlockCount = cntNonEmpty; this.tickingBlockCount = cntTicking; @@ -224,12 +231,11 @@ public class ChunkSection { public void setExtendedIdArray(NibbleArray nibblearray) { // CraftBukkit start - Don't hang on to an empty nibble array boolean empty = true; - for (int i = 0; i < nibblearray.a.length; i++) { - if (nibblearray.a[i] != 0) { - empty = false; - break; - } + // Spigot start + if ((!nibblearray.isTrivialArray()) || (nibblearray.getTrivialArrayValue() != 0)) { + empty = false; } + // Spigot end if (empty) { return; @@ -253,11 +259,11 @@ public class ChunkSection { // CraftBukkit start - Validate array lengths private NibbleArray validateNibbleArray(NibbleArray nibbleArray) { - if (nibbleArray != null && nibbleArray.a.length < 2048) { - byte[] newArray = new byte[2048]; - System.arraycopy(nibbleArray.a, 0, newArray, 0, ((nibbleArray.a.length > 2048) ? 2048 : nibbleArray.a.length)); - nibbleArray = new NibbleArray(newArray, 4); + // Spigot start - fix for more awesome nibble arrays + if (nibbleArray != null && nibbleArray.getByteLength() < 2048) { + nibbleArray.resizeArray(2048); } + // Spigot end return nibbleArray; } diff --git a/src/main/java/net/minecraft/server/CommandBlockListenerAbstract.java b/src/main/java/net/minecraft/server/CommandBlockListenerAbstract.java index 5271272..f8d2ecb 100644 --- a/src/main/java/net/minecraft/server/CommandBlockListenerAbstract.java +++ b/src/main/java/net/minecraft/server/CommandBlockListenerAbstract.java @@ -96,7 +96,7 @@ public abstract class CommandBlockListenerAbstract implements ICommandListener { // Make sure this is a valid command if (commandMap.getCommand(args[0]) == null) { - this.b = 0; + this.b = org.spigotmc.VanillaCommandWrapper.dispatch( sender, command ); // Spigot - Try vanilla commands return; } diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java new file mode 100644 index 0000000..e58be15 --- /dev/null +++ b/src/main/java/net/minecraft/server/CommandDispatcher.java @@ -0,0 +1,93 @@ +package net.minecraft.server; + +import java.util.Iterator; + +public class CommandDispatcher extends CommandHandler implements ICommandDispatcher { + + public CommandDispatcher() { + this.a(new CommandTime()); + this.a(new CommandGamemode()); + this.a(new CommandDifficulty()); + this.a(new CommandGamemodeDefault()); + this.a(new CommandKill()); + this.a(new CommandToggleDownfall()); + this.a(new CommandWeather()); + this.a(new CommandXp()); + this.a(new CommandTp()); + this.a(new CommandGive()); + this.a(new CommandEffect()); + this.a(new CommandEnchant()); + this.a(new CommandMe()); + this.a(new CommandSeed()); + this.a(new CommandHelp()); + this.a(new CommandDebug()); + this.a(new CommandTell()); + this.a(new CommandSay()); + this.a(new CommandSpawnpoint()); + this.a(new CommandSetWorldSpawn()); + this.a(new CommandGamerule()); + this.a(new CommandClear()); + this.a(new CommandTestFor()); + this.a(new CommandSpreadPlayers()); + this.a(new CommandPlaySound()); + this.a(new CommandScoreboard()); + this.a(new CommandAchievement()); + this.a(new CommandSummon()); + this.a(new CommandSetBlock()); + this.a(new CommandTestForBlock()); + this.a(new CommandTellRaw()); + if (MinecraftServer.getServer().V()) { + this.a(new CommandOp()); + this.a(new CommandDeop()); + this.a(new CommandStop()); + this.a(new CommandSaveAll()); + this.a(new CommandSaveOff()); + this.a(new CommandSaveOn()); + this.a(new CommandBanIp()); + this.a(new CommandPardonIP()); + this.a(new CommandBan()); + this.a(new CommandBanList()); + this.a(new CommandPardon()); + this.a(new CommandKick()); + this.a(new CommandList()); + this.a(new CommandWhitelist()); + this.a(new CommandIdleTimeout()); + } else { + this.a(new CommandPublish()); + } + + CommandAbstract.a((ICommandDispatcher) this); + } + + public void a(ICommandListener icommandlistener, int i, String s, Object... aobject) { + boolean flag = true; + + if (icommandlistener instanceof CommandBlockListenerAbstract && !MinecraftServer.getServer().worlds.get(0).getGameRules().getBoolean("commandBlockOutput")) { // Spigot - worldServer -> worlds + flag = false; + } + + ChatMessage chatmessage = new ChatMessage("chat.type.admin", new Object[] { icommandlistener.getName(), new ChatMessage(s, aobject)}); + + chatmessage.b().setColor(EnumChatFormat.GRAY); + chatmessage.b().setItalic(Boolean.valueOf(true)); + if (flag) { + Iterator iterator = MinecraftServer.getServer().getPlayerList().players.iterator(); + + while (iterator.hasNext()) { + EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + + if (entityplayer != icommandlistener && MinecraftServer.getServer().getPlayerList().isOp(entityplayer.getName())) { + entityplayer.sendMessage(chatmessage); + } + } + } + + if (icommandlistener != MinecraftServer.getServer()) { + MinecraftServer.getServer().sendMessage(chatmessage); + } + + if ((i & 1) != 1) { + icommandlistener.sendMessage(new ChatMessage(s, aobject)); + } + } +} diff --git a/src/main/java/net/minecraft/server/DataWatcher.java b/src/main/java/net/minecraft/server/DataWatcher.java new file mode 100644 index 0000000..cca6bd9 --- /dev/null +++ b/src/main/java/net/minecraft/server/DataWatcher.java @@ -0,0 +1,306 @@ +package net.minecraft.server; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import net.minecraft.util.org.apache.commons.lang3.ObjectUtils; + +public class DataWatcher { + + private final Entity a; + private boolean b = true; + // Spigot Start + private static final gnu.trove.map.TObjectIntMap classToId = new gnu.trove.map.hash.TObjectIntHashMap( 10, 0.5f, -1 ); + private final gnu.trove.map.TIntObjectMap dataValues = new gnu.trove.map.hash.TIntObjectHashMap( 10, 0.5f, -1 ); + // These exist as an attempt at backwards compatability for (broken) NMS plugins + private static final Map c = gnu.trove.TDecorators.wrap( classToId ); + private final Map d = gnu.trove.TDecorators.wrap( dataValues ); + // Spigot End + private boolean e; + private ReadWriteLock f = new ReentrantReadWriteLock(); + + public DataWatcher(Entity entity) { + this.a = entity; + } + + public void a(int i, Object object) { + int integer = classToId.get(object.getClass()); // Spigot + + if (integer == -1) { // Spigot + throw new IllegalArgumentException("Unknown data type: " + object.getClass()); + } else if (i > 31) { + throw new IllegalArgumentException("Data value id is too big with " + i + "! (Max is " + 31 + ")"); + } else if (this.dataValues.containsKey(i)) { // Spigot + throw new IllegalArgumentException("Duplicate id value for " + i + "!"); + } else { + WatchableObject watchableobject = new WatchableObject(integer, i, object); // Spigot + + this.f.writeLock().lock(); + this.dataValues.put(i, watchableobject); // Spigot + this.f.writeLock().unlock(); + this.b = false; + } + } + + public void a(int i, int j) { + WatchableObject watchableobject = new WatchableObject(j, i, null); + + this.f.writeLock().lock(); + this.dataValues.put(i, watchableobject); // Spigot + this.f.writeLock().unlock(); + this.b = false; + } + + public byte getByte(int i) { + return ((Byte) this.i(i).b()).byteValue(); + } + + public short getShort(int i) { + return ((Short) this.i(i).b()).shortValue(); + } + + public int getInt(int i) { + return ((Integer) this.i(i).b()).intValue(); + } + + public float getFloat(int i) { + return ((Float) this.i(i).b()).floatValue(); + } + + public String getString(int i) { + return (String) this.i(i).b(); + } + + public ItemStack getItemStack(int i) { + return (ItemStack) this.i(i).b(); + } + + private WatchableObject i(int i) { + this.f.readLock().lock(); + + WatchableObject watchableobject; + + try { + watchableobject = (WatchableObject) this.dataValues.get(i); // Spigot + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Getting synched entity data"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Synched entity data"); + + crashreportsystemdetails.a("Data ID", Integer.valueOf(i)); + throw new ReportedException(crashreport); + } + + this.f.readLock().unlock(); + return watchableobject; + } + + public void watch(int i, Object object) { + WatchableObject watchableobject = this.i(i); + + if (ObjectUtils.notEqual(object, watchableobject.b())) { + watchableobject.a(object); + this.a.i(i); + watchableobject.a(true); + this.e = true; + } + } + + public void h(int i) { + WatchableObject.a(this.i(i), true); + this.e = true; + } + + public boolean a() { + return this.e; + } + + public static void a(List list, PacketDataSerializer packetdataserializer) { + if (list != null) { + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + WatchableObject watchableobject = (WatchableObject) iterator.next(); + + a(packetdataserializer, watchableobject); + } + } + + packetdataserializer.writeByte(127); + } + + public List b() { + ArrayList arraylist = null; + + if (this.e) { + this.f.readLock().lock(); + Iterator iterator = this.dataValues.valueCollection().iterator(); // Spigot + + while (iterator.hasNext()) { + WatchableObject watchableobject = (WatchableObject) iterator.next(); + + if (watchableobject.d()) { + watchableobject.a(false); + if (arraylist == null) { + arraylist = new ArrayList(); + } + + arraylist.add(watchableobject); + } + } + + this.f.readLock().unlock(); + } + + this.e = false; + return arraylist; + } + + public void a(PacketDataSerializer packetdataserializer) { + this.f.readLock().lock(); + Iterator iterator = this.dataValues.valueCollection().iterator(); // Spigot + + while (iterator.hasNext()) { + WatchableObject watchableobject = (WatchableObject) iterator.next(); + + a(packetdataserializer, watchableobject); + } + + this.f.readLock().unlock(); + packetdataserializer.writeByte(127); + } + + public List c() { + ArrayList arraylist = new ArrayList(); // Spigot + + this.f.readLock().lock(); + + arraylist.addAll(this.dataValues.valueCollection()); // Spigot + + this.f.readLock().unlock(); + return arraylist; + } + + private static void a(PacketDataSerializer packetdataserializer, WatchableObject watchableobject) { + int i = (watchableobject.c() << 5 | watchableobject.a() & 31) & 255; + + packetdataserializer.writeByte(i); + switch (watchableobject.c()) { + case 0: + packetdataserializer.writeByte(((Byte) watchableobject.b()).byteValue()); + break; + + case 1: + packetdataserializer.writeShort(((Short) watchableobject.b()).shortValue()); + break; + + case 2: + packetdataserializer.writeInt(((Integer) watchableobject.b()).intValue()); + break; + + case 3: + packetdataserializer.writeFloat(((Float) watchableobject.b()).floatValue()); + break; + + case 4: + try { + packetdataserializer.a((String) watchableobject.b()); + } catch (java.io.IOException ex) { + throw new RuntimeException(ex); + } + break; + + case 5: + ItemStack itemstack = (ItemStack) watchableobject.b(); + + packetdataserializer.a(itemstack); + break; + + case 6: + ChunkCoordinates chunkcoordinates = (ChunkCoordinates) watchableobject.b(); + + packetdataserializer.writeInt(chunkcoordinates.x); + packetdataserializer.writeInt(chunkcoordinates.y); + packetdataserializer.writeInt(chunkcoordinates.z); + } + } + + public static List b(PacketDataSerializer packetdataserializer) { + ArrayList arraylist = null; + + for (byte b0 = packetdataserializer.readByte(); b0 != 127; b0 = packetdataserializer.readByte()) { + if (arraylist == null) { + arraylist = new ArrayList(); + } + + int i = (b0 & 224) >> 5; + int j = b0 & 31; + WatchableObject watchableobject = null; + + switch (i) { + case 0: + watchableobject = new WatchableObject(i, j, Byte.valueOf(packetdataserializer.readByte())); + break; + + case 1: + watchableobject = new WatchableObject(i, j, Short.valueOf(packetdataserializer.readShort())); + break; + + case 2: + watchableobject = new WatchableObject(i, j, Integer.valueOf(packetdataserializer.readInt())); + break; + + case 3: + watchableobject = new WatchableObject(i, j, Float.valueOf(packetdataserializer.readFloat())); + break; + + case 4: + try { + watchableobject = new WatchableObject(i, j, packetdataserializer.c(32767)); + } catch (java.io.IOException ex) { + throw new RuntimeException(ex); + } + break; + + case 5: + watchableobject = new WatchableObject(i, j, packetdataserializer.c()); + break; + + case 6: + int k = packetdataserializer.readInt(); + int l = packetdataserializer.readInt(); + int i1 = packetdataserializer.readInt(); + + watchableobject = new WatchableObject(i, j, new ChunkCoordinates(k, l, i1)); + } + + arraylist.add(watchableobject); + } + + return arraylist; + } + + public boolean d() { + return this.b; + } + + public void e() { + this.e = false; + } + + static { + // Spigot Start - remove valueOf + classToId.put(Byte.class, 0); + classToId.put(Short.class, 1); + classToId.put(Integer.class, 2); + classToId.put(Float.class, 3); + classToId.put(String.class, 4); + classToId.put(ItemStack.class, 5); + classToId.put(ChunkCoordinates.class, 6); + // Spigot End + } +} diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java old mode 100644 new mode 100755 index 30ce5f4..8ec6474 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -109,21 +109,32 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer if (this.J() < 0) { this.setPort(this.propertyManager.getInt("server-port", 25565)); } + // Spigot start + this.a((PlayerList) (new DedicatedPlayerList(this))); + org.spigotmc.SpigotConfig.init(); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end h.info("Generating keypair"); this.a(MinecraftEncryption.b()); h.info("Starting Minecraft server on " + (this.getServerIp().length() == 0 ? "*" : this.getServerIp()) + ":" + this.J()); - try { - this.ag().a(inetaddress, this.J()); - } catch (Throwable ioexception) { // CraftBukkit - IOException -> Throwable - h.warn("**** FAILED TO BIND TO PORT!"); - h.warn("The exception was: {}", new Object[] { ioexception.toString()}); - h.warn("Perhaps a server is already running on that port?"); - return false; + if (!org.spigotmc.SpigotConfig.lateBind) { + try { + this.ag().a(inetaddress, this.J()); + } catch (Throwable ioexception) { // CraftBukkit - IOException -> Throwable + h.warn("**** FAILED TO BIND TO PORT!"); + h.warn("The exception was: {}", new Object[] { ioexception.toString()}); + h.warn("Perhaps a server is already running on that port?"); + return false; + } } - this.a((PlayerList) (new DedicatedPlayerList(this))); // CraftBukkit + // Spigot Start - Move DedicatedPlayerList up and bring plugin loading from CraftServer to here + // this.a((PlayerList) (new DedicatedPlayerList(this))); + server.loadPlugins(); + server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); + // Spigot End if (!this.getOnlineMode()) { h.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); @@ -177,6 +188,18 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer String s3 = String.format("%.3fs", new Object[] { Double.valueOf((double) i1 / 1.0E9D)}); h.info("Done (" + s3 + ")! For help, type \"help\" or \"?\""); + + if (org.spigotmc.SpigotConfig.lateBind) { + try { + this.ag().a(inetaddress, this.J()); + } catch (Throwable ioexception) { // CraftBukkit - IOException -> Throwable + h.warn("**** FAILED TO BIND TO PORT!"); + h.warn("The exception was: {}", new Object[] { ioexception.toString()}); + h.warn("Perhaps a server is already running on that port?"); + return false; + } + } + if (this.propertyManager.getBoolean("enable-query", false)) { h.info("Starting GS4 status listener"); this.j = new RemoteStatusListener(this); diff --git a/src/main/java/net/minecraft/server/DispenseBehaviorMonsterEgg.java b/src/main/java/net/minecraft/server/DispenseBehaviorMonsterEgg.java index 2c14ab0..a854f7a 100644 --- a/src/main/java/net/minecraft/server/DispenseBehaviorMonsterEgg.java +++ b/src/main/java/net/minecraft/server/DispenseBehaviorMonsterEgg.java @@ -44,7 +44,7 @@ final class DispenseBehaviorMonsterEgg extends DispenseBehaviorItem { itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); - Entity entity = ItemMonsterEgg.spawnCreature(isourceblock.k(), itemstack.getData(), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG); + Entity entity = ItemMonsterEgg.a(isourceblock.k(), itemstack.getData(), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); if (entity instanceof EntityLiving && itemstack.hasName()) { ((EntityInsentient) entity).setCustomName(itemstack.getName()); diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index f74d937..4d09fdb 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -14,6 +14,7 @@ import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Painting; import org.bukkit.entity.Vehicle; +import org.spigotmc.CustomTimingsHandler; // Spigot import org.bukkit.event.entity.EntityCombustByEntityEvent; import org.bukkit.event.painting.PaintingBreakByEntityEvent; import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; @@ -87,7 +88,7 @@ public abstract class Entity { public int ticksLived; public int maxFireTicks; public int fireTicks; // CraftBukkit - private -> public - protected boolean inWater; + public boolean inWater; // Spigot - protected -> public public int noDamageTicks; private boolean justCreated; protected boolean fireProof; @@ -111,6 +112,15 @@ public abstract class Entity { public boolean valid; // CraftBukkit public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only + // Spigot start + public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + public final byte activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = 0; + public boolean fromMobSpawner; + public void inactiveTick() { } + // Spigot end + public int getId() { return this.id; } @@ -130,13 +140,18 @@ public abstract class Entity { this.random = new Random(); this.maxFireTicks = 1; this.justCreated = true; - this.uniqueID = UUID.randomUUID(); + this.uniqueID = new UUID(random.nextLong(), random.nextLong()); // Spigot this.at = EnumEntitySize.SIZE_2; this.world = world; this.setPosition(0.0D, 0.0D, 0.0D); if (world != null) { this.dimension = world.worldProvider.dimension; + // Spigot start + this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); + } else { + this.defaultActivationState = false; } + // Spigot end this.datawatcher = new DataWatcher(this); this.datawatcher.a(0, Byte.valueOf((byte) 0)); @@ -424,6 +439,8 @@ public abstract class Entity { return; } // CraftBukkit end + + org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot if (this.Y) { this.boundingBox.d(d0, d1, d2); this.locX = (this.boundingBox.a + this.boundingBox.d) / 2.0D; @@ -732,6 +749,7 @@ public abstract class Entity { this.world.methodProfiler.b(); } + org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot } protected String H() { @@ -1003,6 +1021,7 @@ public abstract class Entity { public void b_(EntityHuman entityhuman) {} + int numCollisions = 0; // Spigot public void collide(Entity entity) { if (entity.passenger != this && entity.vehicle != this) { double d0 = entity.locX - this.locX; @@ -1115,6 +1134,7 @@ public abstract class Entity { nbttagcompound.setLong("WorldUUIDLeast", this.world.getDataManager().getUUID().getLeastSignificantBits()); nbttagcompound.setLong("WorldUUIDMost", this.world.getDataManager().getUUID().getMostSignificantBits()); nbttagcompound.setInt("Bukkit.updateLevel", CURRENT_LEVEL); + nbttagcompound.setInt("Spigot.ticksLived", this.ticksLived); // CraftBukkit end this.b(nbttagcompound); if (this.vehicle != null) { @@ -1183,6 +1203,8 @@ public abstract class Entity { if (this instanceof EntityLiving) { EntityLiving entity = (EntityLiving) this; + this.ticksLived = nbttagcompound.getInt("Spigot.ticksLived"); + // Reset the persistence for tamed animals if (entity instanceof EntityTameableAnimal && !isLevelAtLeast(nbttagcompound, 2) && !nbttagcompound.getBoolean("PersistenceRequired")) { EntityInsentient entityinsentient = (EntityInsentient) entity; @@ -1435,6 +1457,7 @@ public abstract class Entity { } } // CraftBukkit end + pluginManager.callEvent( new org.spigotmc.event.entity.EntityDismountEvent( this.getBukkitEntity(), this.vehicle.getBukkitEntity() ) ); // Spigot this.setPositionRotation(this.vehicle.locX, this.vehicle.boundingBox.b + (double) this.vehicle.length, this.vehicle.locZ, this.yaw, this.pitch); this.vehicle.passenger = null; @@ -1470,6 +1493,17 @@ public abstract class Entity { } } // CraftBukkit end + // Spigot Start + if ( entity.world.isChunkLoaded( (int) entity.locX >> 4, (int) entity.locZ >> 4 ) ) + { + org.spigotmc.event.entity.EntityMountEvent event = new org.spigotmc.event.entity.EntityMountEvent( this.getBukkitEntity(), entity.getBukkitEntity() ); + pluginManager.callEvent( event ); + if ( event.isCancelled() ) + { + return; + } + } + // Spigot End if (this.vehicle != null) { this.vehicle.passenger = null; diff --git a/src/main/java/net/minecraft/server/EntityAgeable.java b/src/main/java/net/minecraft/server/EntityAgeable.java index 36ed831..7ddca48 100644 --- a/src/main/java/net/minecraft/server/EntityAgeable.java +++ b/src/main/java/net/minecraft/server/EntityAgeable.java @@ -6,6 +6,31 @@ public abstract class EntityAgeable extends EntityCreature { private float bq; public boolean ageLocked = false; // CraftBukkit + // Spigot start + @Override + public void inactiveTick() + { + super.inactiveTick(); + if ( this.world.isStatic || this.ageLocked ) + { // CraftBukkit + this.a( this.isBaby() ); + } else + { + int i = this.getAge(); + + if ( i < 0 ) + { + ++i; + this.setAge( i ); + } else if ( i > 0 ) + { + --i; + this.setAge( i ); + } + } + } + // Spigot end + public EntityAgeable(World world) { super(world); } diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java index 5887ee8..7c9780a 100644 --- a/src/main/java/net/minecraft/server/EntityArrow.java +++ b/src/main/java/net/minecraft/server/EntityArrow.java @@ -15,7 +15,7 @@ public class EntityArrow extends Entity implements IProjectile { private int f = -1; private Block g; private int h; - private boolean inGround; + public boolean inGround = false; // Spigot - private -> public public int fromPlayer; public int shake; public Entity shooter; @@ -144,7 +144,7 @@ public class EntityArrow extends Entity implements IProjectile { if (block == this.g && i == this.h) { ++this.j; - if (this.j == 1200) { + if (this.j == world.spigotConfig.arrowDespawnRate) { // Spigot this.die(); } } else { diff --git a/src/main/java/net/minecraft/server/EntityFallingBlock.java b/src/main/java/net/minecraft/server/EntityFallingBlock.java index 991a765..e15840b 100644 --- a/src/main/java/net/minecraft/server/EntityFallingBlock.java +++ b/src/main/java/net/minecraft/server/EntityFallingBlock.java @@ -86,6 +86,7 @@ public class EntityFallingBlock extends Entity { } this.world.setAir(i, j, k); + world.spigotConfig.antiXrayInstance.updateNearbyBlocks(world, i, j, k); // Spigot } if (this.onGround) { @@ -101,6 +102,7 @@ public class EntityFallingBlock extends Entity { } this.world.setTypeAndData(i, j, k, this.id, this.data, 3); // CraftBukkit end + world.spigotConfig.antiXrayInstance.updateNearbyBlocks(world, i, j, k); // Spigot if (this.id instanceof BlockFalling) { ((BlockFalling) this.id).a(this.world, i, j, k, this.data); diff --git a/src/main/java/net/minecraft/server/EntityFireball.java b/src/main/java/net/minecraft/server/EntityFireball.java index 939de5d..436e6a1 100644 --- a/src/main/java/net/minecraft/server/EntityFireball.java +++ b/src/main/java/net/minecraft/server/EntityFireball.java @@ -199,6 +199,8 @@ public abstract class EntityFireball extends Entity { nbttagcompound.setByte("inGround", (byte) (this.i ? 1 : 0)); // CraftBukkit - Fix direction being mismapped to invalid variables nbttagcompound.set("power", this.a(new double[] { this.dirX, this.dirY, this.dirZ})); + // Spigot - Support vanilla's direction tag + nbttagcompound.set("direction", this.a(new double[] { this.motX, this.motY, this.motZ})); } public void a(NBTTagCompound nbttagcompound) { @@ -215,6 +217,13 @@ public abstract class EntityFireball extends Entity { this.dirY = nbttaglist.d(1); this.dirZ = nbttaglist.d(2); // CraftBukkit end + } else if (nbttagcompound.hasKeyOfType("direction", 9)) { // Spigot - Support vanilla's direction tag + NBTTagList nbttaglist = nbttagcompound.getList("direction", 6); + + this.motX = nbttaglist.d(0); + this.motY = nbttaglist.d(1); + this.motZ = nbttaglist.d(2); + } else { this.die(); } diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java index c12a08f..c58b21b 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java @@ -277,6 +277,10 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen // Update client if (this instanceof EntityPlayer) { ((EntityPlayer) this).playerConnection.sendPacket(new PacketPlayOutSetSlot((byte) 0, activeContainer.a((IInventory) this.inventory, this.inventory.itemInHandIndex).index, this.f)); + // Spigot Start + ((EntityPlayer) this).getBukkitEntity().updateInventory(); + ((EntityPlayer) this).getBukkitEntity().updateScaledHealth(); + // Spigot End } return; } @@ -323,6 +327,7 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen public void setPassengerOf(Entity entity) { // CraftBukkit end if (this.vehicle != null && entity == null) { + world.getServer().getPluginManager().callEvent( new org.spigotmc.event.entity.EntityDismountEvent( this.getBukkitEntity(), this.vehicle.getBukkitEntity() ) ); // Spigot // CraftBukkit start - use parent method instead to correctly fire VehicleExitEvent Entity originalVehicle = this.vehicle; // First statement moved down, second statement handled in parent method. @@ -429,7 +434,7 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen List list = this.world.getEntities(this, axisalignedbb); - if (list != null) { + if (list != null && this.R()) { // Spigot: Add this.R() condition for (int i = 0; i < list.size(); ++i) { Entity entity = (Entity) list.get(i); diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java index 885a0ef..639354b 100644 --- a/src/main/java/net/minecraft/server/EntityInsentient.java +++ b/src/main/java/net/minecraft/server/EntityInsentient.java @@ -417,6 +417,12 @@ public abstract class EntityInsentient extends EntityLiving { this.world.methodProfiler.a("checkDespawn"); this.w(); this.world.methodProfiler.b(); + // Spigot Start + if ( this.fromMobSpawner ) + { + return; + } + // Spigot End this.world.methodProfiler.a("sensing"); this.bq.a(); this.world.methodProfiler.b(); diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java index bbcf674..8ea7abc 100644 --- a/src/main/java/net/minecraft/server/EntityItem.java +++ b/src/main/java/net/minecraft/server/EntityItem.java @@ -104,7 +104,7 @@ public class EntityItem extends Entity { } // ++this.age; // CraftBukkit - Moved up - if (!this.world.isStatic && this.age >= 6000) { + if (!this.world.isStatic && this.age >= world.spigotConfig.itemDespawnRate) { // Spigot // CraftBukkit start if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { this.age = 0; @@ -117,7 +117,10 @@ public class EntityItem extends Entity { } private void k() { - Iterator iterator = this.world.a(EntityItem.class, this.boundingBox.grow(0.5D, 0.0D, 0.5D)).iterator(); + // Spigot start + double radius = world.spigotConfig.itemMerge; + Iterator iterator = this.world.a(EntityItem.class, this.boundingBox.grow(radius, radius, radius)).iterator(); + // Spigot end while (iterator.hasNext()) { EntityItem entityitem = (EntityItem) iterator.next(); @@ -148,11 +151,13 @@ public class EntityItem extends Entity { } else if (itemstack1.count + itemstack.count > itemstack1.getMaxStackSize()) { return false; } else { - itemstack1.count += itemstack.count; - entityitem.pickupDelay = Math.max(entityitem.pickupDelay, this.pickupDelay); - entityitem.age = Math.min(entityitem.age, this.age); - entityitem.setItemStack(itemstack1); - this.die(); + // Spigot start + itemstack.count += itemstack1.count; + this.pickupDelay = Math.max(entityitem.pickupDelay, this.pickupDelay); + this.age = Math.min(entityitem.age, this.age); + this.setItemStack(itemstack); + entityitem.die(); + // Spigot end return true; } } else { diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java index 7211da8..ab28bd3 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -78,6 +78,13 @@ public abstract class EntityLiving extends Entity { public int expToDrop; public int maxAirTicks = 300; // CraftBukkit end + // Spigot start + public void inactiveTick() + { + super.inactiveTick(); + ++this.aV; + } + // Spigot end public EntityLiving(World world) { super(world); @@ -1478,8 +1485,10 @@ public abstract class EntityLiving extends Entity { protected void bo() { List list = this.world.getEntities(this, this.boundingBox.grow(0.20000000298023224D, 0.0D, 0.20000000298023224D)); - if (list != null && !list.isEmpty()) { + if (this.R() && list != null && !list.isEmpty()) { // Spigot: Add this.R() condition + numCollisions -= world.spigotConfig.maxCollisionsPerEntity; // Spigot for (int i = 0; i < list.size(); ++i) { + if (numCollisions > world.spigotConfig.maxCollisionsPerEntity) { break; } // Spigot Entity entity = (Entity) list.get(i); // TODO better check now? @@ -1490,9 +1499,12 @@ public abstract class EntityLiving extends Entity { // CraftBukkit end if (entity.S()) { + entity.numCollisions++; // Spigot + numCollisions++; // Spigot this.n(entity); } } + numCollisions = 0; // Spigot } } diff --git a/src/main/java/net/minecraft/server/EntityMinecartContainer.java b/src/main/java/net/minecraft/server/EntityMinecartContainer.java index 1d997e1..1c64d42 100644 --- a/src/main/java/net/minecraft/server/EntityMinecartContainer.java +++ b/src/main/java/net/minecraft/server/EntityMinecartContainer.java @@ -149,6 +149,12 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp } public void b(int i) { + // Spigot Start + for ( HumanEntity human : new java.util.ArrayList( transaction ) ) + { + human.closeInventory(); + } + // Spigot End this.b = false; super.b(i); } diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 1dee8b2..95dbeb9 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -30,7 +30,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; public class EntityPlayer extends EntityHuman implements ICrafting { private static final Logger bM = LogManager.getLogger(); - private String locale = "en_US"; + public String locale = "en_US"; // Spigot public PlayerConnection playerConnection; public final MinecraftServer server; public final PlayerInteractManager playerInteractManager; @@ -62,7 +62,23 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public int newTotalExp = 0; public boolean keepLevel = false; public double maxHealthCache; + public int lastPing = -1; // Spigot // CraftBukkit end + // Spigot start + public boolean collidesWithEntities = true; + + @Override + public boolean R() + { + return this.collidesWithEntities && super.R(); + } + + @Override + public boolean S() + { + return this.collidesWithEntities && super.S(); + } + // Spigot end public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { super(worldserver, gameprofile); @@ -194,7 +210,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { Chunk chunk; - while (iterator1.hasNext() && arraylist.size() < PacketPlayOutMapChunkBulk.c()) { + while (iterator1.hasNext() && arraylist.size() < this.world.spigotConfig.maxBulkChunk) { // Spigot ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator1.next(); if (chunkcoordintpair != null) { @@ -613,7 +629,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerChest(this.inventory, iinventory)); - if(container == null) return; + if(container == null) { + iinventory.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -626,7 +645,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openHopper(TileEntityHopper tileentityhopper) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerHopper(this.inventory, tileentityhopper)); - if(container == null) return; + if(container == null) { + tileentityhopper.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -639,7 +661,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openMinecartHopper(EntityMinecartHopper entityminecarthopper) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerHopper(this.inventory, entityminecarthopper)); - if(container == null) return; + if(container == null) { + entityminecarthopper.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -652,7 +677,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openFurnace(TileEntityFurnace tileentityfurnace) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerFurnace(this.inventory, tileentityfurnace)); - if(container == null) return; + if(container == null) { + tileentityfurnace.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -665,7 +693,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openDispenser(TileEntityDispenser tileentitydispenser) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerDispenser(this.inventory, tileentitydispenser)); - if(container == null) return; + if(container == null) { + tileentitydispenser.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -678,7 +709,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openBrewingStand(TileEntityBrewingStand tileentitybrewingstand) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerBrewingStand(this.inventory, tileentitybrewingstand)); - if(container == null) return; + if(container == null) { + tileentitybrewingstand.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -691,7 +725,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openBeacon(TileEntityBeacon tileentitybeacon) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerBeacon(this.inventory, tileentitybeacon)); - if(container == null) return; + if(container == null) { + tileentitybeacon.l_(); + return; + } // CraftBukkit end this.nextContainerCounter(); @@ -732,7 +769,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void openHorseInventory(EntityHorse entityhorse, IInventory iinventory) { // CraftBukkit start - Inventory open hook Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerHorse(this.inventory, iinventory, entityhorse)); - if(container == null) return; + if(container == null) { + iinventory.l_(); + return; + } // CraftBukkit end if (this.activeContainer != this.defaultContainer) { diff --git a/src/main/java/net/minecraft/server/EntityTracker.java b/src/main/java/net/minecraft/server/EntityTracker.java index 7447e42..fc679ae 100644 --- a/src/main/java/net/minecraft/server/EntityTracker.java +++ b/src/main/java/net/minecraft/server/EntityTracker.java @@ -91,6 +91,8 @@ public class EntityTracker { } public void addEntity(Entity entity, int i, int j, boolean flag) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous entity track!"); // Spigot + i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot if (i > this.e) { i = this.e; } @@ -125,6 +127,7 @@ public class EntityTracker { } public void untrackEntity(Entity entity) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous entity untrack!"); // Spigot if (entity instanceof EntityPlayer) { EntityPlayer entityplayer = (EntityPlayer) entity; Iterator iterator = this.c.iterator(); diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 9f818cf..8052ea6 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -299,6 +299,7 @@ public class EntityTrackerEntry { } public void updatePlayer(EntityPlayer entityplayer) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous player tracker update!"); // Spigot if (entityplayer != this.tracker) { double d0 = entityplayer.locX - (double) (this.xLoc / 32); double d1 = entityplayer.locZ - (double) (this.zLoc / 32); @@ -515,6 +516,7 @@ public class EntityTrackerEntry { } public void clear(EntityPlayer entityplayer) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous player tracker clear!"); // Spigot if (this.trackedPlayers.contains(entityplayer)) { this.trackedPlayers.remove(entityplayer); entityplayer.removeQueue.add(Integer.valueOf(this.tracker.getId())); diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java index 6c6e03e..5c8b0a8 100644 --- a/src/main/java/net/minecraft/server/EntityZombie.java +++ b/src/main/java/net/minecraft/server/EntityZombie.java @@ -26,7 +26,7 @@ public class EntityZombie extends EntityMonster { this.getNavigation().b(true); this.goalSelector.a(0, new PathfinderGoalFloat(this)); this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, EntityHuman.class, 1.0D, false)); - this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, EntityVillager.class, 1.0D, true)); + if ( world.spigotConfig.zombieAggressiveTowardsVillager ) { this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, EntityVillager.class, 1.0D, true)); } // Spigot this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D)); this.goalSelector.a(6, new PathfinderGoalMoveThroughVillage(this, 1.0D, false)); this.goalSelector.a(7, new PathfinderGoalRandomStroll(this, 1.0D)); @@ -34,7 +34,7 @@ public class EntityZombie extends EntityMonster { this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, true)); this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityHuman.class, 0, true)); - this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityVillager.class, 0, false)); + if ( world.spigotConfig.zombieAggressiveTowardsVillager ) { this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityVillager.class, 0, false)); } // Spigot this.a(0.6F, 1.8F); } diff --git a/src/main/java/net/minecraft/server/Explosion.java b/src/main/java/net/minecraft/server/Explosion.java index 39e5b5b..d2587c1 100644 --- a/src/main/java/net/minecraft/server/Explosion.java +++ b/src/main/java/net/minecraft/server/Explosion.java @@ -239,6 +239,7 @@ public class Explosion { j = chunkposition.y; k = chunkposition.z; block = this.world.getType(i, j, k); + world.spigotConfig.antiXrayInstance.updateNearbyBlocks(world, i, j, k); // Spigot if (flag) { double d0 = (double) ((float) i + this.world.random.nextFloat()); double d1 = (double) ((float) j + this.world.random.nextFloat()); diff --git a/src/main/java/net/minecraft/server/HandshakeListener.java b/src/main/java/net/minecraft/server/HandshakeListener.java index a02c83f..6098eb5 100644 --- a/src/main/java/net/minecraft/server/HandshakeListener.java +++ b/src/main/java/net/minecraft/server/HandshakeListener.java @@ -64,15 +64,28 @@ public class HandshakeListener implements PacketHandshakingInListener { // CraftBukkit end if (packethandshakinginsetprotocol.d() > 4) { - chatcomponenttext = new ChatComponentText("Outdated server! I\'m still on 1.7.2"); + chatcomponenttext = new ChatComponentText( org.spigotmc.SpigotConfig.outdatedServerMessage ); // Spigot this.b.handle(new PacketLoginOutDisconnect(chatcomponenttext), new GenericFutureListener[0]); this.b.a((IChatBaseComponent) chatcomponenttext); } else if (packethandshakinginsetprotocol.d() < 4) { - chatcomponenttext = new ChatComponentText("Outdated client! Please use 1.7.2"); + chatcomponenttext = new ChatComponentText( org.spigotmc.SpigotConfig.outdatedClientMessage ); // Spigot this.b.handle(new PacketLoginOutDisconnect(chatcomponenttext), new GenericFutureListener[0]); this.b.a((IChatBaseComponent) chatcomponenttext); } else { this.b.a((PacketListener) (new LoginListener(this.a, this.b))); + // Spigot Start + if (org.spigotmc.SpigotConfig.bungee) { + String[] split = packethandshakinginsetprotocol.b.split("\00"); + if (split.length == 2 || split.length == 3) { + packethandshakinginsetprotocol.b = split[0]; + b.l = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) b.getSocketAddress()).getPort()); + } + if ( split.length == 3 ) + { + b.spoofedUUID = split[2]; + } + } + // Spigot End ((LoginListener) this.b.getPacketListener()).hostname = packethandshakinginsetprotocol.b + ":" + packethandshakinginsetprotocol.c; // CraftBukkit - set hostname } break; diff --git a/src/main/java/net/minecraft/server/IRecipe.java b/src/main/java/net/minecraft/server/IRecipe.java index bb28c12..c0836e4 100644 --- a/src/main/java/net/minecraft/server/IRecipe.java +++ b/src/main/java/net/minecraft/server/IRecipe.java @@ -11,4 +11,6 @@ public interface IRecipe { ItemStack b(); org.bukkit.inventory.Recipe toBukkitRecipe(); // CraftBukkit + + java.util.List getIngredients(); // Spigot } diff --git a/src/main/java/net/minecraft/server/ItemMonsterEgg.java b/src/main/java/net/minecraft/server/ItemMonsterEgg.java index a41184e..34c21ef 100644 --- a/src/main/java/net/minecraft/server/ItemMonsterEgg.java +++ b/src/main/java/net/minecraft/server/ItemMonsterEgg.java @@ -92,12 +92,6 @@ public class ItemMonsterEgg extends Item { } public static Entity a(World world, int i, double d0, double d1, double d2) { - // CraftBukkit start - delegate to spawnCreature - return spawnCreature(world, i, d0, d1, d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); - } - - public static Entity spawnCreature(World world, int i, double d0, double d1, double d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { - // CraftBukkit end if (!EntityTypes.a.containsKey(Integer.valueOf(i))) { return null; } else { @@ -112,7 +106,7 @@ public class ItemMonsterEgg extends Item { entityinsentient.aP = entityinsentient.yaw; entityinsentient.aN = entityinsentient.yaw; entityinsentient.a((GroupDataEntity) null); - world.addEntity(entity, spawnReason); // CraftBukkit + world.addEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit entityinsentient.r(); } } diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java index b308e03..37ceade 100644 --- a/src/main/java/net/minecraft/server/ItemStack.java +++ b/src/main/java/net/minecraft/server/ItemStack.java @@ -130,7 +130,13 @@ public final class ItemStack { } public boolean g() { - return this.item.getMaxDurability() <= 0 ? false : !this.hasTag() || !this.getTag().getBoolean("Unbreakable"); + // Spigot Start + if ( this.item.getMaxDurability() <= 0 ) + { + return false; + } + return ( !hasTag() ) || ( !getTag().getBoolean( "Unbreakable" ) ); + // Spigot End } public boolean usesData() { @@ -176,7 +182,13 @@ public final class ItemStack { return this.item.getMaxDurability(); } + // Spigot start public boolean isDamaged(int i, Random random) { + return isDamaged(i, random, null); + } + + public boolean isDamaged(int i, Random random, EntityLiving entityliving) { + // Spigot end if (!this.g()) { return false; } else { @@ -191,7 +203,16 @@ public final class ItemStack { } i -= k; - if (i <= 0) { + // Spigot start + if (entityliving instanceof EntityPlayer) { + org.bukkit.craftbukkit.inventory.CraftItemStack item = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); + org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent((org.bukkit.entity.Player) entityliving.getBukkitEntity(), item, i); + org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) return false; + i = event.getDamage(); + } + // Spigot end + if (i <= 0 ) { return false; } } @@ -204,7 +225,7 @@ public final class ItemStack { public void damage(int i, EntityLiving entityliving) { if (!(entityliving instanceof EntityHuman) || !((EntityHuman) entityliving).abilities.canInstantlyBuild) { if (this.g()) { - if (this.isDamaged(i, entityliving.aI())) { + if (this.isDamaged(i, entityliving.aI(), entityliving)) { entityliving.a(this); --this.count; if (entityliving instanceof EntityHuman) { diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java index 6beac82..fa80b74 100644 --- a/src/main/java/net/minecraft/server/LoginListener.java +++ b/src/main/java/net/minecraft/server/LoginListener.java @@ -61,9 +61,18 @@ public class LoginListener implements PacketLoginInListener { public void c() { if (!this.i.isComplete()) { - UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.i.getName()).getBytes(Charsets.UTF_8)); - - this.i = new GameProfile(uuid.toString().replaceAll("-", ""), this.i.getName()); + // Spigot Start + String uuid; + if ( networkManager.spoofedUUID != null ) + { + uuid = networkManager.spoofedUUID; + } else + { + uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.i.getName()).getBytes(Charsets.UTF_8)).toString().replaceAll("-", ""); + } + + this.i = new GameProfile(uuid, this.i.getName()); + // Spigot End } // CraftBukkit start @@ -99,7 +108,7 @@ public class LoginListener implements PacketLoginInListener { this.g = EnumProtocolState.KEY; this.networkManager.handle(new PacketLoginOutEncryptionBegin(this.j, this.server.I().getPublic(), this.e), new GenericFutureListener[0]); } else { - this.g = EnumProtocolState.READY_TO_ACCEPT; + (new ThreadPlayerLookupUUID(this, "User Authenticator #" + b.incrementAndGet())).start(); // Spigot } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 6a01982..022e032 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.UUID; @@ -36,6 +35,7 @@ import jline.console.ConsoleReader; import joptsimple.OptionSet; import org.bukkit.World.Environment; +import org.bukkit.craftbukkit.SpigotTimings; // Spigot import org.bukkit.craftbukkit.util.Waitable; import org.bukkit.event.server.RemoteServerCommandEvent; import org.bukkit.event.world.WorldSaveEvent; @@ -51,7 +51,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo private final List m = new ArrayList(); private final ICommandHandler n; public final MethodProfiler methodProfiler = new MethodProfiler(); - private final ServerConnection o; + private ServerConnection o; // Spigot private final ServerPing p = new ServerPing(); private final Random q = new Random(); private String serverIp; @@ -101,12 +101,17 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; // CraftBukkit end + // Spigot start + private static final int TPS = 20; + private static final int TICK_TIME = 1000000000 / TPS; + public final double[] recentTps = new double[ 3 ]; + // Spigot end public MinecraftServer(OptionSet options, Proxy proxy) { // CraftBukkit - signature file -> OptionSet i = this; this.c = proxy; // this.universe = file1; // CraftBukkit - this.o = new ServerConnection(this); + // this.o = new ServerConnection(this); // Spigot this.n = new CommandDispatcher(); // this.convertable = new WorldLoaderServer(file1); // CraftBukkit - moved to DedicatedServer.init this.S = (new YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString())).createMinecraftSessionService(); @@ -419,6 +424,13 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.isRunning = false; } + // Spigot Start + private static double calcTps(double avg, double exp, double tps) + { + return ( avg * exp ) + ( tps * ( 1 - exp ) ); + } + // Spigot End + public void run() { try { if (this.init()) { @@ -429,43 +441,44 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.p.setServerInfo(new ServerPingServerData("1.7.2", 4)); this.a(this.p); + // Spigot start + Arrays.fill( recentTps, 20 ); + long lastTick = 0, catchupTime = 0, curTime, wait; while (this.isRunning) { - long k = ap(); - long l = k - i; - - if (l > 2000L && i - this.O >= 15000L) { - if (this.server.getWarnOnOverload()) // CraftBukkit - Added option to suppress warning messages - h.warn("Can\'t keep up! Did the system time change, or is the server overloaded? Running {}ms behind, skipping {} tick(s)", new Object[] { Long.valueOf(l), Long.valueOf(l / 50L)}); - l = 2000L; - this.O = i; - } - - if (l < 0L) { - h.warn("Time ran backwards! Did the system time change?"); - l = 0L; + curTime = System.nanoTime(); + wait = TICK_TIME - (curTime - lastTick) - catchupTime; + if (wait > 0) { + Thread.sleep(wait / 1000000); + catchupTime = 0; + continue; + } else { + catchupTime = Math.min(1000000000, Math.abs(wait)); } - j += l; - i = k; - if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit - this.t(); - j = 0L; - } else { - while (j > 50L) { - MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit - j -= 50L; - this.t(); - } + if ( MinecraftServer.currentTick++ % 100 == 0 ) + { + double currentTps = 1E9 / ( curTime - lastTick ); + recentTps[0] = calcTps( recentTps[0], 0.92, currentTps ); // 1/exp(5sec/1min) + recentTps[1] = calcTps( recentTps[1], 0.9835, currentTps ); // 1/exp(5sec/5min) + recentTps[2] = calcTps( recentTps[2], 0.9945, currentTps ); // 1/exp(5sec/15min) } + lastTick = curTime; - Thread.sleep(1L); + this.t(); this.N = true; } + // Spigot end } else { this.a((CrashReport) null); } } catch (Throwable throwable) { h.error("Encountered an unexpected exception", throwable); + // Spigot Start + if ( throwable.getCause() != null ) + { + h.error( "\tCause of unexpected exception was", throwable.getCause() ); + } + // Spigot End CrashReport crashreport = null; if (throwable instanceof ReportedException) { @@ -485,6 +498,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.a(crashreport); } finally { try { + org.spigotmc.WatchdogThread.doStop(); this.stop(); this.isStopped = true; } catch (Throwable throwable1) { @@ -531,6 +545,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo protected void s() {} protected void t() throws ExceptionWorldConflict { // CraftBukkit - added throws + SpigotTimings.serverTickTimer.startTiming(); // Spigot long i = System.nanoTime(); AxisAlignedBB.a().a(); @@ -560,7 +575,16 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo if ((this.autosavePeriod > 0) && ((this.ticks % this.autosavePeriod) == 0)) { // CraftBukkit this.methodProfiler.a("save"); this.t.savePlayers(); - this.saveChunks(true); + // Spigot Start + // We replace this with saving each individual world as this.saveChunks(...) is broken, + // and causes the main thread to sleep for random amounts of time depending on chunk activity + server.playerCommandState = true; + for (World world : worlds) { + world.getWorld().save(); + } + server.playerCommandState = false; + // this.saveChunks(true); + // Spigot End this.methodProfiler.b(); } @@ -568,11 +592,11 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.f[this.ticks % 100] = System.nanoTime() - i; this.methodProfiler.b(); this.methodProfiler.a("snooper"); - if (!this.k.d() && this.ticks > 100) { + if (getSnooperEnabled() && !this.k.d() && this.ticks > 100) { // Spigot this.k.a(); } - if (this.ticks % 6000 == 0) { + if (getSnooperEnabled() && this.ticks % 6000 == 0) { // Spigot this.k.b(); } @@ -583,6 +607,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo public void u() { this.methodProfiler.a("levels"); + SpigotTimings.schedulerTimer.startTiming(); // Spigot // CraftBukkit start this.server.getScheduler().mainThreadHeartbeat(this.ticks); @@ -591,7 +616,10 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo processQueue.remove().run(); } + SpigotTimings.schedulerTimer.stopTiming(); // Spigot + SpigotTimings.chunkIOTickTimer.startTiming(); // Spigot org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.tick(); + SpigotTimings.chunkIOTickTimer.stopTiming(); // Spigot // Send time updates to everyone, it will get the right time from the world the player is in. if (this.ticks % 20 == 0) { @@ -628,7 +656,13 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo try { worldserver.doTick(); } catch (Throwable throwable) { + // Spigot Start + try { crashreport = CrashReport.a(throwable, "Exception ticking world"); + } catch (Throwable t){ + throw new RuntimeException("Error generating crash report", t); + } + // Spigot End worldserver.a(crashreport); throw new ReportedException(crashreport); } @@ -636,14 +670,22 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo try { worldserver.tickEntities(); } catch (Throwable throwable1) { + // Spigot Start + try { crashreport = CrashReport.a(throwable1, "Exception ticking world entities"); + } catch (Throwable t){ + throw new RuntimeException("Error generating crash report", t); + } + // Spigot End worldserver.a(crashreport); throw new ReportedException(crashreport); } this.methodProfiler.b(); this.methodProfiler.a("tracker"); + worldserver.timings.tracker.startTiming(); // Spigot worldserver.getTracker().updatePlayers(); + worldserver.timings.tracker.stopTiming(); // Spigot this.methodProfiler.b(); this.methodProfiler.b(); // } // CraftBukkit @@ -652,16 +694,25 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo } this.methodProfiler.c("connection"); + SpigotTimings.connectionTimer.startTiming(); // Spigot this.ag().c(); + SpigotTimings.connectionTimer.stopTiming(); // Spigot this.methodProfiler.c("players"); + SpigotTimings.playerListTimer.startTiming(); // Spigot this.t.tick(); + SpigotTimings.playerListTimer.stopTiming(); // Spigot this.methodProfiler.c("tickables"); + SpigotTimings.tickablesTimer.startTiming(); // Spigot for (i = 0; i < this.m.size(); ++i) { ((IUpdatePlayerListBox) this.m.get(i)).a(); } + SpigotTimings.tickablesTimer.stopTiming(); // Spigot this.methodProfiler.b(); + org.spigotmc.WatchdogThread.tick(); // Spigot + SpigotTimings.serverTickTimer.stopTiming(); // Spigot + org.spigotmc.CustomTimingsHandler.tick(); // Spigot } public boolean getAllowNether() { @@ -1210,7 +1261,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo } public ServerConnection ag() { - return this.o; + return ( this.o ) == null ? this.o = new ServerConnection( this ) : this.o; // Spigot } public boolean ai() { diff --git a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java index 67c3397..20d9094 100644 --- a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java +++ b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java @@ -5,7 +5,11 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.bukkit.event.entity.CreatureSpawnEvent; // CraftBukkit +// CraftBukkit start +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.SpawnerSpawnEvent; +// CraftBukkit end public abstract class MobSpawnerAbstract { @@ -128,7 +132,18 @@ public abstract class MobSpawnerAbstract { entity.f(nbttagcompound); if (entity.world != null) { - entity.world.addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit + // CraftBukkit start - call SpawnerSpawnEvent, abort if cancelled + SpawnerSpawnEvent event = CraftEventFactory.callSpawnerSpawnEvent(entity, this.b(), this.c(), this.d()); + if (!event.isCancelled()) { + entity.world.addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit + // Spigot Start + if ( entity.world.spigotConfig.nerfSpawnerMobs ) + { + entity.fromMobSpawner = true; + } + // Spigot End + } + // CraftBukkit end } NBTTagCompound nbttagcompound1; @@ -152,6 +167,11 @@ public abstract class MobSpawnerAbstract { entity2.f(nbttagcompound2); entity2.setPositionRotation(entity1.locX, entity1.locY, entity1.locZ, entity1.yaw, entity1.pitch); + // CraftBukkit start - call SpawnerSpawnEvent, skip if cancelled + SpawnerSpawnEvent event = CraftEventFactory.callSpawnerSpawnEvent(entity2, this.b(), this.c(), this.d()); + if (event.isCancelled()) { + continue; + } if (entity.world != null) { entity.world.addEntity(entity2, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit } @@ -163,7 +183,18 @@ public abstract class MobSpawnerAbstract { } } else if (entity instanceof EntityLiving && entity.world != null) { ((EntityInsentient) entity).a((GroupDataEntity) null); - this.a().addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit + // CraftBukkit start - call SpawnerSpawnEvent, abort if cancelled + SpawnerSpawnEvent event = CraftEventFactory.callSpawnerSpawnEvent(entity, this.b(), this.c(), this.d()); + if (!event.isCancelled()) { + this.a().addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit + // Spigot Start + if ( entity.world.spigotConfig.nerfSpawnerMobs ) + { + entity.fromMobSpawner = true; + } + // Spigot End + } + // CraftBukkit end } return entity; diff --git a/src/main/java/net/minecraft/server/NBTBase.java b/src/main/java/net/minecraft/server/NBTBase.java new file mode 100644 index 0000000..6e7c3a2 --- /dev/null +++ b/src/main/java/net/minecraft/server/NBTBase.java @@ -0,0 +1,129 @@ +package net.minecraft.server; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public abstract class NBTBase { + + public static final String[] a = new String[] { "END", "BYTE", "SHORT", "INT", "LONG", "FLOAT", "DOUBLE", "BYTE[]", "STRING", "LIST", "COMPOUND", "INT[]"}; + + abstract void write(DataOutput dataoutput) throws IOException; + + abstract void load(DataInput datainput, int i) throws IOException; + + public abstract String toString(); + + public abstract byte getTypeId(); + + protected NBTBase() {} + + protected static NBTBase createTag(byte b0) { + switch (b0) { + case 0: + return new NBTTagEnd(); + + case 1: + return new NBTTagByte(); + + case 2: + return new NBTTagShort(); + + case 3: + return new NBTTagInt(); + + case 4: + return new NBTTagLong(); + + case 5: + return new NBTTagFloat(); + + case 6: + return new NBTTagDouble(); + + case 7: + return new NBTTagByteArray(); + + case 8: + return new NBTTagString(); + + case 9: + return new NBTTagList(); + + case 10: + return new NBTTagCompound(); + + case 11: + return new NBTTagIntArray(); + + default: + return null; + } + } + + public static String getTagName(int i) { + switch (i) { + case 0: + return "TAG_End"; + + case 1: + return "TAG_Byte"; + + case 2: + return "TAG_Short"; + + case 3: + return "TAG_Int"; + + case 4: + return "TAG_Long"; + + case 5: + return "TAG_Float"; + + case 6: + return "TAG_Double"; + + case 7: + return "TAG_Byte_Array"; + + case 8: + return "TAG_String"; + + case 9: + return "TAG_List"; + + case 10: + return "TAG_Compound"; + + case 11: + return "TAG_Int_Array"; + + case 99: + return "Any Numeric Tag"; + + default: + return "UNKNOWN"; + } + } + + public abstract NBTBase clone(); + + public boolean equals(Object object) { + if (!(object instanceof NBTBase)) { + return false; + } else { + NBTBase nbtbase = (NBTBase) object; + + return this.getTypeId() == nbtbase.getTypeId(); + } + } + + public int hashCode() { + return this.getTypeId(); + } + + protected String a_() { + return this.toString(); + } +} diff --git a/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java b/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java new file mode 100644 index 0000000..c9a8ed5 --- /dev/null +++ b/src/main/java/net/minecraft/server/NBTCompressedStreamTools.java @@ -0,0 +1,113 @@ +package net.minecraft.server; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public class NBTCompressedStreamTools { + + public static NBTTagCompound a(InputStream inputstream) { + DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(inputstream))); + + NBTTagCompound nbttagcompound; + + try { + nbttagcompound = a((DataInput) datainputstream); + } finally { + datainputstream.close(); + } + + return nbttagcompound; + } + + public static void a(NBTTagCompound nbttagcompound, OutputStream outputstream) { + DataOutputStream dataoutputstream = new DataOutputStream(new GZIPOutputStream(outputstream)); + + try { + a(nbttagcompound, (DataOutput) dataoutputstream); + } finally { + dataoutputstream.close(); + } + } + + public static NBTTagCompound a(byte[] abyte) { + DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(abyte)))); + + NBTTagCompound nbttagcompound; + + try { + nbttagcompound = a((DataInput) datainputstream); + } finally { + datainputstream.close(); + } + + return nbttagcompound; + } + + public static byte[] a(NBTTagCompound nbttagcompound) { + ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(); + DataOutputStream dataoutputstream = new DataOutputStream(new GZIPOutputStream(bytearrayoutputstream)); + + try { + a(nbttagcompound, (DataOutput) dataoutputstream); + } finally { + dataoutputstream.close(); + } + + return bytearrayoutputstream.toByteArray(); + } + + public static NBTTagCompound a(DataInput datainput) { + NBTBase nbtbase = a(datainput, 0); + + if (nbtbase instanceof NBTTagCompound) { + return (NBTTagCompound) nbtbase; + } else { + throw new IOException("Root tag must be a named compound tag"); + } + } + + public static void a(NBTTagCompound nbttagcompound, DataOutput dataoutput) { + a((NBTBase) nbttagcompound, dataoutput); + } + + private static void a(NBTBase nbtbase, DataOutput dataoutput) { + dataoutput.writeByte(nbtbase.getTypeId()); + if (nbtbase.getTypeId() != 0) { + dataoutput.writeUTF(""); + nbtbase.write(dataoutput); + } + } + + private static NBTBase a(DataInput datainput, int i) { + byte b0 = datainput.readByte(); + + if (b0 == 0) { + return new NBTTagEnd(); + } else { + datainput.readUTF(); + NBTBase nbtbase = NBTBase.createTag(b0); + + try { + nbtbase.load(datainput, i); + return nbtbase; + } catch (IOException ioexception) { + CrashReport crashreport = CrashReport.a(ioexception, "Loading NBT data"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("NBT Tag"); + + crashreportsystemdetails.a("Tag name", "[UNNAMED TAG]"); + crashreportsystemdetails.a("Tag type", Byte.valueOf(b0)); + throw new ReportedException(crashreport); + } + } + } +} diff --git a/src/main/java/net/minecraft/server/NBTTagByteArray.java b/src/main/java/net/minecraft/server/NBTTagByteArray.java new file mode 100644 index 0000000..c2e81cb --- /dev/null +++ b/src/main/java/net/minecraft/server/NBTTagByteArray.java @@ -0,0 +1,57 @@ +package net.minecraft.server; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +public class NBTTagByteArray extends NBTBase { + + private byte[] data; + + NBTTagByteArray() {} + + public NBTTagByteArray(byte[] abyte) { + this.data = abyte; + } + + void write(DataOutput dataoutput) throws IOException { + dataoutput.writeInt(this.data.length); + dataoutput.write(this.data); + } + + void load(DataInput datainput, int i) throws IOException { + int j = datainput.readInt(); + com.google.common.base.Preconditions.checkArgument( j < 1 << 24); + + this.data = new byte[j]; + datainput.readFully(this.data); + } + + public byte getTypeId() { + return (byte) 7; + } + + public String toString() { + return "[" + this.data.length + " bytes]"; + } + + public NBTBase clone() { + byte[] abyte = new byte[this.data.length]; + + System.arraycopy(this.data, 0, abyte, 0, this.data.length); + return new NBTTagByteArray(abyte); + } + + public boolean equals(Object object) { + return super.equals(object) ? Arrays.equals(this.data, ((NBTTagByteArray) object).data) : false; + } + + public int hashCode() { + return super.hashCode() ^ Arrays.hashCode(this.data); + } + + public byte[] c() { + return this.data; + } +} diff --git a/src/main/java/net/minecraft/server/NBTTagIntArray.java b/src/main/java/net/minecraft/server/NBTTagIntArray.java new file mode 100644 index 0000000..4d6a9da --- /dev/null +++ b/src/main/java/net/minecraft/server/NBTTagIntArray.java @@ -0,0 +1,73 @@ +package net.minecraft.server; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +public class NBTTagIntArray extends NBTBase { + + private int[] data; + + NBTTagIntArray() {} + + public NBTTagIntArray(int[] aint) { + this.data = aint; + } + + void write(DataOutput dataoutput) throws IOException { + dataoutput.writeInt(this.data.length); + + for (int i = 0; i < this.data.length; ++i) { + dataoutput.writeInt(this.data[i]); + } + } + + void load(DataInput datainput, int i) throws IOException { + int j = datainput.readInt(); + com.google.common.base.Preconditions.checkArgument( j < 1 << 24); + + this.data = new int[j]; + + for (int k = 0; k < j; ++k) { + this.data[k] = datainput.readInt(); + } + } + + public byte getTypeId() { + return (byte) 11; + } + + public String toString() { + String s = "["; + int[] aint = this.data; + int i = aint.length; + + for (int j = 0; j < i; ++j) { + int k = aint[j]; + + s = s + k + ","; + } + + return s + "]"; + } + + public NBTBase clone() { + int[] aint = new int[this.data.length]; + + System.arraycopy(this.data, 0, aint, 0, this.data.length); + return new NBTTagIntArray(aint); + } + + public boolean equals(Object object) { + return super.equals(object) ? Arrays.equals(this.data, ((NBTTagIntArray) object).data) : false; + } + + public int hashCode() { + return super.hashCode() ^ Arrays.hashCode(this.data); + } + + public int[] c() { + return this.data; + } +} diff --git a/src/main/java/net/minecraft/server/NBTTagList.java b/src/main/java/net/minecraft/server/NBTTagList.java new file mode 100644 index 0000000..ad8c3ab --- /dev/null +++ b/src/main/java/net/minecraft/server/NBTTagList.java @@ -0,0 +1,166 @@ +package net.minecraft.server; + +import java.io.DataInput; +import java.io.DataOutput; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class NBTTagList extends NBTBase { + + private List list = new ArrayList(); + private byte type = 0; + + public NBTTagList() {} + + void write(DataOutput dataoutput) { + if (!this.list.isEmpty()) { + this.type = ((NBTBase) this.list.get(0)).getTypeId(); + } else { + this.type = 0; + } + + dataoutput.writeByte(this.type); + dataoutput.writeInt(this.list.size()); + + for (int i = 0; i < this.list.size(); ++i) { + ((NBTBase) this.list.get(i)).write(dataoutput); + } + } + + void load(DataInput datainput, int i) { + if (i > 512) { + throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); + } else { + this.type = datainput.readByte(); + int j = datainput.readInt(); + + this.list = new ArrayList(); + + for (int k = 0; k < j; ++k) { + NBTBase nbtbase = NBTBase.createTag(this.type); + + nbtbase.load(datainput, i + 1); + this.list.add(nbtbase); + } + } + } + + public byte getTypeId() { + return (byte) 9; + } + + public String toString() { + String s = "["; + int i = 0; + + for (Iterator iterator = this.list.iterator(); iterator.hasNext(); ++i) { + NBTBase nbtbase = (NBTBase) iterator.next(); + + s = s + "" + i + ':' + nbtbase + ','; + } + + return s + "]"; + } + + public void add(NBTBase nbtbase) { + if (this.type == 0) { + this.type = nbtbase.getTypeId(); + } else if (this.type != nbtbase.getTypeId()) { + System.err.println("WARNING: Adding mismatching tag types to tag list"); + return; + } + + this.list.add(nbtbase); + } + + public NBTTagCompound get(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); + + return nbtbase.getTypeId() == 10 ? (NBTTagCompound) nbtbase : new NBTTagCompound(); + } else { + return new NBTTagCompound(); + } + } + + public int[] c(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); + + return nbtbase.getTypeId() == 11 ? ((NBTTagIntArray) nbtbase).c() : new int[0]; + } else { + return new int[0]; + } + } + + public double d(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); + + return nbtbase.getTypeId() == 6 ? ((NBTTagDouble) nbtbase).g() : 0.0D; + } else { + return 0.0D; + } + } + + public float e(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); + + return nbtbase.getTypeId() == 5 ? ((NBTTagFloat) nbtbase).h() : 0.0F; + } else { + return 0.0F; + } + } + + public String f(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); + + return nbtbase.getTypeId() == 8 ? nbtbase.a_() : nbtbase.toString(); + } else { + return ""; + } + } + + public int size() { + return this.list.size(); + } + + public NBTBase clone() { + NBTTagList nbttaglist = new NBTTagList(); + + nbttaglist.type = this.type; + Iterator iterator = this.list.iterator(); + + while (iterator.hasNext()) { + NBTBase nbtbase = (NBTBase) iterator.next(); + NBTBase nbtbase1 = nbtbase.clone(); + + nbttaglist.list.add(nbtbase1); + } + + return nbttaglist; + } + + public boolean equals(Object object) { + if (super.equals(object)) { + NBTTagList nbttaglist = (NBTTagList) object; + + if (this.type == nbttaglist.type) { + return this.list.equals(nbttaglist.list); + } + } + + return false; + } + + public int hashCode() { + return super.hashCode() ^ this.list.hashCode(); + } + + public int d() { + return this.type; + } +} diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java index 8f829bb..6b2fbb9 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -34,7 +34,8 @@ public class NetworkManager extends SimpleChannelInboundHandler { private final Queue i = Queues.newConcurrentLinkedQueue(); private final Queue j = Queues.newConcurrentLinkedQueue(); private Channel k; - private SocketAddress l; + public SocketAddress l; // Spigot + public String spoofedUUID; // Spigot private PacketListener m; private EnumProtocol n; private IChatBaseComponent o; @@ -195,4 +196,11 @@ public class NetworkManager extends SimpleChannelInboundHandler { static Channel a(NetworkManager networkmanager) { return networkmanager.k; } + + // Spigot Start + public SocketAddress getRawAddress() + { + return this.k.remoteAddress(); + } + // Spigot End } diff --git a/src/main/java/net/minecraft/server/NextTickListEntry.java b/src/main/java/net/minecraft/server/NextTickListEntry.java new file mode 100644 index 0000000..06934a1 --- /dev/null +++ b/src/main/java/net/minecraft/server/NextTickListEntry.java @@ -0,0 +1,60 @@ +package net.minecraft.server; + +public class NextTickListEntry implements Comparable { + + private static long f; + private final Block g; + public int a; + public int b; + public int c; + public long d; + public int e; + private long h; + + public NextTickListEntry(int i, int j, int k, Block block) { + this.h = (long) (f++); + this.a = i; + this.b = j; + this.c = k; + this.g = block; + } + + public boolean equals(Object object) { + if (!(object instanceof NextTickListEntry)) { + return false; + } else { + NextTickListEntry nextticklistentry = (NextTickListEntry) object; + + return this.a == nextticklistentry.a && this.b == nextticklistentry.b && this.c == nextticklistentry.c && Block.a(this.g, nextticklistentry.g); + } + } + + public int hashCode() { + return (this.a * 1024 * 1024 + this.c * 1024 + this.b) * 256; + } + + public NextTickListEntry a(long i) { + this.d = i; + return this; + } + + public void a(int i) { + this.e = i; + } + + public int compareTo(NextTickListEntry nextticklistentry) { + return this.d < nextticklistentry.d ? -1 : (this.d > nextticklistentry.d ? 1 : (this.e != nextticklistentry.e ? this.e - nextticklistentry.e : (this.h < nextticklistentry.h ? -1 : (this.h > nextticklistentry.h ? 1 : 0)))); + } + + public String toString() { + return Block.b(this.g) + ": (" + this.a + ", " + this.b + ", " + this.c + "), " + this.d + ", " + this.e + ", " + this.h; + } + + public Block a() { + return this.g; + } + + public int compareTo(Object object) { + return this.compareTo((NextTickListEntry) object); + } +} diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java new file mode 100644 index 0000000..c9bc20c --- /dev/null +++ b/src/main/java/net/minecraft/server/NibbleArray.java @@ -0,0 +1,158 @@ +package net.minecraft.server; + +import java.util.Arrays; // Spigot + +public class NibbleArray { + + private byte[] a; // Spigot - remove final, make private (anyone directly accessing this is broken already) + private final int b; + private final int c; + // Spigot start + private byte trivialValue; + private byte trivialByte; + private int length; + private static final int LEN2K = 2048; // Universal length used right now - optimize around this + private static final byte[][] TrivLen2k; + + static { + TrivLen2k = new byte[16][]; + for (int i = 0; i < 16; i++) { + TrivLen2k[i] = new byte[LEN2K]; + Arrays.fill(TrivLen2k[i], (byte) (i | (i << 4))); + } + } + + // Try to convert array to trivial array + public void detectAndProcessTrivialArray() { + trivialValue = (byte) (a[0] & 0xF); + trivialByte = (byte) (trivialValue | (trivialValue << 4)); + for (int i = 0; i < a.length; i++) { + if (a[i] != trivialByte) return; + } + // All values matches, so array is trivial + this.length = a.length; + this.a = null; + } + + // Force array to non-trivial state + public void forceToNonTrivialArray() { + if (this.a == null) { + this.a = new byte[this.length]; + if (this.trivialByte != 0) { + Arrays.fill(this.a, this.trivialByte); + } + } + } + + // Test if array is in trivial state + public boolean isTrivialArray() { + return (this.a == null); + } + + // Get value of all elements (only valid if array is in trivial state) + public int getTrivialArrayValue() { + return this.trivialValue; + } + + // Get logical length of byte array for nibble data (whether trivial or non-trivial) + public int getByteLength() { + if (this.a == null) { + return this.length; + } else { + return this.a.length; + } + } + + // Return byte encoding of array (whether trivial or non-trivial) - returns read-only array if trivial (do not modify!) + public byte[] getValueArray() { + if (this.a != null) { + return this.a; + } else { + byte[] rslt; + + if (this.length == LEN2K) { // All current uses are 2k long, but be safe + rslt = TrivLen2k[this.trivialValue]; + } else { + rslt = new byte[this.length]; + if (this.trivialByte != 0) { + Arrays.fill(rslt, this.trivialByte); + } + } + return rslt; + } + } + + // Copy byte representation of array to given offset in given byte array + public int copyToByteArray(byte[] dest, int off) { + if (this.a == null) { + Arrays.fill(dest, off, off + this.length, this.trivialByte); + return off + this.length; + } else { + System.arraycopy(this.a, 0, dest, off, this.a.length); + return off + this.a.length; + } + } + + // Resize array to given byte length + public void resizeArray(int len) { + if (this.a == null) { + this.length = len; + } else if (this.a.length != len) { + byte[] newa = new byte[len]; + System.arraycopy(this.a, 0, newa, 0, ((this.a.length > len) ? len : this.a.length)); + this.a = newa; + } + } + // Spigot end + + public NibbleArray(int i, int j) { + // Spigot start + //this.a = new byte[i >> 1]; + this.a = null; // Start off as trivial value (all same zero value) + this.length = i >> 1; + this.trivialByte = this.trivialValue = 0; + // Spigot end + this.b = j; + this.c = j + 4; + } + + public NibbleArray(byte[] abyte, int i) { + this.a = abyte; + this.b = i; + this.c = i + 4; + detectAndProcessTrivialArray(); // Spigot + } + + public int a(int i, int j, int k) { + if (this.a == null) return this.trivialValue; // Spigot + int l = j << this.c | k << this.b | i; + int i1 = l >> 1; + int j1 = l & 1; + + return j1 == 0 ? this.a[i1] & 15 : this.a[i1] >> 4 & 15; + } + + public void a(int i, int j, int k, int l) { + // Spigot start + if (this.a == null) { + if (l != this.trivialValue) { // Not same as trivial value, array no longer trivial + this.a = new byte[this.length]; + if (this.trivialByte != 0) { + Arrays.fill(this.a, this.trivialByte); + } + } else { + return; + } + } + // Spigot end + int i1 = j << this.c | k << this.b | i; + int j1 = i1 >> 1; + int k1 = i1 & 1; + + if (k1 == 0) { + this.a[j1] = (byte) (this.a[j1] & 240 | l & 15); + } else { + this.a[j1] = (byte) (this.a[j1] & 15 | (l & 15) << 4); + } + } +} diff --git a/src/main/java/net/minecraft/server/OldChunkLoader.java b/src/main/java/net/minecraft/server/OldChunkLoader.java new file mode 100644 index 0000000..6ee28cc --- /dev/null +++ b/src/main/java/net/minecraft/server/OldChunkLoader.java @@ -0,0 +1,122 @@ +package net.minecraft.server; + +public class OldChunkLoader { + + public static OldChunk a(NBTTagCompound nbttagcompound) { + int i = nbttagcompound.getInt("xPos"); + int j = nbttagcompound.getInt("zPos"); + OldChunk oldchunk = new OldChunk(i, j); + + oldchunk.g = nbttagcompound.getByteArray("Blocks"); + oldchunk.f = new OldNibbleArray(nbttagcompound.getByteArray("Data"), 7); + oldchunk.e = new OldNibbleArray(nbttagcompound.getByteArray("SkyLight"), 7); + oldchunk.d = new OldNibbleArray(nbttagcompound.getByteArray("BlockLight"), 7); + oldchunk.c = nbttagcompound.getByteArray("HeightMap"); + oldchunk.b = nbttagcompound.getBoolean("TerrainPopulated"); + oldchunk.h = nbttagcompound.getList("Entities", 10); + oldchunk.i = nbttagcompound.getList("TileEntities", 10); + oldchunk.j = nbttagcompound.getList("TileTicks", 10); + + try { + oldchunk.a = nbttagcompound.getLong("LastUpdate"); + } catch (ClassCastException classcastexception) { + oldchunk.a = (long) nbttagcompound.getInt("LastUpdate"); + } + + return oldchunk; + } + + public static void a(OldChunk oldchunk, NBTTagCompound nbttagcompound, WorldChunkManager worldchunkmanager) { + nbttagcompound.setInt("xPos", oldchunk.k); + nbttagcompound.setInt("zPos", oldchunk.l); + nbttagcompound.setLong("LastUpdate", oldchunk.a); + int[] aint = new int[oldchunk.c.length]; + + for (int i = 0; i < oldchunk.c.length; ++i) { + aint[i] = oldchunk.c[i]; + } + + nbttagcompound.setIntArray("HeightMap", aint); + nbttagcompound.setBoolean("TerrainPopulated", oldchunk.b); + NBTTagList nbttaglist = new NBTTagList(); + + int j; + + for (int k = 0; k < 8; ++k) { + boolean flag = true; + + for (j = 0; j < 16 && flag; ++j) { + int l = 0; + + while (l < 16 && flag) { + int i1 = 0; + + while (true) { + if (i1 < 16) { + int j1 = j << 11 | i1 << 7 | l + (k << 4); + byte b0 = oldchunk.g[j1]; + + if (b0 == 0) { + ++i1; + continue; + } + + flag = false; + } + + ++l; + break; + } + } + } + + if (!flag) { + byte[] abyte = new byte[4096]; + NibbleArray nibblearray = new NibbleArray(abyte.length, 4); + NibbleArray nibblearray1 = new NibbleArray(abyte.length, 4); + NibbleArray nibblearray2 = new NibbleArray(abyte.length, 4); + + for (int k1 = 0; k1 < 16; ++k1) { + for (int l1 = 0; l1 < 16; ++l1) { + for (int i2 = 0; i2 < 16; ++i2) { + int j2 = k1 << 11 | i2 << 7 | l1 + (k << 4); + byte b1 = oldchunk.g[j2]; + + abyte[l1 << 8 | i2 << 4 | k1] = (byte) (b1 & 255); + nibblearray.a(k1, l1, i2, oldchunk.f.a(k1, l1 + (k << 4), i2)); + nibblearray1.a(k1, l1, i2, oldchunk.e.a(k1, l1 + (k << 4), i2)); + nibblearray2.a(k1, l1, i2, oldchunk.d.a(k1, l1 + (k << 4), i2)); + } + } + } + + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); + + nbttagcompound1.setByte("Y", (byte) (k & 255)); + nbttagcompound1.setByteArray("Blocks", abyte); + // Spigot start - a -> getValueArray() accessor + nbttagcompound1.setByteArray("Data", nibblearray.getValueArray()); + nbttagcompound1.setByteArray("SkyLight", nibblearray1.getValueArray()); + nbttagcompound1.setByteArray("BlockLight", nibblearray2.getValueArray()); + // Spigot end + nbttaglist.add(nbttagcompound1); + } + } + + nbttagcompound.set("Sections", nbttaglist); + byte[] abyte1 = new byte[256]; + + for (int k2 = 0; k2 < 16; ++k2) { + for (j = 0; j < 16; ++j) { + abyte1[j << 4 | k2] = (byte) (worldchunkmanager.getBiome(oldchunk.k << 4 | k2, oldchunk.l << 4 | j).id & 255); + } + } + + nbttagcompound.setByteArray("Biomes", abyte1); + nbttagcompound.set("Entities", oldchunk.h); + nbttagcompound.set("TileEntities", oldchunk.i); + if (oldchunk.j != null) { + nbttagcompound.set("TileTicks", oldchunk.j); + } + } +} diff --git a/src/main/java/net/minecraft/server/PacketPlayInChat.java b/src/main/java/net/minecraft/server/PacketPlayInChat.java index 604a7af..d419f0f 100644 --- a/src/main/java/net/minecraft/server/PacketPlayInChat.java +++ b/src/main/java/net/minecraft/server/PacketPlayInChat.java @@ -43,7 +43,25 @@ public class PacketPlayInChat extends Packet { } // CraftBukkit end - public void handle(PacketListener packetlistener) { + // Spigot Start + private static final java.util.concurrent.ExecutorService executors = java.util.concurrent.Executors.newCachedThreadPool( + new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon( true ).setNameFormat( "Async Chat Thread - #%d" ).build() ); + public void handle(final PacketListener packetlistener) + { + if ( a() ) + { + executors.submit( new Runnable() + { + + @Override + public void run() + { + PacketPlayInChat.this.a( (PacketPlayInListener) packetlistener ); + } + } ); + return; + } + // Spigot End this.a((PacketPlayInListener) packetlistener); } } diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java new file mode 100644 index 0000000..e80d8b9 --- /dev/null +++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java @@ -0,0 +1,191 @@ +package net.minecraft.server; + +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +public class PacketPlayOutMapChunk extends Packet { + + private int a; + private int b; + private int c; + private int d; + private byte[] e; + private byte[] buffer; + private boolean inflatedBuffer; + private int size; + private static byte[] buildBuffer = new byte[196864]; + + public PacketPlayOutMapChunk() {} + + public PacketPlayOutMapChunk(Chunk chunk, boolean flag, int i) { + this.a = chunk.locX; + this.b = chunk.locZ; + this.inflatedBuffer = flag; + ChunkMap chunkmap = a(chunk, flag, i); + Deflater deflater = new Deflater(4); // Spigot + + this.d = chunkmap.c; + this.c = chunkmap.b; + chunk.world.spigotConfig.antiXrayInstance.obfuscate(chunk.locX, chunk.locZ, i, chunkmap.a, chunk.world); // Spigot + + try { + this.buffer = chunkmap.a; + deflater.setInput(chunkmap.a, 0, chunkmap.a.length); + deflater.finish(); + this.e = new byte[chunkmap.a.length]; + this.size = deflater.deflate(this.e); + } finally { + deflater.end(); + } + } + + public static int c() { + return 196864; + } + + public void a(PacketDataSerializer packetdataserializer) throws IOException { + this.a = packetdataserializer.readInt(); + this.b = packetdataserializer.readInt(); + this.inflatedBuffer = packetdataserializer.readBoolean(); + this.c = packetdataserializer.readShort(); + this.d = packetdataserializer.readShort(); + this.size = packetdataserializer.readInt(); + if (buildBuffer.length < this.size) { + buildBuffer = new byte[this.size]; + } + + packetdataserializer.readBytes(buildBuffer, 0, this.size); + int i = 0; + + int j; + + for (j = 0; j < 16; ++j) { + i += this.c >> j & 1; + } + + j = 12288 * i; + if (this.inflatedBuffer) { + j += 256; + } + + this.buffer = new byte[j]; + Inflater inflater = new Inflater(); + + inflater.setInput(buildBuffer, 0, this.size); + + try { + inflater.inflate(this.buffer); + } catch (DataFormatException dataformatexception) { + throw new IOException("Bad compressed data format"); + } finally { + inflater.end(); + } + } + + public void b(PacketDataSerializer packetdataserializer) { + packetdataserializer.writeInt(this.a); + packetdataserializer.writeInt(this.b); + packetdataserializer.writeBoolean(this.inflatedBuffer); + packetdataserializer.writeShort((short) (this.c & '\uffff')); + packetdataserializer.writeShort((short) (this.d & '\uffff')); + packetdataserializer.writeInt(this.size); + packetdataserializer.writeBytes(this.e, 0, this.size); + } + + public void a(PacketPlayOutListener packetplayoutlistener) { + packetplayoutlistener.a(this); + } + + public String b() { + return String.format("x=%d, z=%d, full=%b, sects=%d, add=%d, size=%d", new Object[] { Integer.valueOf(this.a), Integer.valueOf(this.b), Boolean.valueOf(this.inflatedBuffer), Integer.valueOf(this.c), Integer.valueOf(this.d), Integer.valueOf(this.size)}); + } + + public static ChunkMap a(Chunk chunk, boolean flag, int i) { + int j = 0; + ChunkSection[] achunksection = chunk.i(); + int k = 0; + ChunkMap chunkmap = new ChunkMap(); + byte[] abyte = buildBuffer; + + if (flag) { + chunk.q = true; + } + + int l; + + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) { + chunkmap.b |= 1 << l; + if (achunksection[l].getExtendedIdArray() != null) { + chunkmap.c |= 1 << l; + ++k; + } + } + } + + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) { + byte[] abyte1 = achunksection[l].getIdArray(); + + System.arraycopy(abyte1, 0, abyte, j, abyte1.length); + j += abyte1.length; + } + } + + NibbleArray nibblearray; + + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].getDataArray(); + nibblearray.copyToByteArray(abyte, j); + j += nibblearray.getByteLength(); + } + } + + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].getEmittedLightArray(); + nibblearray.copyToByteArray(abyte, j); + j += nibblearray.getByteLength(); + } + } + + if (!chunk.world.worldProvider.g) { + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].getSkyLightArray(); + nibblearray.copyToByteArray(abyte, j); + j += nibblearray.getByteLength(); + } + } + } + + if (k > 0) { + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && achunksection[l].getExtendedIdArray() != null && (i & 1 << l) != 0) { + nibblearray = achunksection[l].getExtendedIdArray(); + nibblearray.copyToByteArray(abyte, j); + j += nibblearray.getByteLength(); + } + } + } + + if (flag) { + byte[] abyte2 = chunk.m(); + + System.arraycopy(abyte2, 0, abyte, j, abyte2.length); + j += abyte2.length; + } + + chunkmap.a = new byte[j]; + System.arraycopy(abyte, 0, chunkmap.a, 0, j); + return chunkmap; + } + + @Override + public void handle(PacketListener packetlistener) { + this.a((PacketPlayOutListener) packetlistener); + } +} diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java index 3eac231..fc92026 100644 --- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java +++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java @@ -22,7 +22,7 @@ public class PacketPlayOutMapChunkBulk extends Packet { @Override protected Deflater initialValue() { // Don't use higher compression level, slows things down too much - return new Deflater(6); + return new Deflater(4); // Spigot 6 -> 4 } }; // CraftBukkit end @@ -43,6 +43,7 @@ public class PacketPlayOutMapChunkBulk extends Packet { for (int k = 0; k < i; ++k) { Chunk chunk = (Chunk) list.get(k); ChunkMap chunkmap = PacketPlayOutMapChunk.a(chunk, true, '\uffff'); + chunk.world.spigotConfig.antiXrayInstance.obfuscate(chunk.locX, chunk.locZ, chunkmap.b, chunkmap.a, chunk.world); // Spigot if (buildBuffer.length < j + chunkmap.a.length) { byte[] abyte = new byte[j + chunkmap.a.length]; diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java index 7903c43..f9da452 100644 --- a/src/main/java/net/minecraft/server/PacketStatusListener.java +++ b/src/main/java/net/minecraft/server/PacketStatusListener.java @@ -110,6 +110,13 @@ public class PacketStatusListener implements PacketStatusInListener { } ServerPingPlayerSample playerSample = new ServerPingPlayerSample(event.getMaxPlayers(), profiles.size()); + // Spigot Start + if ( !profiles.isEmpty() ) + { + java.util.Collections.shuffle( profiles ); // This sucks, its inefficient but we have no simple way of doing it differently + profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour + } + // Spigot End playerSample.a(profiles.toArray(new GameProfile[profiles.size()])); ServerPing ping = new ServerPing(); diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index 4e0398c..ae4ca63 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -24,7 +24,7 @@ public class PlayerChunkMap { public PlayerChunkMap(WorldServer worldserver, int i) { if (i > 15) { throw new IllegalArgumentException("Too big view radius!"); - } else if (i < 3) { + } else if (i < 1) { throw new IllegalArgumentException("Too small view radius!"); } else { this.f = i; diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index 3b378fa..2b68b96 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -105,6 +105,7 @@ public class PlayerConnection implements PacketPlayInListener { private float lastPitch = Float.MAX_VALUE; private float lastYaw = Float.MAX_VALUE; private boolean justTeleported = false; + private boolean hasMoved; // Spigot // For the PacketPlayOutBlockPlace hack :( Long lastPacket; @@ -229,7 +230,7 @@ public class PlayerConnection implements PacketPlayInListener { this.lastPitch = to.getPitch(); // Skip the first time we do this - if (from.getX() != Double.MAX_VALUE) { + if (hasMoved) { // Spigot - Better Check! PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.server.getPluginManager().callEvent(event); @@ -253,7 +254,7 @@ public class PlayerConnection implements PacketPlayInListener { this.justTeleported = false; return; } - } + } else { hasMoved = true; } // Spigot - Better Check! } if (this.checkMovement && !this.player.dead) { @@ -625,7 +626,9 @@ public class PlayerConnection implements PacketPlayInListener { return; } - this.player.playerInteractManager.interact(this.player, worldserver, itemstack, i, j, k, l, packetplayinblockplace.h(), packetplayinblockplace.i(), packetplayinblockplace.j()); + if (!this.player.playerInteractManager.interact(this.player, worldserver, itemstack, i, j, k, l, packetplayinblockplace.h(), packetplayinblockplace.i(), packetplayinblockplace.j())) { + always = true; // force Packet103SetSlot to be sent to client to update ItemStack count + } // CraftBukkit end flag = true; @@ -837,7 +840,27 @@ public class PlayerConnection implements PacketPlayInListener { // this.chatThrottle += 20; if (chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.getName())) { // CraftBukkit end - this.disconnect("disconnect.spam"); + if (packetplayinchat.a()) { // Spigot - Thread safety + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { + PlayerConnection.this.disconnect( "disconnect.spam" ); + return null; + } + }; + + this.minecraftServer.processQueue.add(waitable); + + try { + waitable.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } else { + PlayerConnection.this.disconnect( "disconnect.spam" ); + } } } } @@ -917,6 +940,7 @@ public class PlayerConnection implements PacketPlayInListener { // CraftBukkit end private void handleCommand(String s) { + org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot // CraftBukkit start CraftPlayer player = this.getPlayer(); @@ -924,19 +948,28 @@ public class PlayerConnection implements PacketPlayInListener { this.server.getPluginManager().callEvent(event); if (event.isCancelled()) { + org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot return; } try { - this.c.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit + // Spigot Start + if ( org.spigotmc.SpigotConfig.logCommands ) + { + this.c.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit + } + // Spigot end if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { + org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot return; } } catch (org.bukkit.command.CommandException ex) { player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); java.util.logging.Logger.getLogger(PlayerConnection.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot return; } + org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot //this.minecraftServer.getCommandHandler().a(this.player, s); // CraftBukkit end } @@ -1027,6 +1060,13 @@ public class PlayerConnection implements PacketPlayInListener { if (this.player.dead) return; // CraftBukkit WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension); Entity entity = packetplayinuseentity.a((World) worldserver); + // Spigot Start + if ( entity == player ) + { + disconnect( "Cannot interact with self!" ); + return; + } + // Spigot End this.player.w(); if (entity != null) { @@ -1534,6 +1574,7 @@ public class PlayerConnection implements PacketPlayInListener { for (j = 0; j < 4; ++j) { boolean flag = true; + packetplayinupdatesign.f()[j] = packetplayinupdatesign.f()[j].replaceAll( "\uF700", "" ).replaceAll( "\uF701", "" ); // Spigot - Mac OSX sends weird chars if (packetplayinupdatesign.f()[j].length() > 15) { flag = false; diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java index bceba7b..c963fac 100644 --- a/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -173,6 +173,7 @@ public class PlayerInteractManager { this.o = i1; } } + world.spigotConfig.antiXrayInstance.updateNearbyBlocks(world, i, j, k); // Spigot } } diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index 4383275..9a6e997 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -335,7 +335,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s); } else if (!this.isWhitelisted(gameprofile.getName())) { // return "You are not white-listed on this server!"; - event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, "You are not white-listed on this server!"); + event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot } else { String s1 = socketaddress.toString(); @@ -354,7 +354,7 @@ public abstract class PlayerList { } else { // return this.players.size() >= this.maxPlayers ? "The server is full!" : null; if (this.players.size() >= this.maxPlayers) { - event.disallow(PlayerLoginEvent.Result.KICK_FULL, "The server is full!"); + event.disallow(PlayerLoginEvent.Result.KICK_FULL, org.spigotmc.SpigotConfig.serverFullMessage); // Spigot } } } @@ -467,6 +467,11 @@ public abstract class PlayerList { Player respawnPlayer = this.cserver.getPlayer(entityplayer1); PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn); this.cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.playerConnection.isDisconnected()) { + return entityplayer; + } + // Spigot End location = respawnEvent.getRespawnLocation(); entityplayer.reset(); @@ -498,14 +503,10 @@ public abstract class PlayerList { entityplayer1.playerConnection.sendPacket(new PacketPlayOutSpawnPosition(chunkcoordinates1.x, chunkcoordinates1.y, chunkcoordinates1.z)); entityplayer1.playerConnection.sendPacket(new PacketPlayOutExperience(entityplayer1.exp, entityplayer1.expTotal, entityplayer1.expLevel)); this.b(entityplayer1, worldserver); - // CraftBukkit start - // Don't re-add player to player list if disconnected - if (!entityplayer.playerConnection.isDisconnected()) { - worldserver.getPlayerChunkMap().addPlayer(entityplayer1); - worldserver.addEntity(entityplayer1); - this.players.add(entityplayer1); - } - // Added from changeDimension + worldserver.getPlayerChunkMap().addPlayer(entityplayer1); + worldserver.addEntity(entityplayer1); + this.players.add(entityplayer1); + // CraftBukkit start - Added from changeDimension this.updateClient(entityplayer1); // Update health, etc... entityplayer1.updateAbilities(); Iterator iterator = entityplayer1.getEffects().iterator(); @@ -519,17 +520,11 @@ public abstract class PlayerList { // CraftBukkit end entityplayer1.setHealth(entityplayer1.getHealth()); - // CraftBukkit start - // Don't fire on respawn + // CraftBukkit start - Don't fire on respawn if (fromWorld != location.getWorld()) { PlayerChangedWorldEvent event = new PlayerChangedWorldEvent((Player) entityplayer1.getBukkitEntity(), fromWorld); Bukkit.getServer().getPluginManager().callEvent(event); } - - // Save player file again if they were disconnected - if (entityplayer.playerConnection.isDisconnected()) { - this.b(entityplayer1); - } // CraftBukkit end return entityplayer1; @@ -775,6 +770,25 @@ public abstract class PlayerList { this.sendAll(new PacketPlayOutPlayerInfo(entityplayer.getName(), true, entityplayer.ping)); } // CraftBukkit end */ + // Spigot start + if ( !players.isEmpty() ) + { + int index = MinecraftServer.currentTick % this.players.size(); + EntityPlayer player = (EntityPlayer) this.players.get( index ); + if ( player.lastPing == -1 || Math.abs( player.ping - player.lastPing ) > 20 ) + { + Packet packet = new PacketPlayOutPlayerInfo( player.listName, true, player.ping ); + for ( EntityPlayer splayer : (List) this.players ) + { + if ( splayer.getBukkitEntity().canSee( player.getBukkitEntity() ) ) + { + splayer.playerConnection.sendPacket( packet ); + } + } + player.lastPing = player.ping; + } + } + // Spigot end } public void sendAll(Packet packet) { @@ -1106,8 +1120,15 @@ public abstract class PlayerList { } public void r() { - for (int i = 0; i < this.players.size(); ++i) { - ((EntityPlayer) this.players.get(i)).playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message + while (!this.players.isEmpty()) { + // 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/PropertyManager.java b/src/main/java/net/minecraft/server/PropertyManager.java index bb535c2..93968bd 100644 --- a/src/main/java/net/minecraft/server/PropertyManager.java +++ b/src/main/java/net/minecraft/server/PropertyManager.java @@ -52,7 +52,7 @@ public class PropertyManager { } private T getOverride(String name, T value) { - if ((this.options != null) && (this.options.has(name))) { + if ((this.options != null) && (this.options.has(name)) && !name.equals( "online-mode")) { // Spigot return (T) this.options.valueOf(name); } diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java new file mode 100644 index 0000000..829f4a3 --- /dev/null +++ b/src/main/java/net/minecraft/server/RegionFileCache.java @@ -0,0 +1,67 @@ +package net.minecraft.server; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class RegionFileCache { + + public static final Map a = new HashMap(); // CraftBukkit - private -> public + + public static synchronized RegionFile a(File file1, int i, int j) { + File file2 = new File(file1, "region"); + File file3 = new File(file2, "r." + (i >> 5) + "." + (j >> 5) + ".mca"); + RegionFile regionfile = (RegionFile) a.get(file3); + + if (regionfile != null) { + return regionfile; + } else { + if (!file2.exists()) { + file2.mkdirs(); + } + + if (a.size() >= 256) { + a(); + } + + RegionFile regionfile1 = new RegionFile(file3); + + a.put(file3, regionfile1); + return regionfile1; + } + } + + public static synchronized void a() { + Iterator iterator = a.values().iterator(); + + while (iterator.hasNext()) { + RegionFile regionfile = (RegionFile) iterator.next(); + + try { + if (regionfile != null) { + regionfile.c(); + } + } catch (IOException ioexception) { + ioexception.printStackTrace(); + } + } + + a.clear(); + } + + public static DataInputStream c(File file1, int i, int j) { + RegionFile regionfile = a(file1, i, j); + + return regionfile.a(i & 31, j & 31); + } + + public static DataOutputStream d(File file1, int i, int j) { + RegionFile regionfile = a(file1, i, j); + + return regionfile.b(i & 31, j & 31); + } +} diff --git a/src/main/java/net/minecraft/server/RemoteControlListener.java b/src/main/java/net/minecraft/server/RemoteControlListener.java new file mode 100644 index 0000000..368f14c --- /dev/null +++ b/src/main/java/net/minecraft/server/RemoteControlListener.java @@ -0,0 +1,107 @@ +package net.minecraft.server; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +public class RemoteControlListener extends RemoteConnectionThread { + + private int h; + private int i; + private String j; + private ServerSocket k; + private String l; + private Map m; + + public RemoteControlListener(IMinecraftServer iminecraftserver) { + super(iminecraftserver, "RCON Listener"); + this.h = iminecraftserver.a("rcon.port", 0); + this.l = iminecraftserver.a("rcon.password", ""); + this.j = iminecraftserver.x(); + this.i = iminecraftserver.y(); + if (0 == this.h) { + this.h = this.i + 10; + this.info("Setting default rcon port to " + this.h); + iminecraftserver.a("rcon.port", Integer.valueOf(this.h)); + if (0 == this.l.length()) { + iminecraftserver.a("rcon.password", ""); + } + + iminecraftserver.a(); + } + + if (0 == this.j.length()) { + this.j = "0.0.0.0"; + } + + this.f(); + this.k = null; + } + + private void f() { + this.m = new HashMap(); + } + + private void g() { + Iterator iterator = this.m.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = (Entry) iterator.next(); + + if (!((RemoteControlSession) entry.getValue()).c()) { + iterator.remove(); + } + } + } + + public void run() { + this.info("RCON running on " + this.j + ":" + this.h); + + try { + while (this.running) { + try { + Socket socket = this.k.accept(); + + socket.setSoTimeout(500); + RemoteControlSession remotecontrolsession = new RemoteControlSession(this.server, socket); + + remotecontrolsession.a(); + this.m.put(socket.getRemoteSocketAddress(), remotecontrolsession); + this.g(); + } catch (SocketTimeoutException sockettimeoutexception) { + this.g(); + } catch (IOException ioexception) { + if (this.running) { + this.info("IO: " + ioexception.getMessage()); + } + } + } + } finally { + this.b(this.k); + } + } + + public void a() { + if (0 == this.l.length()) { + this.warning("No rcon password set in \'" + this.server.b() + "\', rcon disabled!"); + } else if (0 < this.h && '\uffff' >= this.h) { + if (!this.running) { + try { + this.k = new ServerSocket(this.h, 0, InetAddress.getByName(this.j)); + this.k.setSoTimeout(500); + super.a(); + } catch (IOException ioexception) { + this.warning("Unable to initialise rcon on " + this.j + ":" + this.h + " : " + ioexception.getMessage()); + } + } + } else { + this.warning("Invalid rcon port " + this.h + " found in \'" + this.server.b() + "\', rcon disabled!"); + } + } +} diff --git a/src/main/java/net/minecraft/server/ServerConnection.java b/src/main/java/net/minecraft/server/ServerConnection.java new file mode 100644 index 0000000..c91ed8e --- /dev/null +++ b/src/main/java/net/minecraft/server/ServerConnection.java @@ -0,0 +1,109 @@ +package net.minecraft.server; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; + +import net.minecraft.util.com.google.common.util.concurrent.ThreadFactoryBuilder; +import net.minecraft.util.io.netty.bootstrap.ServerBootstrap; +import net.minecraft.util.io.netty.channel.ChannelFuture; +import net.minecraft.util.io.netty.channel.nio.NioEventLoopGroup; +import net.minecraft.util.io.netty.channel.socket.nio.NioServerSocketChannel; +import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ServerConnection { + + private static final Logger b = LogManager.getLogger(); + private static final NioEventLoopGroup c = new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty IO #%d").setDaemon(true).build()); + private final MinecraftServer d; + public volatile boolean a; + private final List e = Collections.synchronizedList(new ArrayList()); + private final List f = Collections.synchronizedList(new ArrayList()); + + public ServerConnection(MinecraftServer minecraftserver) { + this.d = minecraftserver; + this.a = true; + } + + public void a(InetAddress inetaddress, int i) { + List list = this.e; + + synchronized (this.e) { + this.e.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(NioServerSocketChannel.class)).childHandler(new ServerConnectionChannel(this)).group(c).localAddress(inetaddress, i)).bind().syncUninterruptibly()); + } + } + + public void b() { + this.a = false; + Iterator iterator = this.e.iterator(); + + while (iterator.hasNext()) { + ChannelFuture channelfuture = (ChannelFuture) iterator.next(); + + channelfuture.channel().close().syncUninterruptibly(); + } + } + + public void c() { + List list = this.f; + + synchronized (this.f) { + // Spigot Start + // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order + if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) + { + Collections.shuffle( this.f ); + } + // Spigot End + Iterator iterator = this.f.iterator(); + + while (iterator.hasNext()) { + NetworkManager networkmanager = (NetworkManager) iterator.next(); + + if (!networkmanager.d()) { + iterator.remove(); + if (networkmanager.f() != null) { + networkmanager.getPacketListener().a(networkmanager.f()); + } else if (networkmanager.getPacketListener() != null) { + networkmanager.getPacketListener().a(new ChatComponentText("Disconnected")); + } + } else { + try { + networkmanager.a(); + } catch (Exception exception) { + if (networkmanager.c()) { + CrashReport crashreport = CrashReport.a(exception, "Ticking memory connection"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Ticking connection"); + + crashreportsystemdetails.a("Connection", (Callable) (new CrashReportServerConnection(this, networkmanager))); + throw new ReportedException(crashreport); + } + + b.warn("Failed to handle packet for " + networkmanager.getSocketAddress(), exception); + ChatComponentText chatcomponenttext = new ChatComponentText("Internal server error"); + + networkmanager.handle(new PacketPlayOutKickDisconnect(chatcomponenttext), new GenericFutureListener[] { new ServerConnectionFuture(this, networkmanager, chatcomponenttext)}); + networkmanager.g(); + } + } + } + } + } + + public MinecraftServer d() { + return this.d; + } + + static List a(ServerConnection serverconnection) { + return serverconnection.f; + } + + static MinecraftServer b(ServerConnection serverconnection) { + return serverconnection.d; + } +} diff --git a/src/main/java/net/minecraft/server/ServerConnectionChannel.java b/src/main/java/net/minecraft/server/ServerConnectionChannel.java new file mode 100644 index 0000000..fb95be4 --- /dev/null +++ b/src/main/java/net/minecraft/server/ServerConnectionChannel.java @@ -0,0 +1,37 @@ +package net.minecraft.server; + +import net.minecraft.util.io.netty.channel.Channel; +import net.minecraft.util.io.netty.channel.ChannelException; +import net.minecraft.util.io.netty.channel.ChannelInitializer; +import net.minecraft.util.io.netty.channel.ChannelOption; +import net.minecraft.util.io.netty.handler.timeout.ReadTimeoutHandler; + +class ServerConnectionChannel extends ChannelInitializer { + + final ServerConnection a; + + ServerConnectionChannel(ServerConnection serverconnection) { + this.a = serverconnection; + } + + protected void initChannel(Channel channel) { + try { + channel.config().setOption(ChannelOption.IP_TOS, Integer.valueOf(24)); + } catch (ChannelException channelexception) { + ; + } + + try { + channel.config().setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(false)); + } catch (ChannelException channelexception1) { + ; + } + + channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyPingHandler(this.a)).addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder()).addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder()); + NetworkManager networkmanager = new NetworkManager(false); + + ServerConnection.a(this.a).add(networkmanager); + channel.pipeline().addLast("packet_handler", networkmanager); + networkmanager.a((PacketListener) (new HandshakeListener(ServerConnection.b(this.a), networkmanager))); + } +} diff --git a/src/main/java/net/minecraft/server/ServerStatisticManager.java b/src/main/java/net/minecraft/server/ServerStatisticManager.java new file mode 100644 index 0000000..3f78728 --- /dev/null +++ b/src/main/java/net/minecraft/server/ServerStatisticManager.java @@ -0,0 +1,211 @@ +package net.minecraft.server; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import net.minecraft.util.com.google.common.collect.Maps; +import net.minecraft.util.com.google.common.collect.Sets; +import net.minecraft.util.com.google.gson.JsonElement; +import net.minecraft.util.com.google.gson.JsonObject; +import net.minecraft.util.com.google.gson.JsonParseException; +import net.minecraft.util.com.google.gson.JsonParser; +import net.minecraft.util.org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ServerStatisticManager extends StatisticManager { + + private static final Logger b = LogManager.getLogger(); + private final MinecraftServer c; + private final File d; + private final Set e = Sets.newHashSet(); + private int f = -300; + private boolean g = false; + + public ServerStatisticManager(MinecraftServer minecraftserver, File file1) { + this.c = minecraftserver; + this.d = file1; + // Spigot start + for ( String name : org.spigotmc.SpigotConfig.forcedStats.keySet() ) + { + StatisticWrapper wrapper = new StatisticWrapper(); + wrapper.a( org.spigotmc.SpigotConfig.forcedStats.get( name ) ); + a.put( StatisticList.a( name ), wrapper ); + } + // Spigot end + } + + public void a() { + if (this.d.isFile()) { + try { + this.a.clear(); + this.a.putAll(this.a(FileUtils.readFileToString(this.d))); + } catch (IOException ioexception) { + b.error("Couldn\'t read statistics file " + this.d, ioexception); + } catch (JsonParseException jsonparseexception) { + b.error("Couldn\'t parse statistics file " + this.d, jsonparseexception); + } + } + } + + public void b() { + if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot + try { + FileUtils.writeStringToFile(this.d, a(this.a)); + } catch (IOException ioexception) { + b.error("Couldn\'t save stats", ioexception); + } + } + + public void a(EntityHuman entityhuman, Statistic statistic, int i) { + if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot + int j = statistic.d() ? this.a(statistic) : 0; + + super.a(entityhuman, statistic, i); + this.e.add(statistic); + if (statistic.d() && j == 0 && i > 0) { + this.g = true; + if (this.c.ar()) { + this.c.getPlayerList().sendMessage(new ChatMessage("chat.type.achievement", new Object[] { entityhuman.getScoreboardDisplayName(), statistic.j()})); + } + } + } + + public Set c() { + HashSet hashset = Sets.newHashSet(this.e); + + this.e.clear(); + this.g = false; + return hashset; + } + + public Map a(String s) { + JsonElement jsonelement = (new JsonParser()).parse(s); + + if (!jsonelement.isJsonObject()) { + return Maps.newHashMap(); + } else { + JsonObject jsonobject = jsonelement.getAsJsonObject(); + HashMap hashmap = Maps.newHashMap(); + Iterator iterator = jsonobject.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = (Entry) iterator.next(); + Statistic statistic = StatisticList.a((String) entry.getKey()); + + if (statistic != null) { + StatisticWrapper statisticwrapper = new StatisticWrapper(); + + if (((JsonElement) entry.getValue()).isJsonPrimitive() && ((JsonElement) entry.getValue()).getAsJsonPrimitive().isNumber()) { + statisticwrapper.a(((JsonElement) entry.getValue()).getAsInt()); + } else if (((JsonElement) entry.getValue()).isJsonObject()) { + JsonObject jsonobject1 = ((JsonElement) entry.getValue()).getAsJsonObject(); + + if (jsonobject1.has("value") && jsonobject1.get("value").isJsonPrimitive() && jsonobject1.get("value").getAsJsonPrimitive().isNumber()) { + statisticwrapper.a(jsonobject1.getAsJsonPrimitive("value").getAsInt()); + } + + if (jsonobject1.has("progress") && statistic.l() != null) { + try { + Constructor constructor = statistic.l().getConstructor(new Class[0]); + IJsonStatistic ijsonstatistic = (IJsonStatistic) constructor.newInstance(new Object[0]); + + statisticwrapper.a(ijsonstatistic); + } catch (Throwable throwable) { + b.warn("Invalid statistic progress in " + this.d, throwable); + } + } + } + + hashmap.put(statistic, statisticwrapper); + } else { + b.warn("Invalid statistic in " + this.d + ": Don\'t know what " + (String) entry.getKey() + " is"); + } + } + + return hashmap; + } + } + + public static String a(Map map) { + JsonObject jsonobject = new JsonObject(); + Iterator iterator = map.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = (Entry) iterator.next(); + + if (((StatisticWrapper) entry.getValue()).b() != null) { + JsonObject jsonobject1 = new JsonObject(); + + jsonobject1.addProperty("value", Integer.valueOf(((StatisticWrapper) entry.getValue()).a())); + + try { + jsonobject1.add("progress", ((StatisticWrapper) entry.getValue()).b().a()); + } catch (Throwable throwable) { + b.warn("Couldn\'t save statistic " + ((Statistic) entry.getKey()).e() + ": error serializing progress", throwable); + } + + jsonobject.add(((Statistic) entry.getKey()).e, jsonobject1); + } else { + jsonobject.addProperty(((Statistic) entry.getKey()).e, Integer.valueOf(((StatisticWrapper) entry.getValue()).a())); + } + } + + return jsonobject.toString(); + } + + public void d() { + Iterator iterator = this.a.keySet().iterator(); + + while (iterator.hasNext()) { + Statistic statistic = (Statistic) iterator.next(); + + this.e.add(statistic); + } + } + + public void a(EntityPlayer entityplayer) { + int i = this.c.aj(); + HashMap hashmap = Maps.newHashMap(); + + if (this.g || i - this.f > 300) { + this.f = i; + Iterator iterator = this.c().iterator(); + + while (iterator.hasNext()) { + Statistic statistic = (Statistic) iterator.next(); + + hashmap.put(statistic, Integer.valueOf(this.a(statistic))); + } + } + + entityplayer.playerConnection.sendPacket(new PacketPlayOutStatistic(hashmap)); + } + + public void b(EntityPlayer entityplayer) { + HashMap hashmap = Maps.newHashMap(); + Iterator iterator = AchievementList.e.iterator(); + + while (iterator.hasNext()) { + Achievement achievement = (Achievement) iterator.next(); + + if (this.a(achievement)) { + hashmap.put(achievement, Integer.valueOf(this.a((Statistic) achievement))); + this.e.remove(achievement); + } + } + + entityplayer.playerConnection.sendPacket(new PacketPlayOutStatistic(hashmap)); + } + + public boolean e() { + return this.g; + } +} diff --git a/src/main/java/net/minecraft/server/ShapedRecipes.java b/src/main/java/net/minecraft/server/ShapedRecipes.java index cc444db..867dd07 100644 --- a/src/main/java/net/minecraft/server/ShapedRecipes.java +++ b/src/main/java/net/minecraft/server/ShapedRecipes.java @@ -10,7 +10,7 @@ public class ShapedRecipes implements IRecipe { private int width; private int height; private ItemStack[] items; - private ItemStack result; + public ItemStack result; // Spigot private boolean e; public ShapedRecipes(int i, int j, ItemStack[] aitemstack, ItemStack itemstack) { @@ -156,4 +156,11 @@ public class ShapedRecipes implements IRecipe { this.e = true; return this; } + + // Spigot start + public java.util.List getIngredients() + { + return java.util.Arrays.asList( items ); + } + // Spigot end } diff --git a/src/main/java/net/minecraft/server/ShapelessRecipes.java b/src/main/java/net/minecraft/server/ShapelessRecipes.java index 0fab83c..21181fb 100644 --- a/src/main/java/net/minecraft/server/ShapelessRecipes.java +++ b/src/main/java/net/minecraft/server/ShapelessRecipes.java @@ -11,7 +11,7 @@ import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; public class ShapelessRecipes implements IRecipe { - private final ItemStack result; + public final ItemStack result; // Spigot private final List ingredients; public ShapelessRecipes(ItemStack itemstack, List list) { @@ -75,4 +75,11 @@ public class ShapelessRecipes implements IRecipe { public int a() { return this.ingredients.size(); } + + // Spigot start + public java.util.List getIngredients() + { + return java.util.Collections.unmodifiableList( ingredients ); + } + // Spigot end } diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java index f514b78..3f0dd70 100644 --- a/src/main/java/net/minecraft/server/SpawnerCreature.java +++ b/src/main/java/net/minecraft/server/SpawnerCreature.java @@ -27,6 +27,23 @@ public final class SpawnerCreature { return new ChunkPosition(k, i1, l); } + // Spigot start - get entity count only from chunks being processed in b + private int getEntityCount(WorldServer server, Class oClass) + { + int i = 0; + for ( Long coord : this.a.keySet() ) + { + int x = LongHash.msw( coord ); + int z = LongHash.lsw( coord ); + if ( !server.chunkProviderServer.unloadQueue.contains( coord ) && server.isChunkLoaded( x, z ) ) + { + i += server.getChunkAt( x, z ).entityCount.get( oClass ); + } + } + return i; + } + // Spigot end + public int spawnEntities(WorldServer worldserver, boolean flag, boolean flag1, boolean flag2) { if (!flag && !flag1) { return 0; @@ -42,6 +59,11 @@ public final class SpawnerCreature { j = MathHelper.floor(entityhuman.locZ / 16.0D); byte b0 = 8; + // Spigot Start + b0 = worldserver.spigotConfig.mobSpawnRange; + b0 = ( b0 > worldserver.spigotConfig.viewDistance ) ? (byte) worldserver.spigotConfig.viewDistance : b0; + b0 = ( b0 > 8 ) ? 8 : b0; + // Spigot End for (int l = -b0; l <= b0; ++l) { for (int i1 = -b0; i1 <= b0; ++i1) { @@ -89,13 +111,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 * this.a.size() / 256) { // CraftBukkit - use per-world limits + if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2) && (mobcnt = getEntityCount(worldserver, enumcreaturetype.a())) <= limit * this.a.size() / 256) { // Spigot - use per-world limits and use all loaded chunks Iterator iterator = this.a.keySet().iterator(); + int moblimit = (limit * this.a.size() / 256) - mobcnt + 1; // Spigot - 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(); @@ -160,6 +184,13 @@ public final class SpawnerCreature { groupdataentity = entityinsentient.a(groupdataentity); worldserver.addEntity(entityinsentient, SpawnReason.NATURAL); // CraftBukkit end + // Spigot start + if ( --moblimit <= 0 ) + { + // If we're past limit, stop spawn + continue label110; + } + // Spigot end if (j2 >= entityinsentient.bz()) { continue label110; } diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java new file mode 100644 index 0000000..2a6a571 --- /dev/null +++ b/src/main/java/net/minecraft/server/StructureGenerator.java @@ -0,0 +1,225 @@ +package net.minecraft.server; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.Callable; + +public abstract class StructureGenerator extends WorldGenBase { + + private PersistentStructure e; + protected Map d = new HashMap(); + + public StructureGenerator() {} + + public abstract String a(); + + protected final void a(World world, int i, int j, int k, int l, Block[] ablock) { + this.a(world); + if (!this.d.containsKey(Long.valueOf(ChunkCoordIntPair.a(i, j)))) { + this.b.nextInt(); + + try { + if (this.a(i, j)) { + StructureStart structurestart = this.b(i, j); + + this.d.put(Long.valueOf(ChunkCoordIntPair.a(i, j)), structurestart); + this.a(i, j, structurestart); + } + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Exception preparing structure feature"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Feature being prepared"); + + crashreportsystemdetails.a("Is feature chunk", (Callable) (new CrashReportIsFeatureChunk(this, i, j))); + crashreportsystemdetails.a("Chunk location", String.format("%d,%d", new Object[] { Integer.valueOf(i), Integer.valueOf(j)})); + crashreportsystemdetails.a("Chunk pos hash", (Callable) (new CrashReportChunkPosHash(this, i, j))); + crashreportsystemdetails.a("Structure type", (Callable) (new CrashReportStructureType(this))); + throw new ReportedException(crashreport); + } + } + } + + public boolean a(World world, Random random, int i, int j) { + this.a(world); + int k = (i << 4) + 8; + int l = (j << 4) + 8; + boolean flag = false; + Iterator iterator = this.d.values().iterator(); + + while (iterator.hasNext()) { + StructureStart structurestart = (StructureStart) iterator.next(); + + if (structurestart.d() && structurestart.a().a(k, l, k + 15, l + 15)) { + structurestart.a(world, random, new StructureBoundingBox(k, l, k + 15, l + 15)); + flag = true; + this.a(structurestart.e(), structurestart.f(), structurestart); + } + } + + return flag; + } + + public boolean b(int i, int j, int k) { + this.a(this.c); + return this.c(i, j, k) != null; + } + + protected StructureStart c(int i, int j, int k) { + Iterator iterator = this.d.values().iterator(); + + while (iterator.hasNext()) { + StructureStart structurestart = (StructureStart) iterator.next(); + + if (structurestart.d() && structurestart.a().a(i, k, i, k)) { + Iterator iterator1 = structurestart.b().iterator(); + + while (iterator1.hasNext()) { + StructurePiece structurepiece = (StructurePiece) iterator1.next(); + + if (structurepiece.c().b(i, j, k)) { + return structurestart; + } + } + } + } + + return null; + } + + public boolean d(int i, int j, int k) { + this.a(this.c); + Iterator iterator = this.d.values().iterator(); + + StructureStart structurestart; + + do { + if (!iterator.hasNext()) { + return false; + } + + structurestart = (StructureStart) iterator.next(); + } while (!structurestart.d()); + + return structurestart.a().a(i, k, i, k); + } + + public ChunkPosition getNearestGeneratedFeature(World world, int i, int j, int k) { + this.c = world; + this.a(world); + this.b.setSeed(world.getSeed()); + long l = this.b.nextLong(); + long i1 = this.b.nextLong(); + long j1 = (long) (i >> 4) * l; + long k1 = (long) (k >> 4) * i1; + + this.b.setSeed(j1 ^ k1 ^ world.getSeed()); + this.a(world, i >> 4, k >> 4, 0, 0, (Block[]) null); + double d0 = Double.MAX_VALUE; + ChunkPosition chunkposition = null; + Iterator iterator = this.d.values().iterator(); + + ChunkPosition chunkposition1; + int l1; + int i2; + double d1; + int j2; + + while (iterator.hasNext()) { + StructureStart structurestart = (StructureStart) iterator.next(); + + if (structurestart.d()) { + StructurePiece structurepiece = (StructurePiece) structurestart.b().get(0); + + chunkposition1 = structurepiece.a(); + i2 = chunkposition1.x - i; + l1 = chunkposition1.y - j; + j2 = chunkposition1.z - k; + d1 = (double) (i2 * i2 + l1 * l1 + j2 * j2); + if (d1 < d0) { + d0 = d1; + chunkposition = chunkposition1; + } + } + } + + if (chunkposition != null) { + return chunkposition; + } else { + List list = this.o_(); + + if (list != null) { + ChunkPosition chunkposition2 = null; + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { + chunkposition1 = (ChunkPosition) iterator1.next(); + i2 = chunkposition1.x - i; + l1 = chunkposition1.y - j; + j2 = chunkposition1.z - k; + d1 = (double) (i2 * i2 + l1 * l1 + j2 * j2); + if (d1 < d0) { + d0 = d1; + chunkposition2 = chunkposition1; + } + } + + return chunkposition2; + } else { + return null; + } + } + } + + protected List o_() { + return null; + } + + private void a(World world) { + if (this.e == null) { + // Spigot Start + if ( world.spigotConfig.saveStructureInfo && !this.a().equals( "Mineshaft" ) ) + { + this.e = (PersistentStructure) world.a(PersistentStructure.class, this.a()); + } else + { + this.e = new PersistentStructure( this.a() ); + } + // Spigot End + if (this.e == null) { + this.e = new PersistentStructure(this.a()); + world.a(this.a(), (PersistentBase) this.e); + } else { + NBTTagCompound nbttagcompound = this.e.a(); + Iterator iterator = nbttagcompound.c().iterator(); + + while (iterator.hasNext()) { + String s = (String) iterator.next(); + NBTBase nbtbase = nbttagcompound.get(s); + + if (nbtbase.getTypeId() == 10) { + NBTTagCompound nbttagcompound1 = (NBTTagCompound) nbtbase; + + if (nbttagcompound1.hasKey("ChunkX") && nbttagcompound1.hasKey("ChunkZ")) { + int i = nbttagcompound1.getInt("ChunkX"); + int j = nbttagcompound1.getInt("ChunkZ"); + StructureStart structurestart = WorldGenFactory.a(nbttagcompound1, world); + + this.d.put(Long.valueOf(ChunkCoordIntPair.a(i, j)), structurestart); + } + } + } + } + } + } + + private void a(int i, int j, StructureStart structurestart) { + this.e.a(structurestart.a(i, j), i, j); + this.e.c(); + } + + protected abstract boolean a(int i, int j); + + protected abstract StructureStart b(int i, int j); +} diff --git a/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java b/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java index cc96775..ea8c269 100644 --- a/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java +++ b/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java @@ -22,47 +22,21 @@ class ThreadPlayerLookupUUID extends Thread { public void run() { try { + // Spigot Start + if ( !LoginListener.b( this.a ).getOnlineMode() ) + { + fireLoginEvents(); + LoginListener.a(this.a, EnumProtocolState.READY_TO_ACCEPT); + return; + } + // Spigot End String s = (new BigInteger(MinecraftEncryption.a(LoginListener.a(this.a), LoginListener.b(this.a).I().getPublic(), LoginListener.c(this.a)))).toString(16); LoginListener.a(this.a, LoginListener.b(this.a).as().hasJoinedServer(new GameProfile((String) null, LoginListener.d(this.a).getName()), s)); if (LoginListener.d(this.a) != null) { - // CraftBukkit start - if (!this.a.networkManager.d()) { - return; - } - - String playerName = LoginListener.d(this.a).getName(); - java.net.InetAddress address = ((java.net.InetSocketAddress) a.networkManager.getSocketAddress()).getAddress(); - final org.bukkit.craftbukkit.CraftServer server = LoginListener.b(this.a).server; - - AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address); - server.getPluginManager().callEvent(asyncEvent); - - if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { - final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address); - if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { - event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); - } - Waitable waitable = new Waitable() { - @Override - protected PlayerPreLoginEvent.Result evaluate() { - server.getPluginManager().callEvent(event); - return event.getResult(); - }}; - - LoginListener.b(this.a).processQueue.add(waitable); - if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { - this.a.disconnect(event.getKickMessage()); - return; - } - } else { - if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - this.a.disconnect(asyncEvent.getKickMessage()); - return; - } - } - // CraftBukkit end - + // Spigot Start + fireLoginEvents(); + // Spigot End LoginListener.e().info("UUID of player " + LoginListener.d(this.a).getName() + " is " + LoginListener.d(this.a).getId()); LoginListener.a(this.a, EnumProtocolState.READY_TO_ACCEPT); } else { @@ -79,4 +53,44 @@ class ThreadPlayerLookupUUID extends Thread { // CraftBukkit end } } + + private void fireLoginEvents() throws Exception + { + // CraftBukkit start + if (!this.a.networkManager.d()) { + return; + } + + String playerName = LoginListener.d(this.a).getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) a.networkManager.getSocketAddress()).getAddress(); + final org.bukkit.craftbukkit.CraftServer server = LoginListener.b(this.a).server; + + AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address); + server.getPluginManager().callEvent(asyncEvent); + + if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { + final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address); + if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { + event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); + } + Waitable waitable = new Waitable() { + @Override + protected PlayerPreLoginEvent.Result evaluate() { + server.getPluginManager().callEvent(event); + return event.getResult(); + }}; + + LoginListener.b(this.a).processQueue.add(waitable); + if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { + this.a.disconnect(event.getKickMessage()); + return; + } + } else { + if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + this.a.disconnect(asyncEvent.getKickMessage()); + return; + } + } + // CraftBukkit end + } } diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 811f1a4..3de32fe 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -7,10 +7,12 @@ import java.util.concurrent.Callable; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.spigotmc.CustomTimingsHandler; // Spigot import org.bukkit.inventory.InventoryHolder; // CraftBukkit public class TileEntity { + public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot private static final Logger a = LogManager.getLogger(); private static Map i = new HashMap(); private static Map j = new HashMap(); diff --git a/src/main/java/net/minecraft/server/TileEntityHopper.java b/src/main/java/net/minecraft/server/TileEntityHopper.java index 730eb78..a68b748 100644 --- a/src/main/java/net/minecraft/server/TileEntityHopper.java +++ b/src/main/java/net/minecraft/server/TileEntityHopper.java @@ -182,12 +182,17 @@ public class TileEntityHopper extends TileEntity implements IHopper { flag = suckInItems(this) || flag; if (flag) { - this.c(8); + this.c(world.spigotConfig.hopperTransfer); // Spigot this.update(); return true; } } - + // Spigot start + if ( !this.j() ) + { + this.c( world.spigotConfig.hopperCheck ); + } + // Spigot end return false; } else { return false; @@ -218,7 +223,7 @@ public class TileEntityHopper extends TileEntity implements IHopper { this.getWorld().getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { this.setItem(i, itemstack); - this.c(8); // Delay hopper checks + this.c(world.spigotConfig.hopperTransfer); // Spigot return false; } ItemStack itemstack1 = addItem(iinventory, CraftItemStack.asNMSCopy(event.getItem()), Facing.OPPOSITE_FACING[BlockHopper.b(this.p())]); @@ -299,9 +304,9 @@ public class TileEntityHopper extends TileEntity implements IHopper { iinventory.setItem(i, itemstack1); if (ihopper instanceof TileEntityHopper) { - ((TileEntityHopper) ihopper).c(8); // Delay hopper checks + ((TileEntityHopper) ihopper).c(ihopper.getWorld().spigotConfig.hopperTransfer); // Spigot } else if (ihopper instanceof EntityMinecartHopper) { - ((EntityMinecartHopper) ihopper).l(4); // Delay hopper minecart checks + ((EntityMinecartHopper) ihopper).l(ihopper.getWorld().spigotConfig.hopperTransfer / 2); // Spigot } return false; @@ -405,7 +410,7 @@ public class TileEntityHopper extends TileEntity implements IHopper { if (flag) { if (iinventory instanceof TileEntityHopper) { - ((TileEntityHopper) iinventory).c(8); + ((TileEntityHopper) iinventory).c(((TileEntityHopper) iinventory).world.spigotConfig.hopperTransfer); // Spigot iinventory.update(); } diff --git a/src/main/java/net/minecraft/server/WatchableObject.java b/src/main/java/net/minecraft/server/WatchableObject.java new file mode 100644 index 0000000..678aa91 --- /dev/null +++ b/src/main/java/net/minecraft/server/WatchableObject.java @@ -0,0 +1,44 @@ +package net.minecraft.server; + +public class WatchableObject { + + private final int a; + private final int b; + private Object c; + private boolean d; + + public WatchableObject(int i, int j, Object object) { + this.b = j; + this.c = object; + this.a = i; + this.d = true; + } + + public int a() { + return this.b; + } + + public void a(Object object) { + this.c = object; + } + + public Object b() { + return this.c; + } + + public int c() { + return this.a; + } + + public boolean d() { + return this.d; + } + + public void a(boolean flag) { + this.d = flag; + } + + static boolean a(WatchableObject watchableobject, boolean flag) { + return watchableobject.d = flag; + } +} diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index f850a0f..ac9b669 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -14,6 +14,7 @@ import java.util.concurrent.Callable; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.LongHashSet; +import org.bukkit.craftbukkit.SpigotTimings; // Spigot import org.bukkit.craftbukkit.util.UnsafeList; import org.bukkit.generator.ChunkGenerator; import org.bukkit.craftbukkit.CraftServer; @@ -29,7 +30,25 @@ import org.bukkit.event.weather.ThunderChangeEvent; public abstract class World implements IBlockAccess { public boolean d; - public List entityList = new ArrayList(); + public List entityList = new ArrayList() { // Spigot start - guard entity list from removals + @Override + public Object remove(int index) { + guard(); + return super.remove(index); + } + + @Override + public boolean remove(Object o) { + guard(); + return super.remove(o); + } + + private void guard() { + if (guardEntityList) { + throw new java.util.ConcurrentModificationException(); + } + } + }; // Spigot end protected List f = new ArrayList(); public Set tileEntityList = new HashSet(); // CraftBukkit - ArrayList -> HashSet private List a = new ArrayList(); @@ -62,7 +81,7 @@ public abstract class World implements IBlockAccess { public Scoreboard scoreboard = new Scoreboard(); // CraftBukkit - protected -> public public boolean isStatic; // CraftBukkit start - public, longhashset - protected LongHashSet chunkTickList = new LongHashSet(); + // protected LongHashSet chunkTickList = new LongHashSet(); // Spigot private int L; public boolean allowMonsters; public boolean allowAnimals; @@ -76,6 +95,31 @@ public abstract class World implements IBlockAccess { private boolean N; int[] I; + // Spigot start + private boolean guardEntityList = false; + protected final gnu.trove.map.hash.TLongShortHashMap chunkTickList; + protected float growthOdds = 100; + protected float modifiedOdds = 100; + private final byte chunkTickRadius; + + public static 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 int keyToX(long k) + { + return (int) ( ( ( k >> 16 ) & 0xFFFF0000 ) | ( k & 0x0000FFFF ) ); + } + + public static 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); @@ -107,6 +151,9 @@ public abstract class World implements IBlockAccess { int lastXAccessed = Integer.MIN_VALUE; int lastZAccessed = Integer.MIN_VALUE; final Object chunkLock = new Object(); + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + + public final SpigotTimings.WorldTimingsHandler timings; // Spigot public CraftWorld getWorld() { return this.world; @@ -118,11 +165,17 @@ public abstract class World implements IBlockAccess { // Changed signature - added gen and env public World(IDataManager idatamanager, String s, WorldSettings worldsettings, WorldProvider worldprovider, MethodProfiler methodprofiler, ChunkGenerator gen, org.bukkit.World.Environment env) { + this.spigotConfig = new org.spigotmc.SpigotWorldConfig( s ); // Spigot this.generator = gen; this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); // CraftBukkit // CraftBukkit end + // Spigot start + this.chunkTickRadius = (byte) ( ( this.getServer().getViewDistance() < 7 ) ? this.getServer().getViewDistance() : 7 ); + this.chunkTickList = new gnu.trove.map.hash.TLongShortHashMap( spigotConfig.chunksPerTick * 5, 0.7f, Long.MIN_VALUE, Short.MIN_VALUE ); + this.chunkTickList.setAutoCompactionFactor( 0 ); + // Spigot end this.L = this.random.nextInt(12000); this.allowMonsters = true; @@ -181,6 +234,7 @@ public abstract class World implements IBlockAccess { this.a(); this.getServer().addWorld(this.world); // CraftBukkit + timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot } protected abstract IChunkProvider j(); @@ -261,20 +315,18 @@ public abstract class World implements IBlockAccess { return this.getChunkAt(i >> 4, j >> 4); } + // Spigot start public Chunk getChunkAt(int i, int j) { - // CraftBukkit start - Chunk result = null; - synchronized (this.chunkLock) { - if (this.lastChunkAccessed == null || this.lastXAccessed != i || this.lastZAccessed != j) { - this.lastChunkAccessed = this.chunkProvider.getOrCreateChunk(i, j); - this.lastXAccessed = i; - this.lastZAccessed = j; - } - result = this.lastChunkAccessed; + //synchronized (this.chunkLock) { + Chunk result = this.lastChunkAccessed; // Exploit fact that read is atomic + if (result == null || result.locX != i || result.locZ != j) { + result = this.chunkProvider.getOrCreateChunk(i, j); + this.lastChunkAccessed = result; // Exploit fact that write is atomic } + //} return result; - // CraftBukkit end } + // Spigot end public boolean setTypeAndData(int i, int j, int k, Block block, int l, int i1) { if (i >= -30000000 && k >= -30000000 && i < 30000000 && k < 30000000) { @@ -438,6 +490,7 @@ public abstract class World implements IBlockAccess { this.e(i, j + 1, k, block); this.e(i, j, k - 1, block); this.e(i, j, k + 1, block); + spigotConfig.antiXrayInstance.updateNearbyBlocks(this, i, j, k); // Spigot } public void b(int i, int j, int k, Block block, int l) { @@ -872,6 +925,7 @@ public abstract class World implements IBlockAccess { } public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous entity add!"); // Spigot if (entity == null) return false; // CraftBukkit end @@ -903,6 +957,23 @@ public abstract class World implements IBlockAccess { // Not all projectiles extend EntityProjectile, so check for Bukkit interface instead event = CraftEventFactory.callProjectileLaunchEvent(entity); } + // Spigot start + else if (entity instanceof EntityExperienceOrb) { + EntityExperienceOrb xp = (EntityExperienceOrb) entity; + double radius = spigotConfig.expMerge; + if (radius > 0) { + List entities = this.getEntities(entity, entity.boundingBox.grow(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof EntityExperienceOrb) { + EntityExperienceOrb loopItem = (EntityExperienceOrb) e; + if (!loopItem.dead) { + xp.value += loopItem.value; + loopItem.die(); + } + } + } + } + } // Spigot end if (event != null && (event.isCancelled() || entity.dead)) { entity.dead = true; @@ -961,21 +1032,25 @@ public abstract class World implements IBlockAccess { } public void removeEntity(Entity entity) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous entity remove!"); // Spigot entity.die(); if (entity instanceof EntityHuman) { this.players.remove(entity); this.everyoneSleeping(); } + // Spigot start + if (!guardEntityList) { // It will get removed after the tick if we are ticking + int i = entity.ai; + int j = entity.ak; - int i = entity.ai; - int j = entity.ak; + if (entity.ah && this.isChunkLoaded(i, j)) { + this.getChunkAt(i, j).b(entity); + } - if (entity.ah && this.isChunkLoaded(i, j)) { - this.getChunkAt(i, j).b(entity); + this.entityList.remove(entity); + this.b(entity); } - - this.entityList.remove(entity); - this.b(entity); + // Spigot end } public void addIWorldAccess(IWorldAccess iworldaccess) { @@ -991,23 +1066,42 @@ public abstract class World implements IBlockAccess { int i1 = MathHelper.floor(axisalignedbb.c); int j1 = MathHelper.floor(axisalignedbb.f + 1.0D); - for (int k1 = i; k1 < j; ++k1) { - for (int l1 = i1; l1 < j1; ++l1) { - if (this.isLoaded(k1, 64, l1)) { - for (int i2 = k - 1; i2 < l; ++i2) { - Block block; - - if (k1 >= -30000000 && k1 < 30000000 && l1 >= -30000000 && l1 < 30000000) { - block = this.getType(k1, i2, l1); - } else { - block = Blocks.STONE; + // 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++ ) + { + Block block = chunk.getType(x - cx, y, z - cz ); + if ( block != null ) + { + block.a( this, x, y, z, axisalignedbb, this.M, entity ); + } } - - block.a(this, k1, i2, l1, axisalignedbb, this.M, entity); } } } } + // Spigot end double d0 = 0.25D; List list = this.getEntities(entity, axisalignedbb.grow(d0, d0, d0)); @@ -1127,6 +1221,7 @@ public abstract class World implements IBlockAccess { CrashReport crashreport; CrashReportSystemDetails crashreportsystemdetails; + long lastChunk = Long.MIN_VALUE; // Spigot - cache chunk x, z cords for unload queue for (i = 0; i < this.i.size(); ++i) { entity = (Entity) this.i.get(i); // CraftBukkit start - Fixed an NPE, don't process entities in chunks queued for unload @@ -1135,10 +1230,15 @@ public abstract class World implements IBlockAccess { } ChunkProviderServer chunkProviderServer = ((WorldServer) this).chunkProviderServer; - if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { - continue; + // Spigot start - check last chunk to see if this loaded (fast cache) + long chunk = org.bukkit.craftbukkit.util.LongHash.toLong(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4); + if (lastChunk != chunk) { + if (chunkProviderServer.unloadQueue.contains(chunk)) { // Spigot end + continue; + } } // CraftBukkit end + lastChunk = chunk; // Spigot try { ++entity.ticksLived; @@ -1159,6 +1259,7 @@ public abstract class World implements IBlockAccess { this.i.remove(i--); } } + lastChunk = Long.MIN_VALUE; // Spigot this.methodProfiler.c("remove"); this.entityList.removeAll(this.f); @@ -1182,15 +1283,23 @@ public abstract class World implements IBlockAccess { this.f.clear(); this.methodProfiler.c("regular"); + org.spigotmc.ActivationRange.activateEntities(this); // Spigot + timings.entityTick.startTiming(); // Spigot + guardEntityList = true; // Spigot for (i = 0; i < this.entityList.size(); ++i) { entity = (Entity) this.entityList.get(i); // CraftBukkit start - Don't tick entities in chunks queued for unload ChunkProviderServer chunkProviderServer = ((WorldServer) this).chunkProviderServer; - if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { - continue; + // Spigot start - check last chunk to see if this loaded (fast cache) + long chunk = org.bukkit.craftbukkit.util.LongHash.toLong(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4); + if (lastChunk != chunk) { + if (chunkProviderServer.unloadQueue.contains(chunk)) { // Spigot end + continue; + } } // CraftBukkit end + lastChunk = Long.MIN_VALUE; // Spigot if (entity.vehicle != null) { if (!entity.vehicle.dead && entity.vehicle.passenger == entity) { @@ -1204,7 +1313,9 @@ public abstract class World implements IBlockAccess { this.methodProfiler.a("tick"); if (!entity.dead) { try { + SpigotTimings.tickEntityTimer.startTiming(); // Spigot this.playerJoinedWorld(entity); + SpigotTimings.tickEntityTimer.stopTiming(); // Spigot } catch (Throwable throwable1) { crashreport = CrashReport.a(throwable1, "Ticking entity"); crashreportsystemdetails = crashreport.a("Entity being ticked"); @@ -1222,19 +1333,31 @@ public abstract class World implements IBlockAccess { this.getChunkAt(j, k).b(entity); } + guardEntityList = false; // Spigot this.entityList.remove(i--); + guardEntityList = true; // Spigot this.b(entity); } this.methodProfiler.b(); } + guardEntityList = false; // Spigot + timings.entityTick.stopTiming(); // Spigot this.methodProfiler.c("blockEntities"); + timings.tileEntityTick.startTiming(); // Spigot this.N = true; Iterator iterator = this.tileEntityList.iterator(); while (iterator.hasNext()) { TileEntity tileentity = (TileEntity) iterator.next(); + // Spigot start + if (tileentity == null) { + getServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash"); + iterator.remove(); + continue; + } + // Spigot end // CraftBukkit start - Don't tick entities in chunks queued for unload ChunkProviderServer chunkProviderServer = ((WorldServer) this).chunkProviderServer; if (chunkProviderServer.unloadQueue.contains(tileentity.x >> 4, tileentity.z >> 4)) { @@ -1244,8 +1367,11 @@ public abstract class World implements IBlockAccess { if (!tileentity.r() && tileentity.o() && this.isLoaded(tileentity.x, tileentity.y, tileentity.z)) { try { + tileentity.tickTimer.startTiming(); // Spigot tileentity.h(); + tileentity.tickTimer.stopTiming(); // Spigot } catch (Throwable throwable2) { + tileentity.tickTimer.stopTiming(); // Spigot crashreport = CrashReport.a(throwable2, "Ticking block entity"); crashreportsystemdetails = crashreport.a("Block entity being ticked"); tileentity.a(crashreportsystemdetails); @@ -1265,6 +1391,8 @@ public abstract class World implements IBlockAccess { } } + timings.tileEntityTick.stopTiming(); // Spigot + timings.tileEntityPending.startTiming(); // Spigot this.N = false; if (!this.b.isEmpty()) { this.tileEntityList.removeAll(this.b); @@ -1303,6 +1431,7 @@ public abstract class World implements IBlockAccess { this.a.clear(); } + timings.tileEntityPending.stopTiming(); // Spigot this.methodProfiler.b(); this.methodProfiler.b(); } @@ -1324,7 +1453,12 @@ public abstract class World implements IBlockAccess { int j = MathHelper.floor(entity.locZ); byte b0 = 32; - if (!flag || this.b(i - b0, 0, j - b0, i + b0, 0, j + b0)) { + // Spigot start + if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { + entity.ticksLived++; + entity.inactiveTick(); + } else { + entity.tickTimer.startTiming(); // Spigot entity.T = entity.locX; entity.U = entity.locY; entity.V = entity.locZ; @@ -1386,6 +1520,7 @@ public abstract class World implements IBlockAccess { entity.passenger = null; } } + entity.tickTimer.stopTiming(); // Spigot } } @@ -1901,24 +2036,44 @@ public abstract class World implements IBlockAccess { int j; int k; + // Spigot start + int optimalChunks = spigotConfig.chunksPerTick; + // Quick conditions to allow us to exist early + if ( optimalChunks <= 0 || players.isEmpty() ) + { + 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; + // Limit to normal tick radius - including view distance + randRange = ( randRange > chunkTickRadius ) ? chunkTickRadius : randRange; + // odds of growth happening vs growth happening in vanilla + this.growthOdds = this.modifiedOdds = Math.max( 35, Math.min( 100, ( ( chunksPerPlayer + 1 ) * 100F ) / 15F ) ); + // Spigot end 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 + // Spigot start - Always update the chunk the player is on + long key = chunkToKey( j, k ); + int existingPlayers = Math.max( 0, chunkTickList.get( key ) ); // filter out -1 + 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 + j, dz + k ); + if ( !chunkTickList.contains( hash ) && this.isChunkLoaded( dx + j, dz + k ) ) + { + chunkTickList.put( hash, (short) -1 ); // no players } } + // Spigot End } this.methodProfiler.b(); @@ -1927,7 +2082,7 @@ public abstract class World implements IBlockAccess { } this.methodProfiler.a("playerCheckLight"); - if (!this.players.isEmpty()) { + if (spigotConfig.randomLightUpdates && !this.players.isEmpty()) { // Spigot i = this.random.nextInt(this.players.size()); entityhuman = (EntityHuman) this.players.get(i); j = MathHelper.floor(entityhuman.locX) + this.random.nextInt(11) - 5; @@ -2310,6 +2465,7 @@ public abstract class World implements IBlockAccess { } public void a(List list) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous entity world add!"); // Spigot // CraftBukkit start // this.entityList.addAll(list); Entity entity = null; diff --git a/src/main/java/net/minecraft/server/WorldGenForestTree.java b/src/main/java/net/minecraft/server/WorldGenForestTree.java index 71ce973..caaac9a 100644 --- a/src/main/java/net/minecraft/server/WorldGenForestTree.java +++ b/src/main/java/net/minecraft/server/WorldGenForestTree.java @@ -132,7 +132,12 @@ public class WorldGenForestTree extends WorldGenTreeAbstract implements BlockSap int k3; for (k3 = 0; k3 < j3; ++k3) { - this.setTypeAndData(world, i + l2, k2 - k3 - 1, k + i3, Blocks.LOG2, 1); + Block block = world.getType(i + l2, k2 - k3 - 1, k + i3); + + if (block.getMaterial() == Material.AIR || block.getMaterial() == Material.LEAVES) + { + this.setTypeAndData(world, i + l2, k2 - k3 - 1, k + i3, Blocks.LOG2, 1); + } } int l3; diff --git a/src/main/java/net/minecraft/server/WorldGenPackedIce2.java b/src/main/java/net/minecraft/server/WorldGenPackedIce2.java new file mode 100644 index 0000000..a830758 --- /dev/null +++ b/src/main/java/net/minecraft/server/WorldGenPackedIce2.java @@ -0,0 +1,101 @@ +package net.minecraft.server; + +import java.util.Random; + +public class WorldGenPackedIce2 extends WorldGenerator { + + public WorldGenPackedIce2() {} + + public boolean a(World world, Random random, int i, int j, int k) { + while (world.isEmpty(i, j, k) && j > 2) { + --j; + } + + if (world.getType(i, j, k) != Blocks.SNOW_BLOCK) { + return false; + } else { + j += random.nextInt(4); + int l = random.nextInt(4) + 7; + int i1 = l / 4 + random.nextInt(2); + + if (i1 > 1 && random.nextInt(60) == 0) { + j += 10 + random.nextInt(30); + } + + int j1; + int k1; + int l1; + + for (j1 = 0; j1 < l; ++j1) { + float f = (1.0F - (float) j1 / (float) l) * (float) i1; + + k1 = MathHelper.f(f); + + for (l1 = -k1; l1 <= k1; ++l1) { + float f1 = (float) MathHelper.a(l1) - 0.25F; + + for (int i2 = -k1; i2 <= k1; ++i2) { + float f2 = (float) MathHelper.a(i2) - 0.25F; + + if ((l1 == 0 && i2 == 0 || f1 * f1 + f2 * f2 <= f * f) && (l1 != -k1 && l1 != k1 && i2 != -k1 && i2 != k1 || random.nextFloat() <= 0.75F)) { + Block block = world.getType(i + l1, j + j1, k + i2); + + if (block.getMaterial() == Material.AIR || block == Blocks.DIRT || block == Blocks.SNOW_BLOCK || block == Blocks.ICE) { + world.setTypeUpdate(i + l1, j + j1, k + i2, Blocks.PACKED_ICE); // Spigot + } + + if (j1 != 0 && k1 > 1) { + block = world.getType(i + l1, j - j1, k + i2); + if (block.getMaterial() == Material.AIR || block == Blocks.DIRT || block == Blocks.SNOW_BLOCK || block == Blocks.ICE) { + world.setTypeUpdate(i + l1, j - j1, k + i2, Blocks.PACKED_ICE); // Spigot + } + } + } + } + } + } + + j1 = i1 - 1; + if (j1 < 0) { + j1 = 0; + } else if (j1 > 1) { + j1 = 1; + } + + for (int j2 = -j1; j2 <= j1; ++j2) { + k1 = -j1; + + while (k1 <= j1) { + l1 = j - 1; + int k2 = 50; + + if (Math.abs(j2) == 1 && Math.abs(k1) == 1) { + k2 = random.nextInt(5); + } + + while (true) { + if (l1 > 50) { + Block block1 = world.getType(i + j2, l1, k + k1); + + if (block1.getMaterial() == Material.AIR || block1 == Blocks.DIRT || block1 == Blocks.SNOW_BLOCK || block1 == Blocks.ICE || block1 == Blocks.PACKED_ICE) { + world.setTypeUpdate(i + j2, l1, k + k1, Blocks.PACKED_ICE); // Spigot + --l1; + --k2; + if (k2 <= 0) { + l1 -= random.nextInt(5) + 1; + k2 = random.nextInt(5); + } + continue; + } + } + + ++k1; + break; + } + } + } + + return true; + } + } +} diff --git a/src/main/java/net/minecraft/server/WorldMapHumanTracker.java b/src/main/java/net/minecraft/server/WorldMapHumanTracker.java index ec708d1..ef56386 100644 --- a/src/main/java/net/minecraft/server/WorldMapHumanTracker.java +++ b/src/main/java/net/minecraft/server/WorldMapHumanTracker.java @@ -37,23 +37,29 @@ public class WorldMapHumanTracker { int i; int j; - org.bukkit.craftbukkit.map.RenderData render = this.worldMap.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) trackee.getBukkitEntity()); // CraftBukkit + // Spigot start + boolean custom = this.worldMap.mapView.renderers.size() > 1 || !(this.worldMap.mapView.renderers.get(0) instanceof org.bukkit.craftbukkit.map.CraftMapRenderer); + org.bukkit.craftbukkit.map.RenderData render = (custom) ? this.worldMap.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) trackee.getBukkitEntity()) : null; // CraftBukkit if (--this.g < 0) { this.g = 4; - abyte = new byte[render.cursors.size() * 3 + 1]; // CraftBukkit + abyte = new byte[((custom) ? render.cursors.size() : this.worldMap.g.size()) * 3 + 1]; // CraftBukkit abyte[0] = 1; i = 0; // CraftBukkit start - for (i = 0; i < render.cursors.size(); ++i) { - org.bukkit.map.MapCursor cursor = render.cursors.get(i); - if (!cursor.isVisible()) continue; - abyte[i * 3 + 1] = (byte) (cursor.getRawType() << 4 | cursor.getDirection() & 15); - abyte[i * 3 + 2] = (byte) cursor.getX(); - abyte[i * 3 + 3] = (byte) cursor.getY(); + // Spigot start + for (Iterator iterator = ((custom) ? render.cursors.iterator() : this.worldMap.g.values().iterator()); iterator.hasNext(); ++i) { + org.bukkit.map.MapCursor cursor = (custom) ? (org.bukkit.map.MapCursor) iterator.next() : null; + if (cursor != null && !cursor.isVisible()) continue; + WorldMapDecoration deco = (custom) ? null : (WorldMapDecoration) iterator.next(); + + abyte[i * 3 + 1] = (byte) (((custom) ? cursor.getRawType() : deco.type) << 4 | ((custom) ? cursor.getDirection() : deco.rotation) & 15); + abyte[i * 3 + 2] = (byte) ((custom) ? cursor.getX() : deco.locX); + abyte[i * 3 + 3] = (byte) ((custom) ? cursor.getY() : deco.locY); } + // Spigot end // CraftBukkit end boolean flag = !itemstack.A(); @@ -88,7 +94,7 @@ public class WorldMapHumanTracker { abyte1[2] = (byte) j; for (int i1 = 0; i1 < abyte1.length - 3; ++i1) { - abyte1[i1 + 3] = render.buffer[(i1 + j) * 128 + i]; // CraftBukkit + abyte1[i1 + 3] = ((custom) ? render.buffer : this.worldMap.colors)[(i1 + j) * 128 + i]; } this.c[i] = -1; diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index b9b967f..9672508 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -54,7 +54,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate // CraftBukkit end this.server = minecraftserver; this.tracker = new EntityTracker(this); - this.manager = new PlayerChunkMap(this, minecraftserver.getPlayerList().o()); + this.manager = new PlayerChunkMap(this, spigotConfig.viewDistance); // Spigot if (this.entitiesById == null) { this.entitiesById = new IntHashMap(); } @@ -184,10 +184,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate // CraftBukkit start - Only call spawner if we have players online and the world allows for mobs or animals long time = this.worldData.getTime(); if (this.getGameRules().getBoolean("doMobSpawning") && (this.allowMonsters || this.allowAnimals) && (this instanceof WorldServer && this.players.size() > 0)) { + timings.mobSpawn.startTiming(); // Spigot this.R.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 + timings.mobSpawn.stopTiming(); // Spigot } - + // CraftBukkit end + timings.doChunkUnload.startTiming(); // Spigot this.methodProfiler.c("chunkSource"); this.chunkProvider.unloadChunks(); int j = this.a(1.0F); @@ -201,21 +203,36 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate this.worldData.setDayTime(this.worldData.getDayTime() + 1L); } + timings.doChunkUnload.stopTiming(); // Spigot this.methodProfiler.c("tickPending"); + timings.doTickPending.startTiming(); // Spigot this.a(false); + timings.doTickPending.stopTiming(); // Spigot this.methodProfiler.c("tickBlocks"); + timings.doTickTiles.startTiming(); // Spigot this.g(); + timings.doTickTiles.stopTiming(); // Spigot this.methodProfiler.c("chunkMap"); + timings.doChunkMap.startTiming(); // Spigot this.manager.flush(); + timings.doChunkMap.stopTiming(); // Spigot this.methodProfiler.c("village"); + timings.doVillages.startTiming(); // Spigot this.villages.tick(); this.siegeManager.a(); + timings.doVillages.stopTiming(); // Spigot this.methodProfiler.c("portalForcer"); + timings.doPortalForcer.startTiming(); // Spigot this.Q.a(this.getTime()); + timings.doPortalForcer.stopTiming(); // Spigot this.methodProfiler.b(); + timings.doSounds.startTiming(); // Spigot this.Z(); + timings.doSounds.stopTiming(); // Spigot + timings.doChunkGC.startTiming(); // Spigot this.getWorld().processChunkGC(); // CraftBukkit + timings.doChunkGC.stopTiming(); // Spigot } public BiomeMeta a(EnumCreatureType enumcreaturetype, int i, int j, int k) { @@ -306,10 +323,20 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate // CraftBukkit start // Iterator iterator = this.chunkTickList.iterator(); - for (long chunkCoord : this.chunkTickList.popAll()) { + // Spigot start + for (gnu.trove.iterator.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; + } + // Spigot end // ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator.next(); - int chunkX = LongHash.msw(chunkCoord); - int chunkZ = LongHash.lsw(chunkCoord); int k = chunkX * 16; int l = chunkZ * 16; @@ -400,6 +427,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate if (block.isTicking()) { ++i; + this.growthOdds = (iter.value() < 1) ? this.modifiedOdds : 100; // Spigot - grow fast if no players are in this chunk (value = player count) block.a(this, k2 + k, i3 + chunksection.getYPosition(), l2 + l, this.random); } } @@ -408,6 +436,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate this.methodProfiler.b(); } + // Spigot Start + if ( spigotConfig.clearChunksOnTick ) + { + chunkTickList.clear(); + } + // Spigot End } public boolean a(int i, int j, int k, Block block) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index b2c6ef4..55f5225 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -174,7 +174,18 @@ public class CraftChunk implements Chunk { } if (cs[i].getExtendedIdArray() != null) { /* If we've got extended IDs */ - byte[] extids = cs[i].getExtendedIdArray().a; + // Spigot start + if (cs[i].getExtendedIdArray().isTrivialArray()) { + int tval = cs[i].getExtendedIdArray().getTrivialArrayValue(); + if (tval != 0) { + tval = tval << 8; + for (int j = 0; j < 4096; j++) { + blockids[j] |= tval; + } + } + } else { + byte[] extids = cs[i].getExtendedIdArray().getValueArray(); + // Spigot end for (int j = 0; j < 2048; j++) { short b = (short) (extids[j] & 0xFF); @@ -186,21 +197,42 @@ public class CraftChunk implements Chunk { blockids[j<<1] |= (b & 0x0F) << 8; blockids[(j<<1)+1] |= (b & 0xF0) << 4; } + } // Spigot } sectionBlockIDs[i] = blockids; /* Get block data nibbles */ - sectionBlockData[i] = new byte[2048]; - System.arraycopy(cs[i].getDataArray().a, 0, sectionBlockData[i], 0, 2048); + // Spigot start + if (cs[i].getDataArray().isTrivialArray() && (cs[i].getDataArray().getTrivialArrayValue() == 0)) { + sectionBlockData[i] = emptyData; + } else { + sectionBlockData[i] = new byte[2048]; + cs[i].getDataArray().copyToByteArray(sectionBlockData[i], 0); + } if (cs[i].getSkyLightArray() == null) { sectionSkyLights[i] = emptyData; + } + else if (cs[i].getSkyLightArray().isTrivialArray()) { + if (cs[i].getSkyLightArray().getTrivialArrayValue() == 0) { + sectionSkyLights[i] = emptyData; + } else if (cs[i].getSkyLightArray().getTrivialArrayValue() == 15) { + sectionSkyLights[i] = emptySkyLight; + } else { + sectionSkyLights[i] = new byte[2048]; + cs[i].getSkyLightArray().copyToByteArray(sectionSkyLights[i], 0); + } } else { sectionSkyLights[i] = new byte[2048]; - System.arraycopy(cs[i].getSkyLightArray().a, 0, sectionSkyLights[i], 0, 2048); + cs[i].getSkyLightArray().copyToByteArray(sectionSkyLights[i], 0); + } + if (cs[i].getEmittedLightArray().isTrivialArray() && (cs[i].getEmittedLightArray().getTrivialArrayValue() == 0)) { + sectionEmitLights[i] = emptyData; + } else { + sectionEmitLights[i] = new byte[2048]; + cs[i].getEmittedLightArray().copyToByteArray(sectionEmitLights[i], 0); } - sectionEmitLights[i] = new byte[2048]; - System.arraycopy(cs[i].getEmittedLightArray().a, 0, sectionEmitLights[i], 0, 2048); + // Spigot end } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java index 7de0de5..7eca388 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java @@ -55,6 +55,8 @@ public class CraftEffect { Validate.isTrue(((Material) data).isBlock(), "Material is not a block!"); datavalue = ((Material) data).getId(); break; + case ITEM_BREAK: + datavalue = ((Material) data).getId(); default: datavalue = 0; } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index d27ab8c..b482592 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -41,6 +41,8 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.MobEffectList; import net.minecraft.server.PropertyManager; import net.minecraft.server.ServerCommand; +import net.minecraft.server.RegionFile; +import net.minecraft.server.RegionFileCache; import net.minecraft.server.ServerNBTManager; import net.minecraft.server.WorldLoaderServer; import net.minecraft.server.WorldManager; @@ -225,14 +227,16 @@ public final class CraftServer implements Server { loadIcon(); 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); // Spigot 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); - loadPlugins(); - enablePlugins(PluginLoadOrder.STARTUP); + // Spigot Start - Moved to old location of new DedicatedPlayerList in DedicatedServer + // loadPlugins(); + // enablePlugins(PluginLoadOrder.STARTUP); + // Spigot End } private File getConfigFile() { @@ -543,12 +547,14 @@ public final class CraftServer implements Server { if (commandMap.dispatch(sender, commandLine)) { return true; } - - if (sender instanceof Player) { - sender.sendMessage("Unknown command. Type \"/help\" for help."); - } else { - sender.sendMessage("Unknown command. Type \"help\" for help."); + // Spigot Start - Try vanilla commands + if ( org.spigotmc.VanillaCommandWrapper.dispatch( sender, commandLine ) != -1 ) + { + return true; } + // Spigot End + + sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage); return false; } @@ -582,6 +588,7 @@ public final class CraftServer implements Server { playerList.getIPBans().load(); playerList.getNameBans().load(); + org.spigotmc.SpigotConfig.init(); // Spigot for (WorldServer world : console.worlds) { world.difficulty = difficulty; world.setSpawnFlags(monsters, animals); @@ -596,11 +603,13 @@ public final class CraftServer implements Server { } else { world.ticksPerMonsterSpawns = this.getTicksPerMonsterSpawns(); } + world.spigotConfig.init(); // Spigot } pluginManager.clearPlugins(); commandMap.clearCommands(); resetRecipes(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot int pollCount = 0; @@ -845,6 +854,30 @@ public final class CraftServer implements Server { worlds.remove(world.getName().toLowerCase()); console.worlds.remove(console.worlds.indexOf(handle)); + File parentFolder = world.getWorldFolder().getAbsoluteFile(); + + // Synchronized because access to RegionFileCache.a is guarded by this lock. + synchronized (RegionFileCache.class) { + // RegionFileCache.a should be RegionFileCache.cache + Iterator> i = RegionFileCache.a.entrySet().iterator(); + while(i.hasNext()) { + Map.Entry entry = i.next(); + File child = entry.getKey().getAbsoluteFile(); + while (child != null) { + if (child.equals(parentFolder)) { + i.remove(); + try { + entry.getValue().c(); // Should be RegionFile.close(); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, null, ex); + } + break; + } + child = child.getParentFile(); + } + } + } + return true; } @@ -1103,7 +1136,7 @@ public final class CraftServer implements Server { } public OfflinePlayer getOfflinePlayer(String name) { - return getOfflinePlayer(name, true); + return getOfflinePlayer(name, false); // Spigot } public OfflinePlayer getOfflinePlayer(String name, boolean search) { @@ -1368,15 +1401,34 @@ public final class CraftServer implements Server { } public List tabCompleteCommand(Player player, String message) { - List completions = null; + // Spigot Start + if ( !org.spigotmc.SpigotConfig.tabComplete && !message.contains( " " ) ) + { + return ImmutableList.of(); + } + // Spigot End + + // Spigot Start + List completions = new ArrayList(); try { - completions = getCommandMap().tabComplete(player, message.substring(1)); + message = message.substring( 1 ); + List bukkitCompletions = getCommandMap().tabComplete( player, message ); + if ( bukkitCompletions != null ) + { + completions.addAll( bukkitCompletions ); + } + List vanillaCompletions = org.spigotmc.VanillaCommandWrapper.complete( player, message ); + if ( vanillaCompletions != null ) + { + completions.addAll( vanillaCompletions ); + } + // Spigot End } 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); } - return completions == null ? ImmutableList.of() : completions; + return completions; // Spigot } public List tabCompleteChat(Player player, String message) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 2c1fbb5..948e81d 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -159,6 +159,7 @@ public class CraftWorld implements World { } public boolean unloadChunkRequest(int x, int z, boolean safe) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous chunk unload!"); // Spigot if (safe && isChunkInUse(x, z)) { return false; } @@ -169,6 +170,7 @@ public class CraftWorld implements World { } public boolean unloadChunk(int x, int z, boolean save, boolean safe) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous chunk unload!"); // Spigot if (safe && isChunkInUse(x, z)) { return false; } @@ -236,6 +238,7 @@ public class CraftWorld implements World { } public boolean loadChunk(int x, int z, boolean generate) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous chunk load!"); // Spigot chunkLoadCount++; if (generate) { // Use the default variant of loadChunk when generate == true. @@ -776,28 +779,18 @@ public class CraftWorld implements World { Validate.isTrue(effect.getData() == null, "Wrong kind of data for this effect!"); } - int datavalue = data == null ? 0 : CraftEffect.getDataValue(effect, data); - playEffect(loc, effect, datavalue, radius); + if (data != null && data.getClass().equals( org.bukkit.material.MaterialData.class )) { + org.bukkit.material.MaterialData materialData = (org.bukkit.material.MaterialData) data; + Validate.isTrue( materialData.getItemType().isBlock(), "Material must be block" ); + spigot().playEffect( loc, effect, materialData.getItemType().getId(), materialData.getData(), 0, 0, 0, 1, 1, radius ); + } else { + int dataValue = data == null ? 0 : CraftEffect.getDataValue( effect, data ); + playEffect( loc, effect, dataValue, radius ); + } } public void playEffect(Location location, Effect effect, int data, int radius) { - Validate.notNull(location, "Location cannot be null"); - Validate.notNull(effect, "Effect cannot be null"); - Validate.notNull(location.getWorld(), "World cannot be null"); - int packetData = effect.getId(); - PacketPlayOutWorldEvent packet = new PacketPlayOutWorldEvent(packetData, location.getBlockX(), location.getBlockY(), location.getBlockZ(), data, false); - int distance; - radius *= radius; - - for (Player player : getPlayers()) { - if (((CraftPlayer) player).getHandle().playerConnection == null) continue; - if (!location.getWorld().equals(player.getWorld())) continue; - - distance = (int) player.getLocation().distanceSquared(location); - if (distance <= radius) { - ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); - } - } + spigot().playEffect( location, effect, data, 0, 0, 0, 0, 1, 1, radius ); } public T spawn(Location location, Class clazz) throws IllegalArgumentException { @@ -1280,4 +1273,64 @@ public class CraftWorld implements World { cps.queueUnload(chunk.locX, chunk.locZ); } } + // Spigot start + private final Spigot spigot = new Spigot() + { + @Override + public void playEffect( Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius ) + { + Validate.notNull( location, "Location cannot be null" ); + Validate.notNull( effect, "Effect cannot be null" ); + Validate.notNull( location.getWorld(), "World cannot be null" ); + Packet packet; + if ( effect.getType() != Effect.Type.PARTICLE ) + { + int packetData = effect.getId(); + packet = new PacketPlayOutWorldEvent( packetData, location.getBlockX(), location.getBlockY(), location.getBlockZ(), id, false ); + } else + { + StringBuilder particleFullName = new StringBuilder(); + particleFullName.append( effect.getName() ); + if ( effect.getData() != null && ( effect.getData().equals( Material.class ) || effect.getData().equals( org.bukkit.material.MaterialData.class ) ) ) + { + particleFullName.append( '_' ).append( id ); + } + if ( effect.getData() != null && effect.getData().equals( org.bukkit.material.MaterialData.class ) ) + { + particleFullName.append( '_' ).append( data ); + } + packet = new PacketPlayOutWorldParticles( particleFullName.toString(), (float) location.getX(), (float) location.getY(), (float) location.getZ(), offsetX, offsetY, offsetZ, speed, particleCount ); + } + int distance; + radius *= radius; + for ( Player player : getPlayers() ) + { + if ( ( (CraftPlayer) player ).getHandle().playerConnection == null ) + { + continue; + } + if ( !location.getWorld().equals( player.getWorld() ) ) + { + continue; + } + distance = (int) player.getLocation().distanceSquared( location ); + if ( distance <= radius ) + { + ( (CraftPlayer) player ).getHandle().playerConnection.sendPacket( packet ); + } + } + } + + @Override + public void playEffect( Location location, Effect effect ) + { + CraftWorld.this.playEffect( location, effect, 0 ); + } + }; + + public Spigot spigot() + { + return spigot; + } + // Spigot end } diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index 07c26f1..246e641 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -149,6 +149,22 @@ public class Main { useConsole = false; } + // Spigot Start + int maxPermGen = 0; // In kb + for ( String s : java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments() ) + { + if ( s.startsWith( "-XX:MaxPermSize" ) ) + { + maxPermGen = Integer.parseInt( s.replaceAll( "[^\\d]", "" ) ); + maxPermGen <<= 10 * ("kmg".indexOf( Character.toLowerCase( s.charAt( s.length() - 1 ) ) ) ); + } + } + if ( maxPermGen < (128<<10) ) // 128mb + { + System.out.println( "Warning, your max perm gen size is not set or less than 128mb. It is recommended you restart Java with the following argument: -XX:MaxPermSize=128M" ); + System.out.println( "Please see http://www.spigotmc.org/wiki/changing-permgen-size/ for more details and more in-depth instructions." ); + } + // Spigot End System.out.println("Loading libraries, please wait..."); MinecraftServer.main(options); } catch (Throwable t) { diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java new file mode 100644 index 0000000..541dfe4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java @@ -0,0 +1,128 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.server.*; +import org.spigotmc.CustomTimingsHandler; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import org.bukkit.craftbukkit.scheduler.CraftTask; + +public class SpigotTimings { + + public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick"); + public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List"); + public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Player Tick"); + public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); + public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); + public static final CustomTimingsHandler chunkIOTickTimer = new CustomTimingsHandler("ChunkIOTick"); + public static final CustomTimingsHandler syncChunkLoadTimer = new CustomTimingsHandler("syncChunkLoad"); + + public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove"); + public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity"); + public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity"); + public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity"); + + public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick"); + public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI"); + public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision"); + public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove"); + public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest"); + + public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand"); + + public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck"); + public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive"); + + public static final HashMap entityTypeTimingMap = new HashMap(); + public static final HashMap tileEntityTypeTimingMap = new HashMap(); + public static final HashMap pluginTaskTimingMap = new HashMap(); + + /** + * Gets a timer associated with a plugins tasks. + * @param task + * @param period + * @return + */ + public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) { + String plugin = task.getOwner().getDescription().getFullName(); + String name = "Task: " + plugin + " Runnable: " + ( (CraftTask) task ).getTaskClass().getSimpleName(); + if (period > 0) { + name += "(interval:" + period +")"; + } else { + name += "(Single)"; + } + CustomTimingsHandler result = pluginTaskTimingMap.get(name); + if (result == null) { + result = new CustomTimingsHandler(name); + pluginTaskTimingMap.put(name, result); + } + return result; + } + + /** + * Get a named timer for the specified entity type to track type specific timings. + * @param entity + * @return + */ + public static CustomTimingsHandler getEntityTimings(Entity entity) { + String entityType = entity.getClass().getSimpleName(); + CustomTimingsHandler result = entityTypeTimingMap.get(entityType); + if (result == null) { + result = new CustomTimingsHandler("** tickEntity - " + entityType, activatedEntityTimer); + entityTypeTimingMap.put(entityType, result); + } + return result; + } + + /** + * Get a named timer for the specified tile entity type to track type specific timings. + * @param entity + * @return + */ + public static CustomTimingsHandler getTileEntityTimings(TileEntity entity) { + String entityType = entity.getClass().getSimpleName(); + CustomTimingsHandler result = tileEntityTypeTimingMap.get(entityType); + if (result == null) { + result = new CustomTimingsHandler("** tickTileEntity - " + entityType, tickTileEntityTimer); + tileEntityTypeTimingMap.put(entityType, result); + } + return result; + } + + /** + * Set of timers per world, to track world specific timings. + */ + public static class WorldTimingsHandler { + public final CustomTimingsHandler mobSpawn; + public final CustomTimingsHandler doChunkUnload; + public final CustomTimingsHandler doPortalForcer; + public final CustomTimingsHandler doTickPending; + public final CustomTimingsHandler doTickTiles; + public final CustomTimingsHandler doVillages; + public final CustomTimingsHandler doChunkMap; + public final CustomTimingsHandler doChunkGC; + public final CustomTimingsHandler doSounds; + public final CustomTimingsHandler entityTick; + public final CustomTimingsHandler tileEntityTick; + public final CustomTimingsHandler tileEntityPending; + public final CustomTimingsHandler tracker; + + public WorldTimingsHandler(World server) { + String name = server.worldData.getName() +" - "; + + mobSpawn = new CustomTimingsHandler(name + "mobSpawn"); + doChunkUnload = new CustomTimingsHandler(name + "doChunkUnload"); + doTickPending = new CustomTimingsHandler(name + "doTickPending"); + doTickTiles = new CustomTimingsHandler(name + "doTickTiles"); + doVillages = new CustomTimingsHandler(name + "doVillages"); + doChunkMap = new CustomTimingsHandler(name + "doChunkMap"); + doSounds = new CustomTimingsHandler(name + "doSounds"); + doChunkGC = new CustomTimingsHandler(name + "doChunkGC"); + doPortalForcer = new CustomTimingsHandler(name + "doPortalForcer"); + entityTick = new CustomTimingsHandler(name + "entityTick"); + tileEntityTick = new CustomTimingsHandler(name + "tileEntityTick"); + tileEntityPending = new CustomTimingsHandler(name + "tileEntityPending"); + tracker = new CustomTimingsHandler(name + "tracker"); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java index 1647100..43c4434 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java @@ -14,6 +14,12 @@ public class CraftSign extends CraftBlockState implements Sign { CraftWorld world = (CraftWorld) block.getWorld(); sign = (TileEntitySign) world.getTileEntityAt(getX(), getY(), getZ()); + // Spigot start + if (sign == null) { + lines = new String[]{"", "", "", ""}; + return; + } + // Spigot end lines = new String[sign.lines.length]; System.arraycopy(sign.lines, 0, lines, 0, lines.length); } @@ -34,7 +40,7 @@ public class CraftSign extends CraftBlockState implements Sign { public boolean update(boolean force, boolean applyPhysics) { boolean result = super.update(force, applyPhysics); - if (result) { + if (result && sign != null) { // Spigot, add null check for(int i = 0; i < 4; i++) { if(lines[i] != null) { sign.lines[i] = lines[i]; diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java index afcf764..92fbc4f 100644 --- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java @@ -5,6 +5,7 @@ import net.minecraft.server.ChunkProviderServer; import net.minecraft.server.ChunkRegionLoader; import net.minecraft.server.World; import org.bukkit.craftbukkit.util.AsynchronousExecutor; +import org.bukkit.craftbukkit.util.LongHash; public class ChunkIOExecutor { static final int BASE_THREADS = 1; @@ -12,12 +13,12 @@ public class ChunkIOExecutor { private static final AsynchronousExecutor instance = new AsynchronousExecutor(new ChunkIOProvider(), BASE_THREADS); - public static Chunk syncChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z) { - return instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider)); + public static void waitForChunkLoad(World world, int x, int z) { + instance.get(new QueuedChunk(LongHash.toLong(x, z), null, world, null)); } public static void queueChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) { - instance.add(new QueuedChunk(x, z, loader, world, provider), runnable); + instance.add(new QueuedChunk(LongHash.toLong(x, z), loader, world, provider), runnable); } public static void adjustPoolSize(int players) { diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java index 8feadd1..b9de12d 100644 --- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java @@ -16,7 +16,7 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider channels = new HashSet(); - private final Map hiddenPlayers = new MapMaker().softValues().makeMap(); + private final Map hiddenPlayers = new MapMaker().weakValues().makeMap(); // Spigot - soft -> weak private int hash = 0; private double health = 20; private boolean scaledHealth = false; @@ -230,6 +230,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void kickPlayer(String message) { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous player kick!"); // Spigot if (getHandle().playerConnection == null) return; getHandle().playerConnection.disconnect(message == null ? "" : message); @@ -429,9 +430,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return false; } - if (entity.vehicle != null || entity.passenger != null) { - return false; - } + // Spigot Start + // if (entity.vehicle != null || entity.passenger != null) { + // return false; + // } + // Spigot End // From = Players current Location Location from = this.getLocation(); @@ -445,6 +448,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { if (event.isCancelled()) { return false; } + + // Spigot Start + eject(); + leaveVehicle(); + // Spigot End // Update the From Location from = event.getFrom(); @@ -1130,7 +1138,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void setFlySpeed(float value) { validateSpeed(value); EntityPlayer player = getHandle(); - player.abilities.flySpeed = value / 2f; + player.abilities.flySpeed = Math.max( value, 0.0001f ) / 2f; // Spigot player.updateAbilities(); } @@ -1138,7 +1146,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void setWalkSpeed(float value) { validateSpeed(value); EntityPlayer player = getHandle(); - player.abilities.walkSpeed = value / 2f; + player.abilities.walkSpeed = Math.max( value, 0.0001f ) / 2f; // Spigot player.updateAbilities(); } @@ -1186,7 +1194,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { throw new IllegalStateException("Cannot set scoreboard yet"); } if (playerConnection.isDisconnected()) { - throw new IllegalStateException("Cannot set scoreboard for invalid CraftPlayer"); + // throw new IllegalStateException("Cannot set scoreboard for invalid CraftPlayer"); // Spigot - remove this as Mojang's semi asynchronous Netty implementation can lead to races } this.server.getScoreboardManager().setPlayerBoard(this, scoreboard); @@ -1254,4 +1262,49 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } collection.add(new AttributeModifiable(getHandle().bc(), (new AttributeRanged("generic.maxHealth", scaledHealth ? healthScale : getMaxHealth(), 0.0D, Float.MAX_VALUE)).a("Max Health").a(true))); } + + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { + + @Override + public InetSocketAddress getRawAddress() + { + return (InetSocketAddress) getHandle().playerConnection.networkManager.getRawAddress(); + } + + @Override + public boolean getCollidesWithEntities() + { + return getHandle().collidesWithEntities; + } + + @Override + public void setCollidesWithEntities(boolean collides) + { + getHandle().collidesWithEntities = collides; + getHandle().l = collides; // First boolean of Entity + } + + @Override + public void respawn() + { + if ( getHealth() <= 0 && isOnline() ) + { + server.getServer().getPlayerList().moveToWorld( getHandle(), 0, false ); + } + } + + @Override + public String getLocale() + { + return getHandle().locale; + } + }; + + public Player.Spigot spigot() + { + return spigot; + } + // Spigot end } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index a91ffb1..161506a 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -36,6 +36,7 @@ import org.bukkit.Statistic.Type; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; +import org.bukkit.block.CreatureSpawner; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftStatistic; import org.bukkit.craftbukkit.CraftWorld; @@ -120,6 +121,22 @@ public class CraftEventFactory { } /** + * Mob spawner event + */ + public static SpawnerSpawnEvent callSpawnerSpawnEvent(Entity spawnee, int spawnerX, int spawnerY, int spawnerZ) { + org.bukkit.craftbukkit.entity.CraftEntity entity = spawnee.getBukkitEntity(); + BlockState state = entity.getWorld().getBlockAt(spawnerX, spawnerY, spawnerZ).getState(); + + if (!(state instanceof CreatureSpawner)) { + state = null; + } + + SpawnerSpawnEvent event = new SpawnerSpawnEvent(entity, (CreatureSpawner) state); + entity.getServer().getPluginManager().callEvent(event); + return event; + } + + /** * Bucket methods */ public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(EntityHuman who, int clickedX, int clickedY, int clickedZ, int clickedFace, ItemStack itemInHand) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java index a91d81a..46a1d38 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java @@ -1,7 +1,9 @@ package org.bukkit.craftbukkit.inventory; import net.minecraft.server.IInventory; + import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; public class CraftInventoryAnvil extends CraftInventory implements AnvilInventory { private final IInventory resultInventory; @@ -20,6 +22,26 @@ public class CraftInventoryAnvil extends CraftInventory implements AnvilInventor } @Override + public ItemStack getItem(int slot) { + if (slot < getIngredientsInventory().getSize()) { + net.minecraft.server.ItemStack item = getIngredientsInventory().getItem(slot); + return item == null ? null : CraftItemStack.asCraftMirror(item); + } else { + net.minecraft.server.ItemStack item = getResultInventory().getItem(slot - getIngredientsInventory().getSize()); + return item == null ? null : CraftItemStack.asCraftMirror(item); + } + } + + @Override + public void setItem(int index, ItemStack item) { + if (index < getIngredientsInventory().getSize()) { + getIngredientsInventory().setItem(index, (item == null ? null : CraftItemStack.asNMSCopy(item))); + } else { + getResultInventory().setItem((index - getIngredientsInventory().getSize()), (item == null ? null : CraftItemStack.asNMSCopy(item))); + } + } + + @Override public int getSize() { return getResultInventory().getSize() + getIngredientsInventory().getSize(); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java index 0f7da6b..5a409ae 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java @@ -145,7 +145,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { case BURST: return 4; default: - throw new AssertionError(type); + throw new IllegalStateException(type.toString()); // Spigot } } @@ -162,7 +162,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { case 4: return Type.BURST; default: - throw new AssertionError(nbt); + throw new IllegalStateException(Integer.toString(nbt)); // Spigot } } diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java index 1a150d9..c9f0027 100644 --- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java +++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java @@ -18,7 +18,7 @@ import org.bukkit.map.MapView; public final class CraftMapView implements MapView { private final Map renderCache = new HashMap(); - private final List renderers = new ArrayList(); + public final List renderers = new ArrayList(); // Spigot private final Map> canvases = new HashMap>(); protected final WorldMap worldMap; diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 84dcfcc..a30f217 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -71,7 +71,7 @@ public class CraftScheduler implements BukkitScheduler { */ private final ConcurrentHashMap runners = new ConcurrentHashMap(); private volatile int currentTick = -1; - private final Executor executor = Executors.newCachedThreadPool(); + private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; private CraftAsyncDebugger debugTail = debugHead; private static final int RECENT_TICKS; diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java index 55db3ff..7d294c0 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -1,11 +1,13 @@ package org.bukkit.craftbukkit.scheduler; import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.SpigotTimings; // Spigot +import org.spigotmc.CustomTimingsHandler; // Spigot import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitTask; -class CraftTask implements BukkitTask, Runnable { +public class CraftTask implements BukkitTask, Runnable { // Spigot private volatile CraftTask next = null; /** @@ -22,6 +24,7 @@ class CraftTask implements BukkitTask, Runnable { private final Plugin plugin; private final int id; + CustomTimingsHandler timings = null; // Spigot CraftTask() { this(null, null, -1, -1); } @@ -50,7 +53,22 @@ class CraftTask implements BukkitTask, Runnable { } public void run() { + // Spigot start - Wrap custom timings on Tasks + if (!Bukkit.getServer().getPluginManager().useTimings()) { + task.run(); + return; + } + if (timings == null && this.getOwner() != null && this.isSync()) { + timings = SpigotTimings.getPluginTaskTimings(this, period); + } + if (timings != null) { + timings.startTiming(); + } task.run(); + if (timings != null) { + timings.stopTiming(); + } + // Spigot end } long getPeriod() { @@ -77,7 +95,7 @@ class CraftTask implements BukkitTask, Runnable { this.next = next; } - Class getTaskClass() { + public Class getTaskClass() { // Spigot return task.getClass(); } diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java index 84091a4..2f30d66 100644 --- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java @@ -42,6 +42,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { } public CraftScoreboard getNewScoreboard() { + if (Thread.currentThread() != MinecraftServer.getServer().primaryThread) throw new IllegalStateException("Asynchronous scoreboard creation"); // Spigot CraftScoreboard scoreboard = new CraftScoreboard(new ScoreboardServer(server)); scoreboards.add(scoreboard); return scoreboard; diff --git a/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java index 7e9efb8..8f3e80e 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java +++ b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java @@ -24,7 +24,7 @@ import org.apache.commons.lang.Validate; * @param The type of object you provide. This is created in stage 1, and passed to stage 2, 3, and returned if get() is called. * @param The type of callback you provide. You may register many of these to be passed to the provider in stage 3, one at a time. * @param A type of exception you may throw and expect to be handled by the main thread - * @author Wesley Wolfe (c) 2012, 2014 + * @author Wesley Wolfe (c) 2012 */ public final class AsynchronousExecutor { @@ -208,7 +208,6 @@ public final class AsynchronousExecutor { /** * Adds a callback to the parameter provided, adding parameter to the queue if needed. - *

* This should always be synchronous. */ public void add(P parameter, C callback) { @@ -222,7 +221,6 @@ public final class AsynchronousExecutor { /** * This method attempts to skip the waiting period for said parameter. - *

* This should always be synchronous. * @throws IllegalStateException if the parameter is not in the queue anymore, or sometimes if called from asynchronous thread */ @@ -238,14 +236,14 @@ public final class AsynchronousExecutor { * Processes a parameter as if it was in the queue, without ever passing to another thread. */ public T getSkipQueue(P parameter) throws E { - return skipQueue(parameter); + return skipQueue(provider, parameter); } /** * Processes a parameter as if it was in the queue, without ever passing to another thread. */ public T getSkipQueue(P parameter, C callback) throws E { - final T object = skipQueue(parameter); + final T object = skipQueue(provider, parameter); provider.callStage3(parameter, object, callback); return object; } @@ -255,7 +253,7 @@ public final class AsynchronousExecutor { */ public T getSkipQueue(P parameter, C...callbacks) throws E { final CallBackProvider provider = this.provider; - final T object = skipQueue(parameter); + final T object = skipQueue(provider, parameter); for (C callback : callbacks) { provider.callStage3(parameter, object, callback); } @@ -267,18 +265,14 @@ public final class AsynchronousExecutor { */ public T getSkipQueue(P parameter, Iterable callbacks) throws E { final CallBackProvider provider = this.provider; - final T object = skipQueue(parameter); + final T object = skipQueue(provider, parameter); for (C callback : callbacks) { provider.callStage3(parameter, object, callback); } return object; } - private T skipQueue(P parameter) throws E { - Task task = tasks.get(parameter); - if (task != null) { - return task.get(); - } + private static T skipQueue(CallBackProvider provider, P parameter) throws E { T object = provider.callStage1(parameter); provider.callStage2(parameter, object); return object; diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java index 66368f4..f4de71c 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java @@ -19,7 +19,7 @@ import com.google.common.collect.ImmutableMap.Builder; public final class CraftChatMessage { private static class StringMessage { private static final Map formatMap; - private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-or])|(\\n)|(?:(https?://[^ ][^ ]*?)(?=[\\.\\?!,;:]?(?:[ \\n]|$)))", Pattern.CASE_INSENSITIVE); + private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-or])|(\\n)|((?:(?:https?)://)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[ \\n]|$))))", Pattern.CASE_INSENSITIVE); static { Builder builder = ImmutableMap.builder(); @@ -85,6 +85,9 @@ public final class CraftChatMessage { currentChatComponent = null; break; case 3: + if ( !( match.startsWith( "http://" ) || match.startsWith( "https://" ) ) ) { + match = "http://" + match; + } modifier.a(new ChatClickable(EnumClickAction.OPEN_URL, match)); // Should be setChatClickable appendNewComponent(matcher.end(groupId)); modifier.a((ChatClickable) null); @@ -96,7 +99,7 @@ public final class CraftChatMessage { appendNewComponent(message.length()); } - output = list.toArray(new IChatBaseComponent[0]); + output = list.toArray(new IChatBaseComponent[list.size()]); } private void appendNewComponent(int index) { diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHash.java b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java index 691cafd..9d54472 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHash.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java @@ -10,6 +10,6 @@ public class LongHash { } public static int lsw(long l) { - return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE; + return (int) (l) + Integer.MIN_VALUE; // Spigot - remove redundant & } } diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java index 22c96c5..7f659b7 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java @@ -31,6 +31,7 @@ public class LongHashSet { private int elements; private long[] values; private int modCount; + private org.spigotmc.FlatMap flat = new org.spigotmc.FlatMap(); // Spigot public LongHashSet() { this(INITIAL_SIZE); @@ -56,10 +57,30 @@ public class LongHashSet { } public boolean contains(int msw, int lsw) { + // Spigot start + if ( elements == 0 ) + { + return false; + } + if ( flat.contains( msw, lsw ) ) + { + return true; + } + // Spigot end return contains(LongHash.toLong(msw, lsw)); } public boolean contains(long value) { + // Spigot start + if ( elements == 0 ) + { + return false; + } + if ( flat.contains( value ) ) + { + return true; + } + // Spigot end int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; @@ -82,6 +103,7 @@ public class LongHashSet { } public boolean add(long value) { + flat.put( value, Boolean.TRUE ); // Spigot int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; @@ -125,10 +147,18 @@ public class LongHashSet { } public void remove(int msw, int lsw) { - remove(LongHash.toLong(msw, lsw)); + // Spigot start + flat.remove(msw, lsw); + remove0(LongHash.toLong(msw, lsw)); } public boolean remove(long value) { + flat.remove(value); + return remove0(value); + } + + private boolean remove0(long value) { + // Spigot end int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; @@ -161,6 +191,7 @@ public class LongHashSet { freeEntries = values.length; modCount++; + flat = new org.spigotmc.FlatMap(); } public long[] toArray() { diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java index 01861cc..2e5b436 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java @@ -28,6 +28,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { private transient V[][] values; private transient int modCount; private transient int size; + private transient org.spigotmc.FlatMap flat = new org.spigotmc.FlatMap(); // Spigot public LongObjectHashMap() { initialize(); @@ -61,6 +62,17 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V get(long key) { + // Spigot start + if ( size == 0 ) + { + return null; + } + V val = flat.get( key ); + if ( val != null ) + { + return val; + } + // Spigot end int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] inner = keys[index]; if (inner == null) return null; @@ -78,6 +90,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V put(long key, V value) { + flat.put(key, value); // Spigot int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] innerKeys = keys[index]; V[] innerValues = values[index]; @@ -124,6 +137,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V remove(long key) { + flat.remove(key); // Spigot int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] inner = keys[index]; if (inner == null) { @@ -174,6 +188,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { size = 0; Arrays.fill(keys, null); Arrays.fill(values, null); + flat = new org.spigotmc.FlatMap(); } public Set keySet() { diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java index f905d17..9304637 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -11,7 +11,7 @@ public final class Versioning { public static String getBukkitVersion() { String result = "Unknown-Version"; - InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.bukkit/bukkit/pom.properties"); + InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.spigotmc/spigot-api/pom.properties"); Properties properties = new Properties(); if (stream != null) { diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java new file mode 100644 index 0000000..db4c927 --- /dev/null +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -0,0 +1,296 @@ +package org.spigotmc; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.server.AxisAlignedBB; +import net.minecraft.server.Chunk; +import net.minecraft.server.Entity; +import net.minecraft.server.EntityAmbient; +import net.minecraft.server.EntityAnimal; +import net.minecraft.server.EntityArrow; +import net.minecraft.server.EntityComplexPart; +import net.minecraft.server.EntityCreature; +import net.minecraft.server.EntityEnderCrystal; +import net.minecraft.server.EntityEnderDragon; +import net.minecraft.server.EntityFireball; +import net.minecraft.server.EntityFireworks; +import net.minecraft.server.EntityHuman; +import net.minecraft.server.EntityLiving; +import net.minecraft.server.EntityMonster; +import net.minecraft.server.EntityProjectile; +import net.minecraft.server.EntitySheep; +import net.minecraft.server.EntitySlime; +import net.minecraft.server.EntityTNTPrimed; +import net.minecraft.server.EntityVillager; +import net.minecraft.server.EntityWeather; +import net.minecraft.server.EntityWither; +import net.minecraft.server.MathHelper; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.SpigotTimings; + +public class ActivationRange +{ + + static AxisAlignedBB maxBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB miscBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB animalBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB monsterBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 ); + + /** + * Initializes an entities type on construction to specify what group this + * entity is in for activation ranges. + * + * @param entity + * @return group id + */ + public static byte initializeEntityActivationType(Entity entity) + { + if ( entity instanceof EntityMonster || entity instanceof EntitySlime ) + { + return 1; // Monster + } else if ( entity instanceof EntityCreature || entity instanceof EntityAmbient ) + { + return 2; // Animal + } else + { + return 3; // Misc + } + } + + /** + * These entities are excluded from Activation range checks. + * + * @param entity + * @param world + * @return boolean If it should always tick. + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { + if ( ( entity.activationType == 3 && config.miscActivationRange == 0 ) + || ( entity.activationType == 2 && config.animalActivationRange == 0 ) + || ( entity.activationType == 1 && config.monsterActivationRange == 0 ) + || entity instanceof EntityHuman + || entity instanceof EntityProjectile + || entity instanceof EntityEnderDragon + || entity instanceof EntityComplexPart + || entity instanceof EntityWither + || entity instanceof EntityFireball + || entity instanceof EntityWeather + || entity instanceof EntityTNTPrimed + || entity instanceof EntityEnderCrystal + || entity instanceof EntityFireworks ) + { + return true; + } + + return false; + } + + /** + * Utility method to grow an AABB without creating a new AABB or touching + * the pool, so we can re-use ones we have. + * + * @param target + * @param source + * @param x + * @param y + * @param z + */ + public static void growBB(AxisAlignedBB target, AxisAlignedBB source, int x, int y, int z) + { + target.a = source.a - x; + target.b = source.b - y; + target.c = source.c - z; + target.d = source.d + x; + target.e = source.e + y; + target.f = source.f + z; + } + + /** + * Find what entities are in range of the players in the world and set + * active if in range. + * + * @param world + */ + public static void activateEntities(World world) + { + SpigotTimings.entityActivationCheckTimer.startTiming(); + final int miscActivationRange = world.spigotConfig.miscActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); + maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); + + for ( Entity player : new ArrayList( world.players ) ) + { + + player.activatedTick = MinecraftServer.currentTick; + growBB( maxBB, player.boundingBox, maxRange, 256, maxRange ); + growBB( miscBB, player.boundingBox, miscActivationRange, 256, miscActivationRange ); + growBB( animalBB, player.boundingBox, animalActivationRange, 256, animalActivationRange ); + growBB( monsterBB, player.boundingBox, monsterActivationRange, 256, monsterActivationRange ); + + int i = MathHelper.floor( maxBB.a / 16.0D ); + int j = MathHelper.floor( maxBB.d / 16.0D ); + int k = MathHelper.floor( maxBB.c / 16.0D ); + int l = MathHelper.floor( maxBB.f / 16.0D ); + + for ( int i1 = i; i1 <= j; ++i1 ) + { + for ( int j1 = k; j1 <= l; ++j1 ) + { + if ( world.getWorld().isChunkLoaded( i1, j1 ) ) + { + activateChunkEntities( world.getChunkAt( i1, j1 ) ); + } + } + } + } + SpigotTimings.entityActivationCheckTimer.stopTiming(); + } + + /** + * Checks for the activation state of all entities in this chunk. + * + * @param chunk + */ + private static void activateChunkEntities(Chunk chunk) + { + for ( List slice : chunk.entitySlices ) + { + for ( Entity entity : slice ) + { + if ( MinecraftServer.currentTick > entity.activatedTick ) + { + if ( entity.defaultActivationState ) + { + entity.activatedTick = MinecraftServer.currentTick; + continue; + } + switch ( entity.activationType ) + { + case 1: + if ( monsterBB.b( entity.boundingBox ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 2: + if ( animalBB.b( entity.boundingBox ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 3: + default: + if ( miscBB.b( entity.boundingBox ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + } + } + } + } + } + + /** + * If an entity is not in range, do some more checks to see if we should + * give it a shot. + * + * @param entity + * @return + */ + public static boolean checkEntityImmunities(Entity entity) + { + // quick checks. + if ( entity.inWater /* isInWater */ || entity.fireTicks > 0 ) + { + return true; + } + if ( !( entity instanceof EntityArrow ) ) + { + if ( !entity.onGround || entity.passenger != null + || entity.vehicle != null ) + { + return true; + } + } else if ( !( (EntityArrow) entity ).inGround ) + { + return true; + } + // special cases. + if ( entity instanceof EntityLiving ) + { + EntityLiving living = (EntityLiving) entity; + if ( living.attackTicks > 0 || living.hurtTicks > 0 || living.effects.size() > 0 ) + { + return true; + } + if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).target != null ) + { + return true; + } + if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).bY() /* Getter for first boolean */ ) + { + return true; + } + if ( entity instanceof EntityAnimal ) + { + EntityAnimal animal = (EntityAnimal) entity; + if ( animal.isBaby() || animal.cc() /*love*/ ) + { + return true; + } + if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).isSheared() ) + { + return true; + } + } + } + return false; + } + + /** + * Checks if the entity is active for this tick. + * + * @param entity + * @return + */ + public static boolean checkIfActive(Entity entity) + { + SpigotTimings.checkIfActiveTimer.startTiming(); + boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; + + // Should this entity tick? + if ( !isActive ) + { + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. + if ( checkEntityImmunities( entity ) ) + { + // Triggered some sort of immunity, give 20 full ticks before we check again. + entity.activatedTick = MinecraftServer.currentTick + 20; + } + isActive = true; + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. + } else if ( !entity.defaultActivationState && entity.ticksLived % 4 == 0 && !checkEntityImmunities( entity ) ) + { + isActive = false; + } + int x = MathHelper.floor( entity.locX ); + int z = MathHelper.floor( entity.locZ ); + // Make sure not on edge of unloaded chunk + if ( isActive && !entity.world.areChunksLoaded( x, 0, z, 16 ) ) + { + isActive = false; + } + SpigotTimings.checkIfActiveTimer.stopTiming(); + return isActive; + } +} diff --git a/src/main/java/org/spigotmc/AntiXray.java b/src/main/java/org/spigotmc/AntiXray.java new file mode 100644 index 0000000..30280d4 --- /dev/null +++ b/src/main/java/org/spigotmc/AntiXray.java @@ -0,0 +1,189 @@ +package org.spigotmc; + +import gnu.trove.set.TByteSet; +import gnu.trove.set.hash.TByteHashSet; +import net.minecraft.server.Block; +import net.minecraft.server.World; + +public class AntiXray +{ + + private static final CustomTimingsHandler update = new CustomTimingsHandler( "xray - update" ); + private static final CustomTimingsHandler obfuscate = new CustomTimingsHandler( "xray - obfuscate" ); + /*========================================================================*/ + // Used to keep track of which blocks to obfuscate + private final boolean[] obfuscateBlocks = new boolean[ Short.MAX_VALUE ]; + // Used to select a random replacement ore + private final byte[] replacementOres; + + public AntiXray(SpigotWorldConfig config) + { + // Set all listed blocks as true to be obfuscated + for ( int id : ( config.engineMode == 1 ) ? config.hiddenBlocks : config.replaceBlocks ) + { + obfuscateBlocks[id] = true; + } + + // For every block + TByteSet blocks = new TByteHashSet(); + for ( Integer i : config.hiddenBlocks ) + { + Block block = Block.e( i ); + // Check it exists and is not a tile entity + if ( block != null && block.isTileEntity() ) + { + continue; + } + // Add it to the set of replacement blocks + blocks.add( (byte) (int) i ); + } + // Bake it to a flat array of replacements + replacementOres = blocks.toArray(); + } + + /** + * Starts the timings handler, then updates all blocks within the set radius + * of the given coordinate, revealing them if they are hidden ores. + */ + public void updateNearbyBlocks(World world, int x, int y, int z) + { + if ( world.spigotConfig.antiXray ) + { + update.startTiming(); + updateNearbyBlocks( world, x, y, z, 2, false ); // 2 is the radius, we shouldn't change it as that would make it exponentially slower + update.stopTiming(); + } + } + + /** + * Removes all non exposed ores from the chunk buffer. + */ + public void obfuscate(int chunkX, int chunkZ, int bitmask, byte[] buffer, World world) + { + // If the world is marked as obfuscated + if ( world.spigotConfig.antiXray ) + { + obfuscate.startTiming(); + // Initial radius to search around for air + int initialRadius = 1; + // Which block in the buffer we are looking at, anywhere from 0 to 16^4 + int index = 0; + // The iterator marking which random ore we should use next + int randomOre = 0; + + // Chunk corner X and Z blocks + int startX = chunkX << 4; + int startZ = chunkZ << 4; + + // Chunks can have up to 16 sections + for ( int i = 0; i < 16; i++ ) + { + // If the bitmask indicates this chunk is sent... + if ( ( bitmask & 1 << i ) != 0 ) + { + // Work through all blocks in the chunk, y,z,x + for ( int y = 0; y < 16; y++ ) + { + for ( int z = 0; z < 16; z++ ) + { + for ( int x = 0; x < 16; x++ ) + { + // For some reason we can get too far ahead of ourselves (concurrent modification on bulk chunks?) so if we do, just abort and move on + if ( index >= buffer.length ) + { + continue; + } + // Grab the block ID in the buffer. + // TODO: extended IDs are not yet supported + int blockId = buffer[index] & 0xFF; + // Check if the block should be obfuscated + if ( obfuscateBlocks[blockId] ) + { + // TODO: Don't really understand this, but if radius is not 0 and the world isn't loaded, bail out + if ( initialRadius != 0 && !isLoaded( world, startX + x, ( i << 4 ) + y, startZ + z, initialRadius ) ) + { + continue; + } + // On the otherhand, if radius is 0, or the nearby blocks are all non air, we can obfuscate + if ( initialRadius == 0 || !hasTransparentBlockAdjacent( world, startX + x, ( i << 4 ) + y, startZ + z, initialRadius ) ) + { + switch ( world.spigotConfig.engineMode ) + { + case 1: + // Replace with stone + buffer[index] = 1; + break; + case 2: + // Replace with random ore. + if ( randomOre >= replacementOres.length ) + { + randomOre = 0; + } + buffer[index] = replacementOres[randomOre++]; + break; + } + } + } + + index++; + } + } + } + } + } + obfuscate.stopTiming(); + } + } + + private void updateNearbyBlocks(World world, int x, int y, int z, int radius, boolean updateSelf) + { + // If the block in question is loaded + if ( world.isLoaded( x, y, z ) ) + { + // Get block id + Block block = world.getType( x, y, z ); + + // See if it needs update + if ( updateSelf && obfuscateBlocks[Block.b( block )] ) + { + // Send the update + world.notify( x, y, z ); + } + + // Check other blocks for updates + if ( radius > 0 ) + { + updateNearbyBlocks( world, x + 1, y, z, radius - 1, true ); + updateNearbyBlocks( world, x - 1, y, z, radius - 1, true ); + updateNearbyBlocks( world, x, y + 1, z, radius - 1, true ); + updateNearbyBlocks( world, x, y - 1, z, radius - 1, true ); + updateNearbyBlocks( world, x, y, z + 1, radius - 1, true ); + updateNearbyBlocks( world, x, y, z - 1, radius - 1, true ); + } + } + } + + private static boolean isLoaded(World world, int x, int y, int z, int radius) + { + return world.isLoaded( x, y, z ) + || ( radius > 0 + && ( isLoaded( world, x + 1, y, z, radius - 1 ) + || isLoaded( world, x - 1, y, z, radius - 1 ) + || isLoaded( world, x, y + 1, z, radius - 1 ) + || isLoaded( world, x, y - 1, z, radius - 1 ) + || isLoaded( world, x, y, z + 1, radius - 1 ) + || isLoaded( world, x, y, z - 1, radius - 1 ) ) ); + } + + private static boolean hasTransparentBlockAdjacent(World world, int x, int y, int z, int radius) + { + return !world.getType( x, y, z ).r() /* isSolidBlock */ + || ( radius > 0 + && ( hasTransparentBlockAdjacent( world, x + 1, y, z, radius - 1 ) + || hasTransparentBlockAdjacent( world, x - 1, y, z, radius - 1 ) + || hasTransparentBlockAdjacent( world, x, y + 1, z, radius - 1 ) + || hasTransparentBlockAdjacent( world, x, y - 1, z, radius - 1 ) + || hasTransparentBlockAdjacent( world, x, y, z + 1, radius - 1 ) + || hasTransparentBlockAdjacent( world, x, y, z - 1, radius - 1 ) ) ); + } +} diff --git a/src/main/java/org/spigotmc/FlatMap.java b/src/main/java/org/spigotmc/FlatMap.java new file mode 100644 index 0000000..9416f6e --- /dev/null +++ b/src/main/java/org/spigotmc/FlatMap.java @@ -0,0 +1,64 @@ +package org.spigotmc; + +import org.bukkit.craftbukkit.util.LongHash; + +public class FlatMap +{ + + private static final int FLAT_LOOKUP_SIZE = 512; + private final Object[][] flatLookup = new Object[ FLAT_LOOKUP_SIZE * 2 ][ FLAT_LOOKUP_SIZE * 2 ]; + + public void put(long msw, long lsw, V value) + { + long acx = Math.abs( msw ); + long acz = Math.abs( lsw ); + if ( acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE ) + { + flatLookup[(int) ( msw + FLAT_LOOKUP_SIZE )][(int) ( lsw + FLAT_LOOKUP_SIZE )] = value; + } + } + + public void put(long key, V value) + { + put( LongHash.msw( key ), LongHash.lsw( key ), value ); + + } + + public void remove(long key) + { + put( key, null ); + } + + public void remove(long msw, long lsw) + { + put( msw, lsw, null ); + } + + public boolean contains(long msw, long lsw) + { + return get( msw, lsw ) != null; + } + + public boolean contains(long key) + { + return get( key ) != null; + } + + 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/spigotmc/Metrics.java b/src/main/java/org/spigotmc/Metrics.java new file mode 100644 index 0000000..f1690a2 --- /dev/null +++ b/src/main/java/org/spigotmc/Metrics.java @@ -0,0 +1,645 @@ +/* + * Copyright 2011-2013 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ +package org.spigotmc; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.scheduler.BukkitTask; + +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.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import net.minecraft.server.MinecraftServer; + +/** + *

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

+ * Public methods provided by this class:

+ * + * Graph createGraph(String name);
+ * void addCustomData(BukkitMetrics.Plotter plotter);
+ * void start();
+ *
+ */ +public class Metrics { + + /** + * The current revision number + */ + private final static int REVISION = 6; + /** + * 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 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 static final int PING_INTERVAL = 10; + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + /** + * The default graph, used for addCustomData when you don't want a specific graph + */ + private final Graph defaultGraph = new Graph("Default"); + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + /** + * The plugin configuration file + */ + private final File configurationFile; + /** + * Unique server id + */ + private final String guid; + /** + * Debug mode + */ + private final boolean debug; + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + /** + * The scheduled task + */ + private volatile Timer task = null; + + public Metrics() throws IOException { + // load the config + configurationFile = getConfigFile(); + configuration = YamlConfiguration.loadConfiguration(configurationFile); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + configuration.addDefault("debug", false); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://mcstats.org").copyDefaults(true); + configuration.save(configurationFile); + } + + // Load the guid then + guid = configuration.getString("guid"); + debug = configuration.getBoolean("debug", false); + } + + /** + * 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 The name of the graph + * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given + */ + public Graph createGraph(final String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + + // Construct the graph object + final Graph graph = new Graph(name); + + // Now we can add our graph + graphs.add(graph); + + // and return back + return graph; + } + + /** + * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend + * + * @param graph The name of the graph + */ + public void addGraph(final Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + + graphs.add(graph); + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter The plotter to use to plot custom data + */ + public void addCustomData(final 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. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (task != null) { + return true; + } + + // Begin hitting the server with glorious data + task = new Timer("Spigot Metrics Thread", true); + + task.scheduleAtFixedRate(new TimerTask() { + private boolean firstPost = true; + + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && task != null) { + task.cancel(); + task = null; + // Tell all plotters to stop gathering information. + for (Graph graph : graphs) { + graph.onOptOut(); + } + } + } + + // 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) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + } + }, 0, TimeUnit.MINUTES.toMillis(PING_INTERVAL)); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized (optOutLock) { + try { + // Reload the metrics file + configuration.load(getConfigFile()); + } catch (IOException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } catch (InvalidConfigurationException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } + return configuration.getBoolean("opt-out", false); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws java.io.IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } + + // Enable Task, if it is not running + if (task == null) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws java.io.IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (task != null) { + task.cancel(); + task = null; + } + } + } + + /** + * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use + // is to abuse the plugin object we already have + // plugin.getDataFolder() => base/plugins/PluginA/ + // pluginsFolder => base/plugins/ + // The base is not necessarily relative to the startup directory. + // File pluginsFolder = plugin.getDataFolder().getParentFile(); + + // return => base/plugins/PluginMetrics/config.yml + return new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "PluginMetrics"), "config.yml"); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(final boolean isPing) throws IOException { + // Server software specific section + String pluginName = "Spigot"; + boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled + String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"; + String serverVersion = Bukkit.getVersion(); + int playersOnline = Bukkit.getServer().getOnlinePlayers().length; + + // END server software specific section -- all code below does not use any code outside of this class / Java + + // Construct the post data + final StringBuilder data = new StringBuilder(); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc + data.append(encode("guid")).append('=').append(encode(guid)); + encodeDataPair(data, "version", pluginVersion); + encodeDataPair(data, "server", serverVersion); + encodeDataPair(data, "players", Integer.toString(playersOnline)); + encodeDataPair(data, "revision", String.valueOf(REVISION)); + + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + encodeDataPair(data, "osname", osname); + encodeDataPair(data, "osarch", osarch); + encodeDataPair(data, "osversion", osversion); + encodeDataPair(data, "cores", Integer.toString(coreCount)); + encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); + encodeDataPair(data, "java_version", java_version); + + // If we're pinging, append it + if (isPing) { + encodeDataPair(data, "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) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + 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 + final 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() + final String value = Integer.toString(plotter.getValue()); + + // Add it to the http post data :) + encodeDataPair(data, key, value); + } + } + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName))); + + // 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 + final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data.toString()); + writer.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + final String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response == null || 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) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + *

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

+ * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer the stringbuilder to append the data pair onto + * @param key the key value + * @param value the value + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is + * rejected + */ + private final String name; + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet(); + + private Graph(final String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return the Graph's name + */ + public String getName() { + return name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter the plotter to add to the graph + */ + public void addPlotter(final Plotter plotter) { + plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter the plotter to remove from the graph + */ + public void removePlotter(final Plotter plotter) { + plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return an unmodifiable {@link java.util.Set} of the plotter objects + */ + public Set getPlotters() { + return Collections.unmodifiableSet(plotters); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Graph)) { + return false; + } + + final Graph graph = (Graph) object; + return graph.name.equals(name); + } + + /** + * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. + */ + protected void onOptOut() { + } + } + + /** + * 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 the name of the plotter to use, which will show up on the website + */ + public Plotter(final String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point. Since this function defers to an external function it may or may + * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called + * from any thread so care should be taken when accessing resources that need to be synchronized. + * + * @return the current value for the point to be plotted. + */ + 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(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + final Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java new file mode 100644 index 0000000..a8840c9 --- /dev/null +++ b/src/main/java/org/spigotmc/RestartCommand.java @@ -0,0 +1,113 @@ +package org.spigotmc; + +import java.io.File; +import java.util.List; +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.IChatBaseComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PacketPlayOutKickDisconnect; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +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 ) ) + { + restart(); + } + return true; + } + + public static void restart() + { + try + { + final File file = new File( SpigotConfig.restartScript ); + if ( file.isFile() ) + { + System.out.println( "Attempting to restart with " + SpigotConfig.restartScript ); + + // Kick all players + for ( EntityPlayer p : (List< EntityPlayer>) MinecraftServer.getServer().getPlayerList().players ) + { + p.playerConnection.disconnect(SpigotConfig.restartMessage); + p.playerConnection.networkManager.d(); + } + // 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 + MinecraftServer.getServer().ag().b(); + + // Give time for it to kick in + try + { + Thread.sleep( 100 ); + } catch ( InterruptedException ex ) + { + } + + // Actually shutdown + try + { + MinecraftServer.getServer().stop(); + } catch ( Throwable t ) + { + } + + // This will be done AFTER the server has completely halted + Thread shutdownHook = new Thread() + { + @Override + public void run() + { + try + { + String os = System.getProperty( "os.name" ).toLowerCase(); + if ( os.contains( "win" ) ) + { + Runtime.getRuntime().exec( "cmd /c start " + file.getPath() ); + } else + { + Runtime.getRuntime().exec( new String[] + { + "sh", file.getPath() + } ); + } + } catch ( Exception e ) + { + e.printStackTrace(); + } + } + }; + + shutdownHook.setDaemon( true ); + Runtime.getRuntime().addShutdownHook( shutdownHook ); + } else + { + System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); + } + System.exit( 0 ); + } catch ( Exception ex ) + { + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java new file mode 100755 index 0000000..769ef2a --- /dev/null +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -0,0 +1,269 @@ +package org.spigotmc; + +import com.google.common.base.Throwables; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import gnu.trove.map.hash.TObjectIntHashMap; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +public class SpigotConfig +{ + + private static final File CONFIG_FILE = new File( "spigot.yml" ); + private static final String HEADER = "This is the main configuration file for Spigot.\n" + + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" + + "with caution, and make sure you know what each option does before configuring.\n" + + "For a reference for any variable inside this file, check out the Spigot wiki at\n" + + "http://www.spigotmc.org/wiki/spigot-configuration/\n" + + "\n" + + "If you need help with the configuration or have any questions related to Spigot,\n" + + "join us at the IRC or drop by our forums and leave a post.\n" + + "\n" + + "IRC: #spigot @ irc.esper.net ( http://webchat.esper.net/?channel=spigot )\n" + + "Forums: http://www.spigotmc.org/forum/\n"; + /*========================================================================*/ + static YamlConfiguration config; + static int version; + static Map commands; + /*========================================================================*/ + private static Metrics metrics; + + public static void init() + { + config = YamlConfiguration.loadConfiguration( CONFIG_FILE ); + config.options().header( HEADER ); + config.options().copyDefaults( true ); + + commands = new HashMap(); + + version = getInt( "config-version", 5 ); + set( "config-version", 5 ); + readConfig( SpigotConfig.class, null ); + } + + public static void registerCommands() + { + for ( Map.Entry entry : commands.entrySet() ) + { + MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() ); + } + + if ( metrics == null ) + { + try + { + metrics = new Metrics(); + metrics.start(); + } catch ( IOException ex ) + { + Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex ); + } + } + } + + static void readConfig(Class clazz, Object instance) + { + for ( Method method : clazz.getDeclaredMethods() ) + { + if ( Modifier.isPrivate( method.getModifiers() ) ) + { + if ( method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE ) + { + try + { + method.setAccessible( true ); + method.invoke( instance ); + } catch ( InvocationTargetException ex ) + { + Throwables.propagate( ex.getCause() ); + } catch ( Exception ex ) + { + Bukkit.getLogger().log( Level.SEVERE, "Error invoking " + method, ex ); + } + } + } + } + + try + { + config.save( CONFIG_FILE ); + } catch ( IOException ex ) + { + Bukkit.getLogger().log( Level.SEVERE, "Could not save " + CONFIG_FILE, ex ); + } + } + + private static void set(String path, Object val) + { + config.set( path, val ); + } + + private static boolean getBoolean(String path, boolean def) + { + config.addDefault( path, def ); + return config.getBoolean( path, config.getBoolean( path ) ); + } + + private static int getInt(String path, int def) + { + config.addDefault( path, def ); + return config.getInt( path, config.getInt( path ) ); + } + + private static List getList(String path, T def) + { + config.addDefault( path, def ); + return (List) config.getList( path, config.getList( path ) ); + } + + private static String getString(String path, String def) + { + config.addDefault( path, def ); + return config.getString( path, config.getString( path ) ); + } + + public static boolean logCommands; + private static void logCommands() + { + logCommands = getBoolean( "commands.log", true ); + } + + public static boolean tabComplete; + private static void tabComplete() + { + tabComplete = getBoolean( "commands.tab-complete", true ); + } + + public static String whitelistMessage; + public static String unknownCommandMessage; + public static String serverFullMessage; + public static String outdatedClientMessage = "Outdated client! Please use {}"; + public static String outdatedServerMessage = "Outdated server! I\'m still on {0}"; + private static String transform(String s) + { + return ChatColor.translateAlternateColorCodes( '&', s ).replaceAll( "\\n", "\n" ); + } + private static void messages() + { + if (version < 4) + { + set( "messages.outdated-client", outdatedClientMessage ); + set( "messages.outdated-server", outdatedServerMessage ); + } + + whitelistMessage = transform( getString( "messages.whitelist", "You are not whitelisted on this server!" ) ); + unknownCommandMessage = transform( getString( "messages.unknown-command", "Unknown command. Type \"/help\" for help." ) ); + serverFullMessage = transform( getString( "messages.server-full", "The server is full!" ) ); + outdatedClientMessage = transform( getString( "messages.outdated-client", outdatedClientMessage ) ); + outdatedServerMessage = transform( getString( "messages.outdated-server", outdatedServerMessage ) ); + } + + public static int timeoutTime = 60; + public static boolean restartOnCrash = true; + public static String restartScript = "./start.sh"; + public static String restartMessage; + private static void watchdog() + { + timeoutTime = getInt( "settings.timeout-time", timeoutTime ); + restartOnCrash = getBoolean( "settings.restart-on-crash", restartOnCrash ); + restartScript = getString( "settings.restart-script", restartScript ); + restartMessage = transform( getString( "messages.restart", "Server is restarting" ) ); + commands.put( "restart", new RestartCommand( "restart" ) ); + WatchdogThread.doStart( timeoutTime, restartOnCrash ); + } + + public static boolean bungee; + private static void bungee() { + if ( version < 4 ) + { + set( "settings.bungeecord", false ); + System.out.println( "Oudated config, disabling BungeeCord support!" ); + } + bungee = getBoolean( "settings.bungeecord", false ); + } + + private static void nettyThreads() + { + int count = getInt( "settings.netty-threads", 4 ); + System.setProperty( "io.netty.eventLoopThreads", Integer.toString( count ) ); + Bukkit.getLogger().log( Level.INFO, "Using {0} threads for Netty based IO", count ); + } + + private static void replaceCommands() + { + if ( config.contains( "replace-commands" ) ) { + set( "commands.replace-commands", config.getStringList( "replace-commands" ) ); + config.set( "replace-commands", null ); + } + for ( String command : (List) getList( "commands.replace-commands", Arrays.asList( "setblock", "summon", "testforblock", "tellraw" ) ) ) + { + SimpleCommandMap.removeFallback( command ); + VanillaCommandWrapper.allowedCommands.add( command ); + } + } + + public static boolean lateBind; + private static void lateBind() { + lateBind = getBoolean( "settings.late-bind", false ); + } + + public static boolean disableStatSaving; + public static TObjectIntHashMap forcedStats = new TObjectIntHashMap(); + private static void stats() + { + disableStatSaving = getBoolean( "stats.disable-saving", false ); + + if ( !config.contains( "stats.forced-stats" ) ) { + config.createSection( "stats.forced-stats" ); + } + + ConfigurationSection section = config.getConfigurationSection( "stats.forced-stats" ); + for ( String name : section.getKeys( true ) ) + { + if ( section.isInt( name ) ) + { + forcedStats.put( name, section.getInt( name ) ); + } + } + + if ( disableStatSaving && section.getInt( "achievement.openInventory", 0 ) < 1 ) + { + Bukkit.getLogger().warning( "*** WARNING *** stats.disable-saving is true but stats.forced-stats.achievement.openInventory" + + " isn't set to 1. Disabling stat saving without forcing the achievement may cause it to get stuck on the player's " + + "screen." ); + } + } + + private static void tpsCommand() + { + commands.put( "tps", new TicksPerSecondCommand( "tps" ) ); + } + + public static int playerSample; + private static void playerSample() + { + playerSample = getInt( "settings.sample-count", 12 ); + System.out.println( "Server Ping Player Sample Count: " + playerSample ); + } + + public static int playerShuffle; + private static void playerShuffle() + { + playerShuffle = getInt( "settings.player-shuffle", 0 ); + } +} diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java new file mode 100644 index 0000000..979b315 --- /dev/null +++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java @@ -0,0 +1,281 @@ +package org.spigotmc; + +import java.util.Arrays; +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; + +public class SpigotWorldConfig +{ + + private final String worldName; + private final YamlConfiguration config; + private boolean verbose; + + public SpigotWorldConfig(String worldName) + { + this.worldName = worldName; + this.config = SpigotConfig.config; + init(); + } + + public void init() + { + this.verbose = getBoolean( "verbose", true ); + + log( "-------- World Settings For [" + worldName + "] --------" ); + SpigotConfig.readConfig( SpigotWorldConfig.class, this ); + } + + private void log(String s) + { + if ( verbose ) + { + Bukkit.getLogger().info( s ); + } + } + + private void set(String path, Object val) + { + config.set( "world-settings.default." + path, val ); + } + + private boolean getBoolean(String path, boolean def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getBoolean( "world-settings." + worldName + "." + path, config.getBoolean( "world-settings.default." + path ) ); + } + + private double getDouble(String path, double def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getDouble( "world-settings." + worldName + "." + path, config.getDouble( "world-settings.default." + path ) ); + } + + private int getInt(String path, int def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getInt( "world-settings." + worldName + "." + path, config.getInt( "world-settings.default." + path ) ); + } + + private List getList(String path, T def) + { + config.addDefault( "world-settings.default." + path, def ); + return (List) config.getList( "world-settings." + worldName + "." + path, config.getList( "world-settings.default." + path ) ); + } + + private String getString(String path, String def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) ); + } + + public int chunksPerTick; + public boolean clearChunksOnTick; + private void chunksPerTick() + { + chunksPerTick = getInt( "chunks-per-tick", 650 ); + log( "Chunks to Grow per Tick: " + chunksPerTick ); + + clearChunksOnTick = getBoolean( "clear-tick-list", false ); + log( "Clear tick list: " + false ); + } + + // Crop growth rates + public int cactusModifier; + public int caneModifier; + public int melonModifier; + public int mushroomModifier; + public int pumpkinModifier; + public int saplingModifier; + public int wheatModifier; + private int getAndValidateGrowth(String crop) + { + int modifier = getInt( "growth." + crop.toLowerCase() + "-modifier", 100 ); + if ( modifier == 0 ) + { + log( "Cannot set " + crop + " growth to zero, defaulting to 100" ); + modifier = 100; + } + log( crop + " Growth Modifier: " + modifier + "%" ); + + return modifier; + } + private void growthModifiers() + { + cactusModifier = getAndValidateGrowth( "Cactus" ); + caneModifier = getAndValidateGrowth( "Cane" ); + melonModifier = getAndValidateGrowth( "Melon" ); + mushroomModifier = getAndValidateGrowth( "Mushroom" ); + pumpkinModifier = getAndValidateGrowth( "Pumpkin" ); + saplingModifier = getAndValidateGrowth( "Sapling" ); + wheatModifier = getAndValidateGrowth( "Wheat" ); + } + + public double itemMerge; + private void itemMerge() + { + itemMerge = getDouble("merge-radius.item", 2.5 ); + log( "Item Merge Radius: " + itemMerge ); + } + + public double expMerge; + private void expMerge() + { + expMerge = getDouble("merge-radius.exp", 3.0 ); + log( "Experience Merge Radius: " + expMerge ); + } + + public int viewDistance; + private void viewDistance() + { + viewDistance = getInt( "view-distance", Bukkit.getViewDistance() ); + log( "View Distance: " + viewDistance ); + } + + public byte mobSpawnRange; + private void mobSpawnRange() + { + mobSpawnRange = (byte) getInt( "mob-spawn-range", 4 ); + log( "Mob Spawn Range: " + mobSpawnRange ); + } + + public int animalActivationRange = 32; + public int monsterActivationRange = 32; + public int miscActivationRange = 16; + private void activationRange() + { + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Mi " + miscActivationRange ); + } + + public int playerTrackingRange = 48; + public int animalTrackingRange = 48; + public int monsterTrackingRange = 48; + public int miscTrackingRange = 32; + public int maxTrackingRange = 64; + private void trackingRange() + { + playerTrackingRange = getInt( "entity-tracking-range.players", playerTrackingRange ); + animalTrackingRange = getInt( "entity-tracking-range.animals", animalTrackingRange ); + monsterTrackingRange = getInt( "entity-tracking-range.monsters", monsterTrackingRange ); + miscTrackingRange = getInt( "entity-tracking-range.misc", miscTrackingRange ); + maxTrackingRange = getInt( "entity-tracking-range.other", maxTrackingRange ); + log( "Entity Tracking Range: Pl " + playerTrackingRange + " / An " + animalTrackingRange + " / Mo " + monsterTrackingRange + " / Mi " + miscTrackingRange + " / Other " + maxTrackingRange ); + } + + public int hopperTransfer = 8; + public int hopperCheck = 8; + private void hoppers() + { + // Set the tick delay between hopper item movements + hopperTransfer = getInt( "ticks-per.hopper-transfer", hopperTransfer ); + // Set the tick delay between checking for items after the associated + // container is empty. Default to the hopperTransfer value to prevent + // hopper sorting machines from becoming out of sync. + hopperCheck = getInt( "ticks-per.hopper-check", hopperTransfer ); + log( "Hopper Transfer: " + hopperTransfer + " Hopper Check: " + hopperCheck ); + } + + public boolean randomLightUpdates; + private void lightUpdates() + { + randomLightUpdates = getBoolean( "random-light-updates", false ); + log( "Random Lighting Updates: " + randomLightUpdates ); + } + + public boolean saveStructureInfo; + private void structureInfo() + { + saveStructureInfo = getBoolean( "save-structure-info", true ); + log( "Structure Info Saving: " + saveStructureInfo ); + if ( !saveStructureInfo ) + { + log( "*** WARNING *** You have selected to NOT save structure info. This may cause structures such as fortresses to not spawn mobs when updating to 1.7!" ); + log( "*** WARNING *** Please use this option with caution, SpigotMC is not responsible for any issues this option may cause in the future!" ); + } + } + + public int itemDespawnRate; + private void itemDespawnRate() + { + itemDespawnRate = getInt( "item-despawn-rate", 6000 ); + log( "Item Despawn Rate: " + itemDespawnRate ); + } + + public int arrowDespawnRate; + private void arrowDespawnRate() + { + arrowDespawnRate = getInt( "arrow-despawn-rate", 1200 ); + log( "Arrow Despawn Rate: " + arrowDespawnRate ); + } + + public boolean antiXray; + public int engineMode; + public List hiddenBlocks; + public List replaceBlocks; + public AntiXray antiXrayInstance; + private void antiXray() + { + antiXray = getBoolean( "anti-xray.enabled", true ); + log( "Anti X-Ray: " + antiXray ); + + engineMode = getInt( "anti-xray.engine-mode", 1 ); + log( "\tEngine Mode: " + engineMode ); + + if ( SpigotConfig.version < 5 ) + { + set( "anti-xray.blocks", null ); + } + hiddenBlocks = getList( "anti-xray.hide-blocks", Arrays.asList( new Integer[] + { + 14, 15, 16, 21, 48, 49, 54, 56, 73, 74, 82, 129, 130 + } ) ); + log( "\tHidden Blocks: " + hiddenBlocks ); + + replaceBlocks = getList( "anti-xray.replace-blocks", Arrays.asList( new Integer[] + { + 1, 5 + } ) ); + log( "\tReplace Blocks: " + replaceBlocks ); + + antiXrayInstance = new AntiXray( this ); + } + + public boolean zombieAggressiveTowardsVillager; + private void zombieAggressiveTowardsVillager() + { + zombieAggressiveTowardsVillager = getBoolean( "zombie-aggressive-towards-villager", true ); + log( "Zombie Aggressive Towards Villager: " + zombieAggressiveTowardsVillager ); + } + + public boolean nerfSpawnerMobs; + private void nerfSpawnerMobs() + { + nerfSpawnerMobs = getBoolean( "nerf-spawner-mobs", false ); + log( "Nerfing mobs spawned from spawners: " + nerfSpawnerMobs ); + } + + public boolean enableZombiePigmenPortalSpawns; + private void enableZombiePigmenPortalSpawns() + { + enableZombiePigmenPortalSpawns = getBoolean( "enable-zombie-pigmen-portal-spawns", true ); + log( "Allow Zombie Pigmen to spawn from portal blocks: " + enableZombiePigmenPortalSpawns ); + } + + public int maxBulkChunk; + private void bulkChunkCount() + { + maxBulkChunk = getInt( "max-bulk-chunks", 5 ); + log( "Sending up to " + maxBulkChunk + " chunks per packet" ); + } + + public int maxCollisionsPerEntity; + private void maxEntityCollision() + { + maxCollisionsPerEntity = getInt( "max-entity-collisions", 8 ); + log( "Max Entity Collisions: " + maxCollisionsPerEntity ); + } +} diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java new file mode 100644 index 0000000..2b8343d --- /dev/null +++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java @@ -0,0 +1,45 @@ +package org.spigotmc; + +import com.google.common.base.Joiner; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.com.google.common.collect.Iterables; +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; + } + + StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " ); + for ( double tps : MinecraftServer.getServer().recentTps ) + { + sb.append( format( tps ) ); + sb.append( ", " ); + } + sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); + + return true; + } + + private String format(double tps) + { + return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() + + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); + } +} diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java new file mode 100644 index 0000000..bc6438d --- /dev/null +++ b/src/main/java/org/spigotmc/TrackingRange.java @@ -0,0 +1,45 @@ +package org.spigotmc; + +import net.minecraft.server.Entity; +import net.minecraft.server.EntityExperienceOrb; +import net.minecraft.server.EntityGhast; +import net.minecraft.server.EntityItem; +import net.minecraft.server.EntityItemFrame; +import net.minecraft.server.EntityPainting; +import net.minecraft.server.EntityPlayer; + +public class TrackingRange +{ + + /** + * Gets the range an entity should be 'tracked' by players and visible in + * the client. + * + * @param entity + * @param defaultRange Default range defined by Mojang + * @return + */ + public static int getEntityTrackingRange(Entity entity, int defaultRange) + { + SpigotWorldConfig config = entity.world.spigotConfig; + int range = defaultRange; + if ( entity instanceof EntityPlayer ) + { + range = config.playerTrackingRange; + } else if ( entity.defaultActivationState || entity instanceof EntityGhast ) + { + range = defaultRange; + } else if ( entity.activationType == 1 ) + { + range = config.monsterTrackingRange; + } else if ( entity.activationType == 2 ) + { + range = config.animalTrackingRange; + } else if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) + { + range = config.miscTrackingRange; + } + + return Math.min( config.maxTrackingRange, range ); + } +} diff --git a/src/main/java/org/spigotmc/VanillaCommandWrapper.java b/src/main/java/org/spigotmc/VanillaCommandWrapper.java new file mode 100644 index 0000000..a6c76cc --- /dev/null +++ b/src/main/java/org/spigotmc/VanillaCommandWrapper.java @@ -0,0 +1,194 @@ +package org.spigotmc; + +import com.google.common.collect.ImmutableList; +import net.minecraft.server.ChatComponentText; +import net.minecraft.server.ChunkCoordinates; +import net.minecraft.server.EntityMinecartCommandBlock; +import net.minecraft.server.IChatBaseComponent; +import net.minecraft.server.ICommandListener; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.TileEntityCommand; +import net.minecraft.server.World; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.block.Block; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.command.CraftBlockCommandSender; +import org.bukkit.craftbukkit.entity.CraftMinecartCommand; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class VanillaCommandWrapper +{ + + public static final HashSet allowedCommands = new HashSet(); + + public static int dispatch(CommandSender sender, String commandLine) + { + int pos = commandLine.indexOf( ' ' ); + if ( pos == -1 ) + { + pos = commandLine.length(); + } + String name = commandLine.substring( 0, pos ); + if ( !allowedCommands.contains( name ) ) + { + return -1; + } + if ( !sender.hasPermission( "bukkit.command." + name ) ) + { + sender.sendMessage( ChatColor.RED + "You do not have permission for this command" ); + return 0; + } + ICommandListener listener = getListener( sender ); + if ( listener == null ) + { + return -1; + } + return MinecraftServer.getServer().getCommandHandler().a( listener, commandLine ); + } + + public static List complete(CommandSender sender, String commandLine) + { + int pos = commandLine.indexOf( ' ' ); + if ( pos == -1 ) + { + List completions = new ArrayList(); + commandLine = commandLine.toLowerCase(); + for ( String command : allowedCommands ) + { + if ( command.startsWith( commandLine ) && sender.hasPermission( "bukkit.command." + command ) ) + { + completions.add( "/" + command ); + } + } + return completions; + } + String name = commandLine.substring( 0, pos ); + if ( !allowedCommands.contains( name ) || !sender.hasPermission( "bukkit.command." + name ) ) + { + return ImmutableList.of(); + } + ICommandListener listener = getListener( sender ); + if ( listener == null ) + { + return ImmutableList.of(); + } + return MinecraftServer.getServer().getCommandHandler().b( listener, commandLine ); + } + + private static ICommandListener getListener(CommandSender sender) + { + if ( sender instanceof CraftPlayer ) + { + return new PlayerListener( ( (CraftPlayer) sender ).getHandle() ); + } + if ( sender instanceof CraftBlockCommandSender ) + { + CraftBlockCommandSender commandBlock = (CraftBlockCommandSender) sender; + Block block = commandBlock.getBlock(); + return ( (TileEntityCommand) ( (CraftWorld) block.getWorld() ).getTileEntityAt( block.getX(), block.getY(), block.getZ() ) ).a(); + } + if ( sender instanceof CraftMinecartCommand ) + { + return ( (EntityMinecartCommandBlock) ( (CraftMinecartCommand) sender ).getHandle() ).e(); + } + return new ConsoleListener(sender); // Assume console/rcon + } + + private static class PlayerListener implements ICommandListener + { + + private final ICommandListener handle; + + public PlayerListener(ICommandListener handle) + { + this.handle = handle; + } + + @Override + public String getName() + { + return handle.getName(); + } + + @Override + public IChatBaseComponent getScoreboardDisplayName() + { + return handle.getScoreboardDisplayName(); + } + + @Override + public void sendMessage(IChatBaseComponent iChatBaseComponent) + { + handle.sendMessage( iChatBaseComponent ); + } + + @Override + public boolean a(int i, String s) + { + return true; + } + + @Override + public ChunkCoordinates getChunkCoordinates() + { + return handle.getChunkCoordinates(); + } + + @Override + public World getWorld() + { + return handle.getWorld(); + } + } + + private static class ConsoleListener implements ICommandListener { + + private final CommandSender sender; + + public ConsoleListener( CommandSender sender ) + { + this.sender = sender; + } + + @Override + public String getName() + { + return sender.getName(); + } + + @Override + public IChatBaseComponent getScoreboardDisplayName() + { + return new ChatComponentText( getName() ); + } + + @Override + public void sendMessage( IChatBaseComponent iChatBaseComponent ) + { + sender.sendMessage( iChatBaseComponent.e() ); + } + + @Override + public boolean a( int i, String s ) + { + return true; + } + + @Override + public ChunkCoordinates getChunkCoordinates() + { + return new ChunkCoordinates( 0, 0, 0 ); + } + + @Override + public World getWorld() + { + return MinecraftServer.getServer().getWorld(); + } + } +} diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java new file mode 100644 index 0000000..de08ad6 --- /dev/null +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -0,0 +1,117 @@ +package org.spigotmc; + +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; + +public class WatchdogThread extends Thread +{ + + private static WatchdogThread instance; + private final long timeoutTime; + private final boolean restart; + private volatile long lastTick; + private volatile boolean stopping; + + private WatchdogThread(long timeoutTime, boolean restart) + { + super( "Spigot Watchdog Thread" ); + this.timeoutTime = timeoutTime; + this.restart = restart; + } + + public static void doStart(int timeoutTime, boolean restart) + { + if ( instance == null ) + { + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } + } + + public static void tick() + { + instance.lastTick = System.currentTimeMillis(); + } + + public static void doStop() + { + if ( instance != null ) + { + instance.stopping = true; + } + } + + @Override + public void run() + { + while ( !stopping ) + { + // + if ( lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime ) + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "The server has stopped responding!" ); + log.log( Level.SEVERE, "Please report this to http://www.spigotmc.org/" ); + log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); + log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); + // + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + dumpThread( thread, log ); + } + log.log( Level.SEVERE, "------------------------------" ); + + if ( restart ) + { + RestartCommand.restart(); + } + break; + } + + try + { + sleep( 10000 ); + } catch ( InterruptedException ex ) + { + interrupt(); + } + } + } + + private static void dumpThread(ThreadInfo thread, Logger log) + { + log.log( Level.SEVERE, "------------------------------" ); + // + log.log( Level.SEVERE, "Current Thread: " + thread.getThreadName() ); + log.log( Level.SEVERE, "\tPID: " + thread.getThreadId() + + " | Suspended: " + thread.isSuspended() + + " | Native: " + thread.isInNative() + + " | State: " + thread.getThreadState() ); + if ( thread.getLockedMonitors().length != 0 ) + { + log.log( Level.SEVERE, "\tThread is waiting on monitor(s):" ); + for ( MonitorInfo monitor : thread.getLockedMonitors() ) + { + log.log( Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame() ); + } + } + log.log( Level.SEVERE, "\tStack:" ); + // + for ( StackTraceElement stack : thread.getStackTrace() ) + { + log.log( Level.SEVERE, "\t\t" + stack ); + } + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index 60a894f..072c925 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -6,11 +6,10 @@ # If you need help on this file, feel free to join us on irc or leave a message # on the forums asking for advice. # -# IRC: #bukkit @ esper.net -# (If this means nothing to you, just go to http://webchat.esper.net/?channels=bukkit ) -# Forums: http://forums.bukkit.org/forums/bukkit-help.6/ -# Twitter: http://twitter.com/Craftbukkit -# Bug tracker: http://leaky.bukkit.org/ +# IRC: #spigot @ irc.spi.gt +# (If this means nothing to you, just go to http://irc.spi.gt/iris/?nick=&channels=spigot ) +# Forums: http://www.spigotmc.org/forums/help.40/ +# Bug tracker: http://www.spigotmc.org/forums/bugs-feature-requests.8/ settings: -- 1.8.3.2