summaryrefslogtreecommitdiffstats
path: root/nms-patches
diff options
context:
space:
mode:
authorThinkofdeath <thinkofdeath@spigotmc.org>2014-11-26 08:32:16 +1100
committermd_5 <git@md-5.net>2014-11-28 17:16:30 +1100
commit24557bc2b37deb6a0edf497d547471832457b1dd (patch)
treec560572889a3b0b34964a0cddb35dc87fda3c914 /nms-patches
parenta4805dbd77da057cc1ea0bf344379bc6e53ca1f6 (diff)
downloadcraftbukkit-24557bc2b37deb6a0edf497d547471832457b1dd.tar
craftbukkit-24557bc2b37deb6a0edf497d547471832457b1dd.tar.gz
craftbukkit-24557bc2b37deb6a0edf497d547471832457b1dd.tar.lz
craftbukkit-24557bc2b37deb6a0edf497d547471832457b1dd.tar.xz
craftbukkit-24557bc2b37deb6a0edf497d547471832457b1dd.zip
Update to Minecraft 1.8
For more information please see http://www.spigotmc.org/
Diffstat (limited to 'nms-patches')
-rw-r--r--nms-patches/BiomeTheEndDecorator.patch11
-rw-r--r--nms-patches/Block.patch23
-rw-r--r--nms-patches/BlockBloodStone.patch28
-rw-r--r--nms-patches/BlockButtonAbstract.patch111
-rw-r--r--nms-patches/BlockCactus.patch31
-rw-r--r--nms-patches/BlockCake.patch22
-rw-r--r--nms-patches/BlockCocoa.patch35
-rw-r--r--nms-patches/BlockCommand.patch34
-rw-r--r--nms-patches/BlockCrops.patch35
-rw-r--r--nms-patches/BlockDaylightDetector.patch10
-rw-r--r--nms-patches/BlockDiodeAbstract.patch30
-rw-r--r--nms-patches/BlockDispenser.patch18
-rw-r--r--nms-patches/BlockDoor.patch43
-rw-r--r--nms-patches/BlockDragonEgg.patch30
-rw-r--r--nms-patches/BlockDropper.patch41
-rw-r--r--nms-patches/BlockEnderPortal.patch22
-rw-r--r--nms-patches/BlockFire.patch110
-rw-r--r--nms-patches/BlockFlowing.patch83
-rw-r--r--nms-patches/BlockGrass.patch77
-rw-r--r--nms-patches/BlockIce.patch15
-rw-r--r--nms-patches/BlockLeaves.patch26
-rw-r--r--nms-patches/BlockLever.patch32
-rw-r--r--nms-patches/BlockMinecartDetector.patch29
-rw-r--r--nms-patches/BlockMobSpawner.patch22
-rw-r--r--nms-patches/BlockMonsterEggs.patch20
-rw-r--r--nms-patches/BlockMushroom.patch57
-rw-r--r--nms-patches/BlockMycel.patch58
-rw-r--r--nms-patches/BlockNetherWart.patch12
-rw-r--r--nms-patches/BlockOre.patch42
-rw-r--r--nms-patches/BlockPiston.patch76
-rw-r--r--nms-patches/BlockPortal.patch53
-rw-r--r--nms-patches/BlockPoweredRail.patch25
-rw-r--r--nms-patches/BlockPressurePlateAbstract.patch31
-rw-r--r--nms-patches/BlockPressurePlateBinary.patch38
-rw-r--r--nms-patches/BlockPressurePlateWeighted.patch43
-rw-r--r--nms-patches/BlockPumpkin.patch117
-rw-r--r--nms-patches/BlockRedstoneLamp.patch47
-rw-r--r--nms-patches/BlockRedstoneOre.patch102
-rw-r--r--nms-patches/BlockRedstoneTorch.patch55
-rw-r--r--nms-patches/BlockRedstoneWire.patch27
-rw-r--r--nms-patches/BlockReed.patch18
-rw-r--r--nms-patches/BlockSapling.patch125
-rw-r--r--nms-patches/BlockSkull.patch133
-rw-r--r--nms-patches/BlockSnow.patch14
-rw-r--r--nms-patches/BlockSoil.patch52
-rw-r--r--nms-patches/BlockStationary.patch40
-rw-r--r--nms-patches/BlockStem.patch41
-rw-r--r--nms-patches/BlockTrapdoor.patch31
-rw-r--r--nms-patches/BlockTripwire.patch52
-rw-r--r--nms-patches/BlockTripwireHook.patch29
-rw-r--r--nms-patches/BlockVine.patch76
-rw-r--r--nms-patches/Chunk.patch169
-rw-r--r--nms-patches/ChunkProviderServer.patch348
-rw-r--r--nms-patches/ChunkRegionLoader.patch131
-rw-r--r--nms-patches/ChunkSection.patch21
-rw-r--r--nms-patches/CommandBlockListenerAbstract.patch162
-rw-r--r--nms-patches/Container.patch204
-rw-r--r--nms-patches/ContainerAnvil.patch51
-rw-r--r--nms-patches/ContainerAnvilInventory.patch58
-rw-r--r--nms-patches/ContainerBeacon.patch47
-rw-r--r--nms-patches/ContainerBrewingStand.patch52
-rw-r--r--nms-patches/ContainerChest.patch59
-rw-r--r--nms-patches/ContainerDispenser.patch54
-rw-r--r--nms-patches/ContainerEnchantTable.patch170
-rw-r--r--nms-patches/ContainerEnchantTableInventory.patch58
-rw-r--r--nms-patches/ContainerFurnace.patch51
-rw-r--r--nms-patches/ContainerHopper.patch44
-rw-r--r--nms-patches/ContainerHorse.patch36
-rw-r--r--nms-patches/ContainerMerchant.patch36
-rw-r--r--nms-patches/ContainerPlayer.patch74
-rw-r--r--nms-patches/ContainerWorkbench.patch79
-rw-r--r--nms-patches/ControllerLook.patch23
-rw-r--r--nms-patches/ControllerMove.patch12
-rw-r--r--nms-patches/CraftingManager.patch57
-rw-r--r--nms-patches/CrashReport.patch10
-rw-r--r--nms-patches/DedicatedServer.patch162
-rw-r--r--nms-patches/DispenseBehaviorArmor.patch59
-rw-r--r--nms-patches/DispenseBehaviorBoat.patch54
-rw-r--r--nms-patches/DispenseBehaviorBonemeal.patch44
-rw-r--r--nms-patches/DispenseBehaviorEmptyBucket.patch44
-rw-r--r--nms-patches/DispenseBehaviorFilledBucket.patch65
-rw-r--r--nms-patches/DispenseBehaviorFireball.patch55
-rw-r--r--nms-patches/DispenseBehaviorFlintAndSteel.patch57
-rw-r--r--nms-patches/DispenseBehaviorItem.patch76
-rw-r--r--nms-patches/DispenseBehaviorMinecart.patch58
-rw-r--r--nms-patches/DispenseBehaviorMonsterEgg.patch61
-rw-r--r--nms-patches/DispenseBehaviorProjectile.patch54
-rw-r--r--nms-patches/DispenseBehaviorTNT.patch56
-rw-r--r--nms-patches/Enchantment.patch19
-rw-r--r--nms-patches/Entity.patch578
-rw-r--r--nms-patches/EntityAgeable.patch48
-rw-r--r--nms-patches/EntityArrow.patch106
-rw-r--r--nms-patches/EntityBoat.patch249
-rw-r--r--nms-patches/EntityChicken.patch14
-rw-r--r--nms-patches/EntityCow.patch42
-rw-r--r--nms-patches/EntityCreature.patch31
-rw-r--r--nms-patches/EntityCreeper.patch104
-rw-r--r--nms-patches/EntityDamageSourceIndirect.patch14
-rw-r--r--nms-patches/EntityEgg.patch65
-rw-r--r--nms-patches/EntityEnderCrystal.patch43
-rw-r--r--nms-patches/EntityEnderDragon.patch330
-rw-r--r--nms-patches/EntityEnderPearl.patch50
-rw-r--r--nms-patches/EntityEnderman.patch34
-rw-r--r--nms-patches/EntityExperienceOrb.patch82
-rw-r--r--nms-patches/EntityFallingBlock.patch44
-rw-r--r--nms-patches/EntityFireball.patch111
-rw-r--r--nms-patches/EntityFishingHook.patch159
-rw-r--r--nms-patches/EntityHanging.patch111
-rw-r--r--nms-patches/EntityHorse.patch140
-rw-r--r--nms-patches/EntityHuman.patch383
-rw-r--r--nms-patches/EntityInsentient.patch164
-rw-r--r--nms-patches/EntityIronGolem.patch11
-rw-r--r--nms-patches/EntityItem.patch115
-rw-r--r--nms-patches/EntityItemFrame.patch15
-rw-r--r--nms-patches/EntityLargeFireball.patch38
-rw-r--r--nms-patches/EntityLeash.patch61
-rw-r--r--nms-patches/EntityLightning.patch111
-rw-r--r--nms-patches/EntityLiving.patch440
-rw-r--r--nms-patches/EntityMinecartAbstract.patch231
-rw-r--r--nms-patches/EntityMinecartCommandBlockListener.patch10
-rw-r--r--nms-patches/EntityMinecartContainer.patch61
-rw-r--r--nms-patches/EntityMonster.patch26
-rw-r--r--nms-patches/EntityMushroomCow.patch26
-rw-r--r--nms-patches/EntityOcelot.patch30
-rw-r--r--nms-patches/EntityPainting.patch10
-rw-r--r--nms-patches/EntityPig.patch29
-rw-r--r--nms-patches/EntityPlayer.patch542
-rw-r--r--nms-patches/EntityPotion.patch70
-rw-r--r--nms-patches/EntityProjectile.patch31
-rw-r--r--nms-patches/EntitySheep.patch54
-rw-r--r--nms-patches/EntitySkeleton.patch60
-rw-r--r--nms-patches/EntitySlime.patch40
-rw-r--r--nms-patches/EntitySmallFireball.patch39
-rw-r--r--nms-patches/EntitySnowman.patch42
-rw-r--r--nms-patches/EntitySpider.patch11
-rw-r--r--nms-patches/EntitySquid.patch37
-rw-r--r--nms-patches/EntityTNTPrimed.patch52
-rw-r--r--nms-patches/EntityThrownExpBottle.patch22
-rw-r--r--nms-patches/EntityTrackerEntry.patch176
-rw-r--r--nms-patches/EntityVillager.patch19
-rw-r--r--nms-patches/EntityWither.patch78
-rw-r--r--nms-patches/EntityWitherSkull.patch36
-rw-r--r--nms-patches/EntityWolf.patch87
-rw-r--r--nms-patches/EntityZombie.patch108
-rw-r--r--nms-patches/ExpirableListEntry.patch42
-rw-r--r--nms-patches/Explosion.patch127
-rw-r--r--nms-patches/FoodMetaData.patch66
-rw-r--r--nms-patches/HandshakeListener.patch69
-rw-r--r--nms-patches/IDataManager.patch18
-rw-r--r--nms-patches/IInventory.patch31
-rw-r--r--nms-patches/IRecipe.patch9
-rw-r--r--nms-patches/InventoryCraftResult.patch48
-rw-r--r--nms-patches/InventoryCrafting.patch64
-rw-r--r--nms-patches/InventoryEnderChest.patch51
-rw-r--r--nms-patches/InventoryHorseChest.patch63
-rw-r--r--nms-patches/InventoryLargeChest.patch66
-rw-r--r--nms-patches/InventoryMerchant.patch59
-rw-r--r--nms-patches/InventorySubcontainer.patch59
-rw-r--r--nms-patches/ItemBoat.patch17
-rw-r--r--nms-patches/ItemBow.patch49
-rw-r--r--nms-patches/ItemBucket.patch102
-rw-r--r--nms-patches/ItemDye.patch28
-rw-r--r--nms-patches/ItemFireball.patch19
-rw-r--r--nms-patches/ItemFishingRod.patch31
-rw-r--r--nms-patches/ItemFlintAndSteel.patch47
-rw-r--r--nms-patches/ItemHanging.patch41
-rw-r--r--nms-patches/ItemLeash.patch35
-rw-r--r--nms-patches/ItemMapEmpty.patch24
-rw-r--r--nms-patches/ItemMinecart.patch16
-rw-r--r--nms-patches/ItemMonsterEgg.patch25
-rw-r--r--nms-patches/ItemStack.patch219
-rw-r--r--nms-patches/ItemWaterLily.patch18
-rw-r--r--nms-patches/ItemWorldMap.patch75
-rw-r--r--nms-patches/JsonList.patch33
-rw-r--r--nms-patches/LoginListener.patch35
-rw-r--r--nms-patches/MethodProfiler.patch135
-rw-r--r--nms-patches/MinecraftServer.patch726
-rw-r--r--nms-patches/MobEffectList.patch74
-rw-r--r--nms-patches/MobSpawnerAbstract.patch38
-rw-r--r--nms-patches/NameReferencingFileConverter.patch76
-rw-r--r--nms-patches/NetworkManager.patch20
-rw-r--r--nms-patches/Packet.patch14
-rw-r--r--nms-patches/PacketDataSerializer.patch122
-rw-r--r--nms-patches/PacketPlayInBlockPlace.patch32
-rw-r--r--nms-patches/PacketPlayInCloseWindow.patch29
-rw-r--r--nms-patches/PacketStatusListener.patch113
-rw-r--r--nms-patches/Path.patch11
-rw-r--r--nms-patches/PathfinderGoalBreakDoor.patch15
-rw-r--r--nms-patches/PathfinderGoalBreed.patch23
-rw-r--r--nms-patches/PathfinderGoalDefendVillage.patch11
-rw-r--r--nms-patches/PathfinderGoalEatTile.patch34
-rw-r--r--nms-patches/PathfinderGoalEndermanPickupBlock.patch17
-rw-r--r--nms-patches/PathfinderGoalEndermanPlaceBlock.patch15
-rw-r--r--nms-patches/PathfinderGoalGhastAttackTarget.patch12
-rw-r--r--nms-patches/PathfinderGoalHurtByTarget.patch19
-rw-r--r--nms-patches/PathfinderGoalMakeLove.patch11
-rw-r--r--nms-patches/PathfinderGoalNearestAttackableTarget.patch11
-rw-r--r--nms-patches/PathfinderGoalNearestAttackableTargetInsentient.patch11
-rw-r--r--nms-patches/PathfinderGoalOwnerHurtByTarget.patch11
-rw-r--r--nms-patches/PathfinderGoalOwnerHurtTarget.patch11
-rw-r--r--nms-patches/PathfinderGoalPanic.patch15
-rw-r--r--nms-patches/PathfinderGoalSelector.patch32
-rw-r--r--nms-patches/PathfinderGoalSilverfishHideInBlock.patch14
-rw-r--r--nms-patches/PathfinderGoalSilverfishWakeOthers.patch14
-rw-r--r--nms-patches/PathfinderGoalSit.patch11
-rw-r--r--nms-patches/PathfinderGoalTame.patch31
-rw-r--r--nms-patches/PathfinderGoalTargetNearestPlayer.patch11
-rw-r--r--nms-patches/PlayerChunk.patch104
-rw-r--r--nms-patches/PlayerChunkMap.patch208
-rw-r--r--nms-patches/PlayerConnection.patch1458
-rw-r--r--nms-patches/PlayerDatFileConverter.patch33
-rw-r--r--nms-patches/PlayerInteractManager.patch289
-rw-r--r--nms-patches/PlayerInventory.patch100
-rw-r--r--nms-patches/PlayerList.patch788
-rw-r--r--nms-patches/PlayerSelector.patch14
-rw-r--r--nms-patches/PortalCreator.patch102
-rw-r--r--nms-patches/PortalTravelAgent.patch273
-rw-r--r--nms-patches/PropertyManager.patch94
-rw-r--r--nms-patches/RecipeArmorDye.patch18
-rw-r--r--nms-patches/RecipeBookClone.patch17
-rw-r--r--nms-patches/RecipeFireworks.patch21
-rw-r--r--nms-patches/RecipeMapClone.patch17
-rw-r--r--nms-patches/RecipeRepair.patch39
-rw-r--r--nms-patches/RecipesBannerInnerClass1.patch17
-rw-r--r--nms-patches/RecipesBannerInnerClass2.patch17
-rw-r--r--nms-patches/RecipesFurnace.patch49
-rw-r--r--nms-patches/RegionFile.patch81
-rw-r--r--nms-patches/RemoteControlCommandListener.patch15
-rw-r--r--nms-patches/ScoreboardServer.patch126
-rw-r--r--nms-patches/SecondaryWorldServer.patch15
-rw-r--r--nms-patches/ShapedRecipes.patch77
-rw-r--r--nms-patches/ShapelessRecipes.patch35
-rw-r--r--nms-patches/SlotFurnaceResult.patch31
-rw-r--r--nms-patches/SpawnerCreature.patch109
-rw-r--r--nms-patches/StatisticManager.patch15
-rw-r--r--nms-patches/SwitchHelperLogVariant.patch9
-rw-r--r--nms-patches/ThreadCommandReader.patch43
-rw-r--r--nms-patches/ThreadPlayerLookupUUID.patch72
-rw-r--r--nms-patches/TileEntity.patch24
-rw-r--r--nms-patches/TileEntityBeacon.patch54
-rw-r--r--nms-patches/TileEntityBrewingStand.patch94
-rw-r--r--nms-patches/TileEntityChest.patch104
-rw-r--r--nms-patches/TileEntityCommandListener.patch10
-rw-r--r--nms-patches/TileEntityDispenser.patch63
-rw-r--r--nms-patches/TileEntityFurnace.patch183
-rw-r--r--nms-patches/TileEntityHopper.patch154
-rw-r--r--nms-patches/TileEntityNote.patch16
-rw-r--r--nms-patches/TileEntityPiston.patch10
-rw-r--r--nms-patches/TileEntityRecordPlayer.patch14
-rw-r--r--nms-patches/TileEntitySkull.patch13
-rw-r--r--nms-patches/Village.patch11
-rw-r--r--nms-patches/VillageSiege.patch11
-rw-r--r--nms-patches/World.patch560
-rw-r--r--nms-patches/WorldBorder.patch25
-rw-r--r--nms-patches/WorldGenGroundBush.patch14
-rw-r--r--nms-patches/WorldGenMegaTreeAbstract.patch11
-rw-r--r--nms-patches/WorldGenVillagePiece.patch11
-rw-r--r--nms-patches/WorldGenVillagePieces.patch0
-rw-r--r--nms-patches/WorldGenWitchHut.patch11
-rw-r--r--nms-patches/WorldManager.patch28
-rw-r--r--nms-patches/WorldMap.patch95
-rw-r--r--nms-patches/WorldMapHumanTracker.patch31
-rw-r--r--nms-patches/WorldNBTStorage.patch123
-rw-r--r--nms-patches/WorldServer.patch550
264 files changed, 20633 insertions, 0 deletions
diff --git a/nms-patches/BiomeTheEndDecorator.patch b/nms-patches/BiomeTheEndDecorator.patch
new file mode 100644
index 00000000..6c957c2a
--- /dev/null
+++ b/nms-patches/BiomeTheEndDecorator.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BiomeTheEndDecorator.java 2014-11-27 08:59:46.501422728 +1100
++++ src/main/java/net/minecraft/server/BiomeTheEndDecorator.java 2014-11-27 08:42:10.136850942 +1100
+@@ -21,7 +21,7 @@
+ EntityEnderDragon entityenderdragon = new EntityEnderDragon(this.a);
+
+ entityenderdragon.setPositionRotation(0.0D, 128.0D, 0.0D, this.b.nextFloat() * 360.0F, 0.0F);
+- this.a.addEntity(entityenderdragon);
++ this.a.addEntity(entityenderdragon, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+ }
+
+ }
diff --git a/nms-patches/Block.patch b/nms-patches/Block.patch
new file mode 100644
index 00000000..fc44dc75
--- /dev/null
+++ b/nms-patches/Block.patch
@@ -0,0 +1,23 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Block.java 2014-11-27 08:59:46.537422569 +1100
++++ src/main/java/net/minecraft/server/Block.java 2014-11-27 08:42:10.172850872 +1100
+@@ -295,7 +295,8 @@
+ int j = this.getDropCount(i, world.random);
+
+ for (int k = 0; k < j; ++k) {
+- if (world.random.nextFloat() <= f) {
++ // CraftBukkit - <= to < to allow for plugins to completely disable block drops from explosions
++ if (world.random.nextFloat() < f) {
+ Item item = this.getDropType(iblockdata, world.random, i);
+
+ if (item != null) {
+@@ -920,4 +921,10 @@
+ private static void a(int i, String s, Block block) {
+ a(i, new MinecraftKey(s), block);
+ }
++
++ // CraftBukkit start
++ public int getExpDrop(World world, IBlockData data, int enchantmentLevel) {
++ return 0;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/BlockBloodStone.patch b/nms-patches/BlockBloodStone.patch
new file mode 100644
index 00000000..82233f90
--- /dev/null
+++ b/nms-patches/BlockBloodStone.patch
@@ -0,0 +1,28 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockBloodStone.java 2014-11-27 08:59:46.505422709 +1100
++++ src/main/java/net/minecraft/server/BlockBloodStone.java 2014-11-27 08:42:10.124850965 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockBloodStone extends Block {
+
+ public BlockBloodStone() {
+@@ -10,4 +12,17 @@
+ public MaterialMapColor g(IBlockData iblockdata) {
+ return MaterialMapColor.K;
+ }
++
++ // CraftBukkit start
++ @Override
++ public void doPhysics(World world, BlockPosition position, IBlockData iblockdata, Block block) {
++ if (block != null && block.isPowerSource()) {
++ org.bukkit.block.Block bl = world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ());
++ int power = bl.getBlockPower();
++
++ BlockRedstoneEvent event = new BlockRedstoneEvent(bl, power, power);
++ world.getServer().getPluginManager().callEvent(event);
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/BlockButtonAbstract.patch b/nms-patches/BlockButtonAbstract.patch
new file mode 100644
index 00000000..b41c8016
--- /dev/null
+++ b/nms-patches/BlockButtonAbstract.patch
@@ -0,0 +1,111 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockButtonAbstract.java 2014-11-27 08:59:46.505422709 +1100
++++ src/main/java/net/minecraft/server/BlockButtonAbstract.java 2014-11-27 08:42:10.160850895 +1100
+@@ -3,6 +3,11 @@
+ import java.util.List;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.event.block.BlockRedstoneEvent;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
++
+ public abstract class BlockButtonAbstract extends Block {
+
+ public static final BlockStateDirection FACING = BlockStateDirection.of("facing");
+@@ -122,6 +127,19 @@
+ if (((Boolean) iblockdata.get(BlockButtonAbstract.POWERED)).booleanValue()) {
+ return true;
+ } else {
++ // CraftBukkit start
++ boolean powered = ((Boolean) iblockdata.get(POWERED));
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++ return true;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, iblockdata.set(BlockButtonAbstract.POWERED, Boolean.valueOf(true)), 3);
+ world.b(blockposition, blockposition);
+ world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "random.click", 0.3F, 0.6F);
+@@ -159,6 +177,16 @@
+ if (this.M) {
+ this.f(world, blockposition, iblockdata);
+ } else {
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() > 0) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, iblockdata.set(BlockButtonAbstract.POWERED, Boolean.valueOf(false)));
+ this.b(world, blockposition, (EnumDirection) iblockdata.get(BlockButtonAbstract.FACING));
+ world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "random.click", 0.3F, 0.5F);
+@@ -192,8 +220,42 @@
+ List list = world.a(EntityArrow.class, new AxisAlignedBB((double) blockposition.getX() + this.minX, (double) blockposition.getY() + this.minY, (double) blockposition.getZ() + this.minZ, (double) blockposition.getX() + this.maxX, (double) blockposition.getY() + this.maxY, (double) blockposition.getZ() + this.maxZ));
+ boolean flag = !list.isEmpty();
+ boolean flag1 = ((Boolean) iblockdata.get(BlockButtonAbstract.POWERED)).booleanValue();
++
++ // CraftBukkit start - Call interact event when arrows turn on wooden buttons
++ if (flag1 != flag && flag) {
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ boolean allowed = false;
++
++ // If all of the events are cancelled block the button press, else allow
++ for (Object object : list) {
++ if (object != null) {
++ EntityInteractEvent event = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ allowed = true;
++ break;
++ }
++ }
++ }
++
++ if (!allowed) {
++ return;
++ }
++ }
++ // CraftBukkit end
+
+ if (flag && !flag1) {
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 0, 15);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() <= 0) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, iblockdata.set(BlockButtonAbstract.POWERED, Boolean.valueOf(true)));
+ this.b(world, blockposition, (EnumDirection) iblockdata.get(BlockButtonAbstract.FACING));
+ world.b(blockposition, blockposition);
+@@ -201,6 +263,16 @@
+ }
+
+ if (!flag && flag1) {
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() > 0) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, iblockdata.set(BlockButtonAbstract.POWERED, Boolean.valueOf(false)));
+ this.b(world, blockposition, (EnumDirection) iblockdata.get(BlockButtonAbstract.FACING));
+ world.b(blockposition, blockposition);
diff --git a/nms-patches/BlockCactus.patch b/nms-patches/BlockCactus.patch
new file mode 100644
index 00000000..7159a89e
--- /dev/null
+++ b/nms-patches/BlockCactus.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockCactus.java 2014-11-27 08:59:46.509422692 +1100
++++ src/main/java/net/minecraft/server/BlockCactus.java 2014-11-27 08:42:10.152850911 +1100
+@@ -3,6 +3,8 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockCactus extends Block {
+
+ public static final BlockStateInteger AGE = BlockStateInteger.of("age", 0, 15);
+@@ -31,7 +33,8 @@
+ world.setTypeUpdate(blockposition1, this.getBlockData());
+ IBlockData iblockdata1 = iblockdata.set(BlockCactus.AGE, Integer.valueOf(0));
+
+- world.setTypeAndData(blockposition, iblockdata1, 4);
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ(), this, 0); // CraftBukkit
++ // world.setTypeAndData(blockposition, iblockdata1, 4); // CraftBukkit
+ this.doPhysics(world, blockposition1, iblockdata1, this);
+ } else {
+ world.setTypeAndData(blockposition, iblockdata.set(BlockCactus.AGE, Integer.valueOf(j + 1)), 4);
+@@ -83,7 +86,9 @@
+ }
+
+ public void a(World world, BlockPosition blockposition, IBlockData iblockdata, Entity entity) {
++ CraftEventFactory.blockDamage = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); // CraftBukkit
+ entity.damageEntity(DamageSource.CACTUS, 1.0F);
++ CraftEventFactory.blockDamage = null; // CraftBukkit
+ }
+
+ public IBlockData fromLegacyData(int i) {
diff --git a/nms-patches/BlockCake.patch b/nms-patches/BlockCake.patch
new file mode 100644
index 00000000..facc1d4f
--- /dev/null
+++ b/nms-patches/BlockCake.patch
@@ -0,0 +1,22 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockCake.java 2014-11-27 08:59:46.509422692 +1100
++++ src/main/java/net/minecraft/server/BlockCake.java 2014-11-27 08:42:10.168850880 +1100
+@@ -54,7 +54,18 @@
+
+ private void b(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman) {
+ if (entityhuman.j(false)) {
+- entityhuman.getFoodData().eat(2, 0.1F);
++ // CraftBukkit start
++ // entityhuman.getFoodData().eat(2, 0.1F);
++ int oldFoodLevel = entityhuman.getFoodData().foodLevel;
++
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityhuman, 2 + oldFoodLevel);
++
++ if (!event.isCancelled()) {
++ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F);
++ }
++
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutUpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel));
++ // CraftBukkit end
+ int i = ((Integer) iblockdata.get(BlockCake.BITES)).intValue();
+
+ if (i < 6) {
diff --git a/nms-patches/BlockCocoa.patch b/nms-patches/BlockCocoa.patch
new file mode 100644
index 00000000..40006fff
--- /dev/null
+++ b/nms-patches/BlockCocoa.patch
@@ -0,0 +1,35 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockCocoa.java 2014-11-27 08:59:46.513422674 +1100
++++ src/main/java/net/minecraft/server/BlockCocoa.java 2014-11-27 08:42:10.152850911 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockCocoa extends BlockDirectional implements IBlockFragilePlantElement {
+
+ public static final BlockStateInteger AGE = BlockStateInteger.of("age", 0, 2);
+@@ -19,7 +21,10 @@
+ int i = ((Integer) iblockdata.get(BlockCocoa.AGE)).intValue();
+
+ if (i < 2) {
+- world.setTypeAndData(blockposition, iblockdata.set(BlockCocoa.AGE, Integer.valueOf(i + 1)), 2);
++ // CraftBukkit start
++ IBlockData data = iblockdata.set(AGE, Integer.valueOf(i + 1));
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(data));
++ // CraftBukkit end
+ }
+ }
+
+@@ -125,7 +130,10 @@
+ }
+
+ public void b(World world, Random random, BlockPosition blockposition, IBlockData iblockdata) {
+- world.setTypeAndData(blockposition, iblockdata.set(BlockCocoa.AGE, Integer.valueOf(((Integer) iblockdata.get(BlockCocoa.AGE)).intValue() + 1)), 2);
++ // CraftBukkit start
++ IBlockData data = iblockdata.set(AGE, Integer.valueOf(((Integer) iblockdata.get(AGE)).intValue() + 1));
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(data));
++ // CraftBukkit end
+ }
+
+ public IBlockData fromLegacyData(int i) {
diff --git a/nms-patches/BlockCommand.patch b/nms-patches/BlockCommand.patch
new file mode 100644
index 00000000..e7fe808d
--- /dev/null
+++ b/nms-patches/BlockCommand.patch
@@ -0,0 +1,34 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockCommand.java 2014-11-27 08:59:46.513422674 +1100
++++ src/main/java/net/minecraft/server/BlockCommand.java 2014-11-27 08:42:10.152850911 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockCommand extends BlockContainer {
+
+ public static final BlockStateBoolean TRIGGERED = BlockStateBoolean.of("triggered");
+@@ -19,11 +21,20 @@
+ if (!world.isStatic) {
+ boolean flag = world.isBlockIndirectlyPowered(blockposition);
+ boolean flag1 = ((Boolean) iblockdata.get(BlockCommand.TRIGGERED)).booleanValue();
++
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ int old = flag1 ? 15 : 0;
++ int current = flag ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, old, current);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++ // CraftBukkit end
+
+- if (flag && !flag1) {
++ if (eventRedstone.getNewCurrent() > 0 && !(eventRedstone.getOldCurrent() > 0)) { // CraftBukkit
+ world.setTypeAndData(blockposition, iblockdata.set(BlockCommand.TRIGGERED, Boolean.valueOf(true)), 4);
+ world.a(blockposition, (Block) this, this.a(world));
+- } else if (!flag && flag1) {
++ } else if (!(eventRedstone.getNewCurrent() > 0) && eventRedstone.getOldCurrent() > 0) { // CraftBukkit
+ world.setTypeAndData(blockposition, iblockdata.set(BlockCommand.TRIGGERED, Boolean.valueOf(false)), 4);
+ }
+ }
diff --git a/nms-patches/BlockCrops.patch b/nms-patches/BlockCrops.patch
new file mode 100644
index 00000000..fde60f42
--- /dev/null
+++ b/nms-patches/BlockCrops.patch
@@ -0,0 +1,35 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockCrops.java 2014-11-27 08:59:46.517422657 +1100
++++ src/main/java/net/minecraft/server/BlockCrops.java 2014-11-27 08:42:10.160850895 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockCrops extends BlockPlant implements IBlockFragilePlantElement {
+
+ public static final BlockStateInteger AGE = BlockStateInteger.of("age", 0, 7);
+@@ -31,7 +33,10 @@
+ float f = a((Block) this, world, blockposition);
+
+ if (random.nextInt((int) (25.0F / f) + 1) == 0) {
+- world.setTypeAndData(blockposition, iblockdata.set(BlockCrops.AGE, Integer.valueOf(i + 1)), 2);
++ // CraftBukkit start
++ IBlockData data = iblockdata.set(AGE, Integer.valueOf(i + 1));
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(data));
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -45,7 +50,10 @@
+ i = 7;
+ }
+
+- world.setTypeAndData(blockposition, iblockdata.set(BlockCrops.AGE, Integer.valueOf(i)), 2);
++ // CraftBukkit start
++ IBlockData data = iblockdata.set(AGE, Integer.valueOf(i));
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(data));
++ // CraftBukkit end
+ }
+
+ protected static float a(Block block, World world, BlockPosition blockposition) {
diff --git a/nms-patches/BlockDaylightDetector.patch b/nms-patches/BlockDaylightDetector.patch
new file mode 100644
index 00000000..7f37a071
--- /dev/null
+++ b/nms-patches/BlockDaylightDetector.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDaylightDetector.java 2014-11-27 08:59:46.517422657 +1100
++++ src/main/java/net/minecraft/server/BlockDaylightDetector.java 2014-11-27 08:42:10.164850887 +1100
+@@ -41,6 +41,7 @@
+ }
+
+ if (((Integer) iblockdata.get(BlockDaylightDetector.POWER)).intValue() != i) {
++ i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), ((Integer) iblockdata.get(POWER)), i).getNewCurrent(); // CraftBukkit - Call BlockRedstoneEvent
+ world.setTypeAndData(blockposition, iblockdata.set(BlockDaylightDetector.POWER, Integer.valueOf(i)), 3);
+ }
+
diff --git a/nms-patches/BlockDiodeAbstract.patch b/nms-patches/BlockDiodeAbstract.patch
new file mode 100644
index 00000000..f0c210d8
--- /dev/null
+++ b/nms-patches/BlockDiodeAbstract.patch
@@ -0,0 +1,30 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDiodeAbstract.java 2014-11-27 08:59:46.517422657 +1100
++++ src/main/java/net/minecraft/server/BlockDiodeAbstract.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public abstract class BlockDiodeAbstract extends BlockDirectional {
+
+ protected final boolean M;
+@@ -31,8 +33,18 @@
+ boolean flag = this.e(world, blockposition, iblockdata);
+
+ if (this.M && !flag) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, this.k(iblockdata), 2);
+ } else if (!this.M) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, this.e(iblockdata), 2);
+ if (!flag) {
+ world.a(blockposition, this.e(iblockdata).getBlock(), this.m(iblockdata), -1);
diff --git a/nms-patches/BlockDispenser.patch b/nms-patches/BlockDispenser.patch
new file mode 100644
index 00000000..35ad0d58
--- /dev/null
+++ b/nms-patches/BlockDispenser.patch
@@ -0,0 +1,18 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDispenser.java 2014-11-27 08:59:46.521422639 +1100
++++ src/main/java/net/minecraft/server/BlockDispenser.java 2014-11-27 08:42:10.084851043 +1100
+@@ -8,6 +8,7 @@
+ public static final BlockStateBoolean TRIGGERED = BlockStateBoolean.of("triggered");
+ public static final RegistryDefault M = new RegistryDefault(new DispenseBehaviorItem());
+ protected Random N = new Random();
++ public static boolean eventFired = false; // CraftBukkit
+
+ protected BlockDispenser() {
+ super(Material.STONE);
+@@ -78,6 +79,7 @@
+
+ if (idispensebehavior != IDispenseBehavior.a) {
+ ItemStack itemstack1 = idispensebehavior.a(sourceblock, itemstack);
++ eventFired = false; // CraftBukkit - reset event status
+
+ tileentitydispenser.setItem(i, itemstack1.count == 0 ? null : itemstack1);
+ }
diff --git a/nms-patches/BlockDoor.patch b/nms-patches/BlockDoor.patch
new file mode 100644
index 00000000..a14fff0f
--- /dev/null
+++ b/nms-patches/BlockDoor.patch
@@ -0,0 +1,43 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDoor.java 2014-11-27 08:59:46.521422639 +1100
++++ src/main/java/net/minecraft/server/BlockDoor.java 2014-11-27 08:42:10.156850903 +1100
+@@ -3,6 +3,8 @@
+ import com.google.common.base.Predicate;
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockDoor extends Block {
+
+ public static final BlockStateDirection FACING = BlockStateDirection.of("facing", (Predicate) EnumDirectionLimit.HORIZONTAL);
+@@ -151,9 +153,21 @@
+ this.b(world, blockposition, iblockdata, 0);
+ }
+ } else {
+- boolean flag1 = world.isBlockIndirectlyPowered(blockposition) || world.isBlockIndirectlyPowered(blockposition2);
++ // CraftBukkit start
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block blockTop = bworld.getBlockAt(blockposition2.getX(), blockposition2.getY(), blockposition2.getZ());
++
++ int power = bukkitBlock.getBlockPower();
++ int powerTop = blockTop.getBlockPower();
++ if (powerTop > power) power = powerTop;
++ int oldPower = (Boolean)iblockdata2.get(POWERED) ? 15 : 0;
++
++ if (oldPower == 0 ^ power == 0) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, oldPower, power);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
+
+- if ((flag1 || block.isPowerSource()) && block != this && flag1 != ((Boolean) iblockdata2.get(BlockDoor.POWERED)).booleanValue()) {
++ boolean flag1 = eventRedstone.getNewCurrent() > 0;
+ world.setTypeAndData(blockposition2, iblockdata2.set(BlockDoor.POWERED, Boolean.valueOf(flag1)), 2);
+ if (flag1 != ((Boolean) iblockdata.get(BlockDoor.OPEN)).booleanValue()) {
+ world.setTypeAndData(blockposition, iblockdata.set(BlockDoor.OPEN, Boolean.valueOf(flag1)), 2);
+@@ -161,6 +175,7 @@
+ world.a((EntityHuman) null, flag1 ? 1003 : 1006, blockposition, 0);
+ }
+ }
++ // CraftBukkit end
+ }
+ }
+
diff --git a/nms-patches/BlockDragonEgg.patch b/nms-patches/BlockDragonEgg.patch
new file mode 100644
index 00000000..d2415b9b
--- /dev/null
+++ b/nms-patches/BlockDragonEgg.patch
@@ -0,0 +1,30 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDragonEgg.java 2014-11-27 08:59:46.525422622 +1100
++++ src/main/java/net/minecraft/server/BlockDragonEgg.java 2014-11-27 08:42:10.152850911 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockFromToEvent; // CraftBukkit
++
+ public class BlockDragonEgg extends Block {
+
+ public BlockDragonEgg() {
+@@ -61,6 +63,18 @@
+ BlockPosition blockposition1 = blockposition.a(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16));
+
+ if (world.getType(blockposition1).getBlock().material == Material.AIR) {
++ // CraftBukkit start
++ org.bukkit.block.Block from = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block to = world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
++ BlockFromToEvent event = new BlockFromToEvent(from, to);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ blockposition1 = new BlockPosition(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ());
++ // CraftBukkit end
+ if (world.isStatic) {
+ for (int j = 0; j < 128; ++j) {
+ double d0 = world.random.nextDouble();
diff --git a/nms-patches/BlockDropper.patch b/nms-patches/BlockDropper.patch
new file mode 100644
index 00000000..43df579c
--- /dev/null
+++ b/nms-patches/BlockDropper.patch
@@ -0,0 +1,41 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockDropper.java 2014-11-27 08:59:46.525422622 +1100
++++ src/main/java/net/minecraft/server/BlockDropper.java 2014-11-27 08:42:10.124850965 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.inventory.InventoryMoveItemEvent;
++// CraftBukkit end
++
+ public class BlockDropper extends BlockDispenser {
+
+ private final IDispenseBehavior O = new DispenseBehaviorItem();
+@@ -38,8 +43,25 @@
+ itemstack1 = null;
+ }
+ } else {
+- itemstack1 = TileEntityHopper.addItem(iinventory, itemstack.cloneItemStack().a(1), enumdirection.opposite());
+- if (itemstack1 == null) {
++ // CraftBukkit start - Fire event when pushing items into other inventories
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack.cloneItemStack().a(1));
++
++ org.bukkit.inventory.Inventory destinationInventory;
++ // Have to special case large chests as they work oddly
++ if (iinventory instanceof InventoryLargeChest) {
++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
++ } else {
++ destinationInventory = iinventory.getOwner().getInventory();
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentitydispenser.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
++ world.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ itemstack1 = TileEntityHopper.addItem(iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection.opposite());
++ if (event.getItem().equals(oitemstack) && itemstack1 == null) {
++ // CraftBukkit end
+ itemstack1 = itemstack.cloneItemStack();
+ if (--itemstack1.count == 0) {
+ itemstack1 = null;
diff --git a/nms-patches/BlockEnderPortal.patch b/nms-patches/BlockEnderPortal.patch
new file mode 100644
index 00000000..ae5743c2
--- /dev/null
+++ b/nms-patches/BlockEnderPortal.patch
@@ -0,0 +1,22 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockEnderPortal.java 2014-11-27 08:59:46.529422604 +1100
++++ src/main/java/net/minecraft/server/BlockEnderPortal.java 2014-11-27 08:42:10.104851005 +1100
+@@ -3,6 +3,8 @@
+ import java.util.List;
+ import java.util.Random;
+
++import org.bukkit.event.entity.EntityPortalEnterEvent; // CraftBukkit
++
+ public class BlockEnderPortal extends BlockContainer {
+
+ protected BlockEnderPortal(Material material) {
+@@ -36,6 +38,10 @@
+
+ public void a(World world, BlockPosition blockposition, IBlockData iblockdata, Entity entity) {
+ if (entity.vehicle == null && entity.passenger == null && !world.isStatic) {
++ // CraftBukkit start - Entity in portal
++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
+ entity.c(1);
+ }
+
diff --git a/nms-patches/BlockFire.patch b/nms-patches/BlockFire.patch
new file mode 100644
index 00000000..0abc00c0
--- /dev/null
+++ b/nms-patches/BlockFire.patch
@@ -0,0 +1,110 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockFire.java 2014-11-27 08:59:46.529422604 +1100
++++ src/main/java/net/minecraft/server/BlockFire.java 2014-11-27 08:42:10.168850880 +1100
+@@ -4,6 +4,12 @@
+ import java.util.Map;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.BlockBurnEvent;
++import org.bukkit.event.block.BlockSpreadEvent;
++// CraftBukkit end
++
+ public class BlockFire extends Block {
+
+ public static final BlockStateInteger AGE = BlockStateInteger.of("age", 0, 15);
+@@ -109,7 +115,7 @@
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (world.getGameRules().getBoolean("doFireTick")) {
+ if (!this.canPlace(world, blockposition)) {
+- world.setAir(blockposition);
++ fireExtinguished(world, blockposition); // CraftBukkit - invalid place location
+ }
+
+ Block block = world.getType(blockposition.down()).getBlock();
+@@ -120,7 +126,7 @@
+ }
+
+ if (!flag && world.S() && this.d(world, blockposition)) {
+- world.setAir(blockposition);
++ fireExtinguished(world, blockposition); // CraftBukkit - extinguished by rain
+ } else {
+ int i = ((Integer) iblockdata.get(BlockFire.AGE)).intValue();
+
+@@ -186,7 +192,26 @@
+ l1 = 15;
+ }
+
+- world.setTypeAndData(blockposition1, iblockdata.set(BlockFire.AGE, Integer.valueOf(l1)), 3);
++ // CraftBukkit start - Call to stop spread of fire
++ if (world.getType(blockposition1) != Blocks.FIRE) {
++ if (CraftEventFactory.callBlockIgniteEvent(world, i1, k1, j1, i, j, k).isCancelled()) {
++ continue;
++ }
++
++ org.bukkit.Server server = world.getServer();
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.block.BlockState blockState = bworld.getBlockAt(i1, k1, j1).getState();
++ blockState.setTypeId(Block.getId(this));
++ blockState.setData(new org.bukkit.material.MaterialData(Block.getId(this), (byte) l1));
++
++ BlockSpreadEvent spreadEvent = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(i, j, k), blockState);
++ server.getPluginManager().callEvent(spreadEvent);
++
++ if (!spreadEvent.isCancelled()) {
++ blockState.update(true);
++ }
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -223,6 +248,17 @@
+
+ if (random.nextInt(i) < k) {
+ IBlockData iblockdata = world.getType(blockposition);
++
++ // CraftBukkit start
++ org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockBurnEvent event = new BlockBurnEvent(theBlock);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+
+ if (random.nextInt(j + 10) < 5 && !world.isRainingAt(blockposition)) {
+ int l = j + random.nextInt(5) / 4;
+@@ -290,7 +326,7 @@
+
+ public void doPhysics(World world, BlockPosition blockposition, IBlockData iblockdata, Block block) {
+ if (!World.a((IBlockAccess) world, blockposition.down()) && !this.e(world, blockposition)) {
+- world.setAir(blockposition);
++ fireExtinguished(world, blockposition); // CraftBukkit - fuel block gone
+ }
+
+ }
+@@ -298,7 +334,7 @@
+ public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) {
+ if (world.worldProvider.getDimension() > 0 || !Blocks.PORTAL.d(world, blockposition)) {
+ if (!World.a((IBlockAccess) world, blockposition.down()) && !this.e(world, blockposition)) {
+- world.setAir(blockposition);
++ fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke
+ } else {
+ world.a(blockposition, (Block) this, this.a(world) + world.random.nextInt(10));
+ }
+@@ -320,4 +356,12 @@
+ protected BlockStateList getStateList() {
+ return new BlockStateList(this, new IBlockState[] { BlockFire.AGE, BlockFire.NORTH, BlockFire.EAST, BlockFire.SOUTH, BlockFire.WEST, BlockFire.UPPER, BlockFire.FLIP, BlockFire.ALT});
+ }
++
++ // CraftBukkit start
++ private void fireExtinguished(World world, BlockPosition position) {
++ if (!CraftEventFactory.callBlockFadeEvent(world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), Blocks.AIR).isCancelled()) {
++ world.setAir(position);
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/BlockFlowing.patch b/nms-patches/BlockFlowing.patch
new file mode 100644
index 00000000..84b3101d
--- /dev/null
+++ b/nms-patches/BlockFlowing.patch
@@ -0,0 +1,83 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockFlowing.java 2014-11-27 08:59:46.529422604 +1100
++++ src/main/java/net/minecraft/server/BlockFlowing.java 2014-11-27 08:42:10.112850989 +1100
+@@ -5,6 +5,11 @@
+ import java.util.Random;
+ import java.util.Set;
+
++// CraftBukkit start
++import org.bukkit.block.BlockFace;
++import org.bukkit.event.block.BlockFromToEvent;
++// CraftBukkit end
++
+ public class BlockFlowing extends BlockFluids {
+
+ int a;
+@@ -18,7 +23,12 @@
+ }
+
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+- int i = ((Integer) iblockdata.get(BlockFlowing.LEVEL)).intValue();
++ // CraftBukkit start
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.Server server = world.getServer();
++ org.bukkit.block.Block source = bworld == null ? null : bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ // CraftBukkit end
++ int i = ((Integer) iblockdata.get(LEVEL)).intValue();
+ byte b0 = 1;
+
+ if (this.material == Material.LAVA && !world.worldProvider.n()) {
+@@ -88,17 +98,25 @@
+ IBlockData iblockdata2 = world.getType(blockposition.down());
+
+ if (this.h(world, blockposition.down(), iblockdata2)) {
+- if (this.material == Material.LAVA && world.getType(blockposition.down()).getBlock().getMaterial() == Material.WATER) {
+- world.setTypeUpdate(blockposition.down(), Blocks.STONE.getBlockData());
+- this.fizz(world, blockposition.down());
+- return;
+- }
++ // CraftBukkit start - Send "down" to the server
++ BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN);
++ if (server != null) {
++ server.getPluginManager().callEvent(event);
++ }
++ if (!event.isCancelled()) {
++ if (this.material == Material.LAVA && world.getType(blockposition.down()).getBlock().getMaterial() == Material.WATER) {
++ world.setTypeUpdate(blockposition.down(), Blocks.STONE.getBlockData());
++ this.fizz(world, blockposition.down());
++ return;
++ }
+
+- if (i >= 8) {
+- this.flow(world, blockposition.down(), iblockdata2, i);
+- } else {
+- this.flow(world, blockposition.down(), iblockdata2, i + 8);
++ if (i >= 8) {
++ this.flow(world, blockposition.down(), iblockdata2, i);
++ } else {
++ this.flow(world, blockposition.down(), iblockdata2, i + 8);
++ }
+ }
++ // CraftBukkit end
+ } else if (i >= 0 && (i == 0 || this.g(world, blockposition.down(), iblockdata2))) {
+ Set set = this.e(world, blockposition);
+
+@@ -115,8 +133,17 @@
+
+ while (iterator1.hasNext()) {
+ EnumDirection enumdirection1 = (EnumDirection) iterator1.next();
+-
+- this.flow(world, blockposition.shift(enumdirection1), world.getType(blockposition.shift(enumdirection1)), k);
++
++ // CraftBukkit start
++ BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection1));
++ if (server != null) {
++ server.getPluginManager().callEvent(event);
++ }
++
++ if (!event.isCancelled()) {
++ this.flow(world, blockposition.shift(enumdirection1), world.getType(blockposition.shift(enumdirection1)), k);
++ }
++ // CraftBukkit end
+ }
+ }
+
diff --git a/nms-patches/BlockGrass.patch b/nms-patches/BlockGrass.patch
new file mode 100644
index 00000000..b52f01fd
--- /dev/null
+++ b/nms-patches/BlockGrass.patch
@@ -0,0 +1,77 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockGrass.java 2014-11-27 08:59:46.533422586 +1100
++++ src/main/java/net/minecraft/server/BlockGrass.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,14 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.event.block.BlockSpreadEvent;
++import org.bukkit.event.block.BlockFadeEvent;
++// CraftBukkit end
++
+ public class BlockGrass extends Block implements IBlockFragilePlantElement {
+
+ public static final BlockStateBoolean SNOWY = BlockStateBoolean.of("snowy");
+@@ -22,7 +30,19 @@
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (!world.isStatic) {
+ if (world.getLightLevel(blockposition.up()) < 4 && world.getType(blockposition.up()).getBlock().n() > 2) {
+- world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData());
++ // CraftBukkit start
++ // world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData());
++ org.bukkit.World bworld = world.getWorld();
++ BlockState blockState = bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getState();
++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.DIRT));
++
++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ } else {
+ if (world.getLightLevel(blockposition.up()) >= 9) {
+ for (int i = 0; i < 4; ++i) {
+@@ -31,7 +51,19 @@
+ IBlockData iblockdata1 = world.getType(blockposition1);
+
+ if (iblockdata1.getBlock() == Blocks.DIRT && iblockdata1.get(BlockDirt.VARIANT) == EnumDirtVariant.DIRT && world.getLightLevel(blockposition1.up()) >= 4 && block.n() <= 2) {
+- world.setTypeUpdate(blockposition1, Blocks.GRASS.getBlockData());
++ // CraftBukkit start
++ // world.setTypeUpdate(blockposition1, Blocks.GRASS.getBlockData());
++ org.bukkit.World bworld = world.getWorld();
++ BlockState blockState = bworld.getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()).getState();
++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.GRASS));
++
++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), blockState);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -74,13 +106,15 @@
+ IBlockData iblockdata1 = blockflowers.getBlockData().set(blockflowers.l(), enumflowervarient);
+
+ if (blockflowers.f(world, blockposition2, iblockdata1)) {
+- world.setTypeAndData(blockposition2, iblockdata1, 3);
++ // world.setTypeAndData(blockposition2, iblockdata1, 3); // CraftBukkit
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition2.getX(), blockposition2.getY(), blockposition2.getZ(), iblockdata1.getBlock(), iblockdata1.getBlock().toLegacyData(iblockdata1)); // CraftBukkit
+ }
+ } else {
+ IBlockData iblockdata2 = Blocks.TALLGRASS.getBlockData().set(BlockLongGrass.TYPE, EnumTallGrassType.GRASS);
+
+ if (Blocks.TALLGRASS.f(world, blockposition2, iblockdata2)) {
+- world.setTypeAndData(blockposition2, iblockdata2, 3);
++ // world.setTypeAndData(blockposition2, iblockdata2, 3); // CRaftBukkit
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition2.getX(), blockposition2.getY(), blockposition2.getZ(), iblockdata2.getBlock(), iblockdata2.getBlock().toLegacyData(iblockdata2)); // CraftBukkit
+ }
+ }
+ }
diff --git a/nms-patches/BlockIce.patch b/nms-patches/BlockIce.patch
new file mode 100644
index 00000000..91033f00
--- /dev/null
+++ b/nms-patches/BlockIce.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockIce.java 2014-11-27 08:59:46.533422586 +1100
++++ src/main/java/net/minecraft/server/BlockIce.java 2014-11-27 08:42:10.168850880 +1100
+@@ -44,6 +44,12 @@
+
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (world.b(EnumSkyBlock.BLOCK, blockposition) > 11 - this.n()) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), world.worldProvider.n() ? Blocks.AIR : Blocks.WATER).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++
+ if (world.worldProvider.n()) {
+ world.setAir(blockposition);
+ } else {
diff --git a/nms-patches/BlockLeaves.patch b/nms-patches/BlockLeaves.patch
new file mode 100644
index 00000000..067c5808
--- /dev/null
+++ b/nms-patches/BlockLeaves.patch
@@ -0,0 +1,26 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockLeaves.java 2014-11-27 08:59:46.537422569 +1100
++++ src/main/java/net/minecraft/server/BlockLeaves.java 2014-11-27 08:42:10.132850949 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.block.LeavesDecayEvent; // CraftBukkit
++
+ public abstract class BlockLeaves extends BlockTransparent {
+
+ public static final BlockStateBoolean DECAYABLE = BlockStateBoolean.of("decayable");
+@@ -128,6 +130,14 @@
+ }
+
+ private void d(World world, BlockPosition blockposition) {
++ // CraftBukkit start
++ LeavesDecayEvent event = new LeavesDecayEvent(world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.b(world, blockposition, world.getType(blockposition), 0);
+ world.setAir(blockposition);
+ }
diff --git a/nms-patches/BlockLever.patch b/nms-patches/BlockLever.patch
new file mode 100644
index 00000000..fa9482b1
--- /dev/null
+++ b/nms-patches/BlockLever.patch
@@ -0,0 +1,32 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockLever.java 2014-11-27 08:59:46.541422551 +1100
++++ src/main/java/net/minecraft/server/BlockLever.java 2014-11-27 08:42:10.168850880 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Iterator;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockLever extends Block {
+
+ public static final BlockStateEnum FACING = BlockStateEnum.of("facing", EnumLeverPosition.class);
+@@ -144,6 +146,20 @@
+ if (world.isStatic) {
+ return true;
+ } else {
++ // CraftBukkit start - Interact Lever
++ boolean powered = (Boolean)iblockdata.get(POWERED);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++ return true;
++ }
++ // CraftBukkit end
++
+ iblockdata = iblockdata.a(BlockLever.POWERED);
+ world.setTypeAndData(blockposition, iblockdata, 3);
+ world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "random.click", 0.3F, ((Boolean) iblockdata.get(BlockLever.POWERED)).booleanValue() ? 0.6F : 0.5F);
diff --git a/nms-patches/BlockMinecartDetector.patch b/nms-patches/BlockMinecartDetector.patch
new file mode 100644
index 00000000..4e33f431
--- /dev/null
+++ b/nms-patches/BlockMinecartDetector.patch
@@ -0,0 +1,29 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockMinecartDetector.java 2014-11-27 08:59:46.541422551 +1100
++++ src/main/java/net/minecraft/server/BlockMinecartDetector.java 2014-11-27 08:42:10.124850965 +1100
+@@ -4,6 +4,8 @@
+ import java.util.List;
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockMinecartDetector extends BlockMinecartTrackAbstract {
+
+ public static final BlockStateEnum SHAPE = BlockStateEnum.a("shape", EnumTrackPosition.class, (Predicate) (new BlockMinecartDetectorInnerClass1()));
+@@ -55,6 +57,17 @@
+ if (!list.isEmpty()) {
+ flag1 = true;
+ }
++
++ // CraftBukkit start
++ if (flag != flag1) {
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, flag ? 15 : 0, flag1 ? 15 : 0);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ }
++ // CraftBukkit end
+
+ if (flag1 && !flag) {
+ world.setTypeAndData(blockposition, iblockdata.set(BlockMinecartDetector.POWERED, Boolean.valueOf(true)), 3);
diff --git a/nms-patches/BlockMobSpawner.patch b/nms-patches/BlockMobSpawner.patch
new file mode 100644
index 00000000..5be7f790
--- /dev/null
+++ b/nms-patches/BlockMobSpawner.patch
@@ -0,0 +1,22 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockMobSpawner.java 2014-11-27 08:59:46.541422551 +1100
++++ src/main/java/net/minecraft/server/BlockMobSpawner.java 2014-11-27 08:42:10.172850872 +1100
+@@ -22,9 +22,19 @@
+
+ public void dropNaturally(World world, BlockPosition blockposition, IBlockData iblockdata, float f, int i) {
+ super.dropNaturally(world, blockposition, iblockdata, f, i);
++ /* CraftBukkit start - Delegate to getExpDrop
+ int j = 15 + world.random.nextInt(15) + world.random.nextInt(15);
+
+ this.dropExperience(world, blockposition, j);
++ */
++ }
++
++ @Override
++ public int getExpDrop(World world, IBlockData iblockdata, int enchantmentLevel) {
++ int j = 15 + world.random.nextInt(15) + world.random.nextInt(15);
++
++ return j;
++ // CraftBukkit end
+ }
+
+ public boolean c() {
diff --git a/nms-patches/BlockMonsterEggs.patch b/nms-patches/BlockMonsterEggs.patch
new file mode 100644
index 00000000..9260842f
--- /dev/null
+++ b/nms-patches/BlockMonsterEggs.patch
@@ -0,0 +1,20 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockMonsterEggs.java 2014-11-27 08:59:46.545422534 +1100
++++ src/main/java/net/minecraft/server/BlockMonsterEggs.java 2014-11-27 08:42:10.136850942 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit
++
+ public class BlockMonsterEggs extends Block {
+
+ public static final BlockStateEnum VARIANT = BlockStateEnum.of("variant", EnumMonsterEggVarient.class);
+@@ -50,7 +52,7 @@
+ EntitySilverfish entitysilverfish = new EntitySilverfish(world);
+
+ entitysilverfish.setPositionRotation((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, 0.0F, 0.0F);
+- world.addEntity(entitysilverfish);
++ world.addEntity(entitysilverfish, SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason
+ entitysilverfish.y();
+ }
+
diff --git a/nms-patches/BlockMushroom.patch b/nms-patches/BlockMushroom.patch
new file mode 100644
index 00000000..5f2477b7
--- /dev/null
+++ b/nms-patches/BlockMushroom.patch
@@ -0,0 +1,57 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockMushroom.java 2014-11-27 08:59:46.545422534 +1100
++++ src/main/java/net/minecraft/server/BlockMushroom.java 2014-11-27 08:42:10.100851012 +1100
+@@ -3,6 +3,12 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.TreeType;
++import org.bukkit.block.BlockState;
++import org.bukkit.event.block.BlockSpreadEvent;
++// CraftBukkit end
++
+ public class BlockMushroom extends BlockPlant implements IBlockFragilePlantElement {
+
+ protected BlockMushroom() {
+@@ -13,6 +19,7 @@
+ }
+
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
++ final int sourceX = blockposition.getX(), sourceY = blockposition.getY(), sourceZ = blockposition.getZ(); // CraftBukkit
+ if (random.nextInt(25) == 0) {
+ int i = 5;
+ boolean flag = true;
+@@ -39,8 +46,20 @@
+ blockposition2 = blockposition.a(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1);
+ }
+
+- if (world.isEmpty(blockposition2) && this.f(world, blockposition2, this.getBlockData())) {
+- world.setTypeAndData(blockposition2, this.getBlockData(), 2);
++ if (world.isEmpty(blockposition2) && this.f(world, blockposition2, this.getBlockData())) {
++ // CraftBukkit start
++ // world.setTypeAndData(blockposition2, this.getBlockData(), 2);
++ org.bukkit.World bworld = world.getWorld();
++ BlockState blockState = bworld.getBlockAt(blockposition2.getX(), blockposition2.getY(), blockposition2.getZ()).getState();
++ blockState.setType(org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this)); // nms: this.id, 0, 2
++
++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(sourceX, sourceY, sourceZ), blockState);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -69,8 +88,10 @@
+ WorldGenHugeMushroom worldgenhugemushroom = null;
+
+ if (this == Blocks.BROWN_MUSHROOM) {
++ BlockSapling.treeType = TreeType.BROWN_MUSHROOM; // CraftBukkit
+ worldgenhugemushroom = new WorldGenHugeMushroom(0);
+ } else if (this == Blocks.RED_MUSHROOM) {
++ BlockSapling.treeType = TreeType.RED_MUSHROOM; // CraftBukkit
+ worldgenhugemushroom = new WorldGenHugeMushroom(1);
+ }
+
diff --git a/nms-patches/BlockMycel.patch b/nms-patches/BlockMycel.patch
new file mode 100644
index 00000000..0a0bd4fb
--- /dev/null
+++ b/nms-patches/BlockMycel.patch
@@ -0,0 +1,58 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockMycel.java 2014-11-27 08:59:46.549422516 +1100
++++ src/main/java/net/minecraft/server/BlockMycel.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,13 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.event.block.BlockFadeEvent;
++import org.bukkit.event.block.BlockSpreadEvent;
++// CraftBukkit end
++
+ public class BlockMycel extends Block {
+
+ public static final BlockStateBoolean SNOWY = BlockStateBoolean.of("snowy");
+@@ -22,7 +29,19 @@
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (!world.isStatic) {
+ if (world.getLightLevel(blockposition.up()) < 4 && world.getType(blockposition.up()).getBlock().n() > 2) {
+- world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData().set(BlockDirt.VARIANT, EnumDirtVariant.DIRT));
++ // CraftBukkit start
++ // world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData().set(BlockDirt.VARIANT, EnumDirtVariant.DIRT));
++ org.bukkit.World bworld = world.getWorld();
++ BlockState blockState = bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getState();
++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.DIRT));
++
++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ } else {
+ if (world.getLightLevel(blockposition.up()) >= 9) {
+ for (int i = 0; i < 4; ++i) {
+@@ -31,7 +50,19 @@
+ Block block = world.getType(blockposition1.up()).getBlock();
+
+ if (iblockdata1.getBlock() == Blocks.DIRT && iblockdata1.get(BlockDirt.VARIANT) == EnumDirtVariant.DIRT && world.getLightLevel(blockposition1.up()) >= 4 && block.n() <= 2) {
+- world.setTypeUpdate(blockposition1, this.getBlockData());
++ // CraftBukkit start
++ // world.setTypeUpdate(blockposition1, this.getBlockData());
++ org.bukkit.World bworld = world.getWorld();
++ BlockState blockState = bworld.getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()).getState();
++ blockState.setType(CraftMagicNumbers.getMaterial(this));
++
++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), blockState);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/BlockNetherWart.patch b/nms-patches/BlockNetherWart.patch
new file mode 100644
index 00000000..c6f458eb
--- /dev/null
+++ b/nms-patches/BlockNetherWart.patch
@@ -0,0 +1,12 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockNetherWart.java 2014-11-27 08:59:46.549422516 +1100
++++ src/main/java/net/minecraft/server/BlockNetherWart.java 2014-11-27 08:42:10.140850934 +1100
+@@ -28,7 +28,8 @@
+
+ if (i < 3 && random.nextInt(10) == 0) {
+ iblockdata = iblockdata.set(BlockNetherWart.AGE, Integer.valueOf(i + 1));
+- world.setTypeAndData(blockposition, iblockdata, 2);
++ // world.setTypeAndData(blockposition, iblockdata, 2); // CraftBukkit
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(iblockdata)); // CraftBukkit
+ }
+
+ super.b(world, blockposition, iblockdata, random);
diff --git a/nms-patches/BlockOre.patch b/nms-patches/BlockOre.patch
new file mode 100644
index 00000000..473fcbaf
--- /dev/null
+++ b/nms-patches/BlockOre.patch
@@ -0,0 +1,42 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockOre.java 2014-11-27 08:59:46.549422516 +1100
++++ src/main/java/net/minecraft/server/BlockOre.java 2014-11-27 08:42:10.144850927 +1100
+@@ -33,6 +33,7 @@
+
+ public void dropNaturally(World world, BlockPosition blockposition, IBlockData iblockdata, float f, int i) {
+ super.dropNaturally(world, blockposition, iblockdata, f, i);
++ /* CraftBukkit start - Delegated to getExpDrop
+ if (this.getDropType(iblockdata, world.random, i) != Item.getItemOf(this)) {
+ int j = 0;
+
+@@ -50,7 +51,31 @@
+
+ this.dropExperience(world, blockposition, j);
+ }
++ // */
++ }
++
++ @Override
++ public int getExpDrop(World world, IBlockData iblockdata, int i) {
++ if (this.getDropType(iblockdata, world.random, i) != Item.getItemOf(this)) {
++ int j = 0;
++
++ if (this == Blocks.COAL_ORE) {
++ j = MathHelper.nextInt(world.random, 0, 2);
++ } else if (this == Blocks.DIAMOND_ORE) {
++ j = MathHelper.nextInt(world.random, 3, 7);
++ } else if (this == Blocks.EMERALD_ORE) {
++ j = MathHelper.nextInt(world.random, 3, 7);
++ } else if (this == Blocks.LAPIS_ORE) {
++ j = MathHelper.nextInt(world.random, 2, 5);
++ } else if (this == Blocks.QUARTZ_ORE) {
++ j = MathHelper.nextInt(world.random, 2, 5);
++ }
++
++ return j;
++ }
+
++ return 0;
++ // CraftBukkit end
+ }
+
+ public int getDropData(World world, BlockPosition blockposition) {
diff --git a/nms-patches/BlockPiston.patch b/nms-patches/BlockPiston.patch
new file mode 100644
index 00000000..816eebdc
--- /dev/null
+++ b/nms-patches/BlockPiston.patch
@@ -0,0 +1,76 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPiston.java 2014-11-27 08:59:46.553422499 +1100
++++ src/main/java/net/minecraft/server/BlockPiston.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,6 +1,16 @@
+ package net.minecraft.server;
+
++import java.util.AbstractList;
++import java.util.Collection;
++import java.util.Iterator;
+ import java.util.List;
++import java.util.ListIterator;
++
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockPistonRetractEvent;
++import org.bukkit.event.block.BlockPistonExtendEvent;
++// CraftBukkit end
+
+ public class BlockPiston extends Block {
+
+@@ -52,10 +62,19 @@
+ boolean flag = this.b(world, blockposition, enumdirection);
+
+ if (flag && !((Boolean) iblockdata.get(BlockPiston.EXTENDED)).booleanValue()) {
+- if ((new PistonExtendsChecker(world, blockposition, enumdirection, true)).a()) {
++ if ((new PistonExtendsChecker(world, blockposition, enumdirection, true)).a()) {
+ world.playBlockAction(blockposition, this, 0, enumdirection.a());
+ }
+ } else if (!flag && ((Boolean) iblockdata.get(BlockPiston.EXTENDED)).booleanValue()) {
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, CraftBlock.notchToBlockFace(enumdirection));
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, iblockdata.set(BlockPiston.EXTENDED, Boolean.valueOf(false)), 2);
+ world.playBlockAction(blockposition, this, 1, enumdirection.a());
+ }
+@@ -286,6 +305,35 @@
+ if (!pistonextendschecker.a()) {
+ return false;
+ } else {
++ final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ final List moved = pistonextendschecker.getMovedBlocks();
++ final List broken = pistonextendschecker.getBrokenBlocks();
++
++ List<org.bukkit.block.Block> blocks = new AbstractList<org.bukkit.block.Block>() {
++
++ @Override
++ public int size() {
++ return moved.size() + broken.size();
++ }
++
++ @Override
++ public org.bukkit.block.Block get(int index) {
++ if (index >= size() || index < 0) {
++ throw new ArrayIndexOutOfBoundsException(index);
++ }
++ BlockPosition pos = (BlockPosition) (index < moved.size() ? moved.get(index) : broken.get(index - moved.size()));
++ return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ }
++ };
++
++ BlockPistonExtendEvent event = new BlockPistonExtendEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection));
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
+ int i = list.size() + list1.size();
+ Block[] ablock = new Block[i];
+ EnumDirection enumdirection1 = flag ? enumdirection : enumdirection.opposite();
diff --git a/nms-patches/BlockPortal.patch b/nms-patches/BlockPortal.patch
new file mode 100644
index 00000000..d575fc78
--- /dev/null
+++ b/nms-patches/BlockPortal.patch
@@ -0,0 +1,53 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPortal.java 2014-11-27 08:59:46.553422499 +1100
++++ src/main/java/net/minecraft/server/BlockPortal.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.entity.EntityPortalEnterEvent; // CraftBukkit
++
+ public class BlockPortal extends BlockHalfTransparent {
+
+ public static final BlockStateEnum AXIS = BlockStateEnum.of("axis", EnumAxis.class, new EnumAxis[] { EnumAxis.X, EnumAxis.Z});
+@@ -24,7 +26,8 @@
+ }
+
+ if (i > 0 && !world.getType(blockposition1.up()).getBlock().isOccluding()) {
+- Entity entity = ItemMonsterEgg.a(world, 57, (double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 1.1D, (double) blockposition1.getZ() + 0.5D);
++ // CraftBukkit - set spawn reason to NETHER_PORTAL
++ Entity entity = ItemMonsterEgg.spawnCreature(world, 57, (double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 1.1D, (double) blockposition1.getZ() + 0.5D, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL);
+
+ if (entity != null) {
+ entity.portalCooldown = entity.ar();
+@@ -66,14 +69,16 @@
+ PortalCreator portalcreator = new PortalCreator(world, blockposition, EnumAxis.X);
+
+ if (portalcreator.b() && PortalCreator.a(portalcreator) == 0) {
+- portalcreator.c();
+- return true;
++ // CraftBukkit start - return portalcreator
++ return portalcreator.c();
++ // return true;
+ } else {
+ PortalCreator portalcreator1 = new PortalCreator(world, blockposition, EnumAxis.Z);
+
+ if (portalcreator1.b() && PortalCreator.a(portalcreator1) == 0) {
+- portalcreator1.c();
+- return true;
++ return portalcreator1.c();
++ // return true;
++ // CraftBukkit end
+ } else {
+ return false;
+ }
+@@ -104,6 +109,10 @@
+
+ public void a(World world, BlockPosition blockposition, IBlockData iblockdata, Entity entity) {
+ if (entity.vehicle == null && entity.passenger == null) {
++ // CraftBukkit start - Entity in portal
++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
+ entity.aq();
+ }
+
diff --git a/nms-patches/BlockPoweredRail.patch b/nms-patches/BlockPoweredRail.patch
new file mode 100644
index 00000000..a43bdf22
--- /dev/null
+++ b/nms-patches/BlockPoweredRail.patch
@@ -0,0 +1,25 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPoweredRail.java 2014-11-27 08:59:46.557422481 +1100
++++ src/main/java/net/minecraft/server/BlockPoweredRail.java 2014-11-27 08:42:10.124850965 +1100
+@@ -2,6 +2,8 @@
+
+ import com.google.common.base.Predicate;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockPoweredRail extends BlockMinecartTrackAbstract {
+
+ public static final BlockStateEnum SHAPE = BlockStateEnum.a("shape", EnumTrackPosition.class, (Predicate) (new BlockPoweredRailInnerClass1()));
+@@ -108,6 +110,13 @@
+ boolean flag1 = world.isBlockIndirectlyPowered(blockposition) || this.a(world, blockposition, iblockdata, true, 0) || this.a(world, blockposition, iblockdata, false, 0);
+
+ if (flag1 != flag) {
++ // CraftBukkit start
++ int power = (Boolean)iblockdata.get(POWERED) ? 15 : 0;
++ int newPower = CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), power, 15 - power).getNewCurrent();
++ if (newPower == power) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, iblockdata.set(BlockPoweredRail.POWERED, Boolean.valueOf(flag1)), 3);
+ world.applyPhysics(blockposition.down(), this);
+ if (((EnumTrackPosition) iblockdata.get(BlockPoweredRail.SHAPE)).c()) {
diff --git a/nms-patches/BlockPressurePlateAbstract.patch b/nms-patches/BlockPressurePlateAbstract.patch
new file mode 100644
index 00000000..a226e9c4
--- /dev/null
+++ b/nms-patches/BlockPressurePlateAbstract.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPressurePlateAbstract.java 2014-11-27 08:59:46.557422481 +1100
++++ src/main/java/net/minecraft/server/BlockPressurePlateAbstract.java 2014-11-27 08:42:10.144850927 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public abstract class BlockPressurePlateAbstract extends Block {
+
+ protected BlockPressurePlateAbstract(Material material) {
+@@ -90,6 +92,19 @@
+ int j = this.e(world, blockposition);
+ boolean flag = i > 0;
+ boolean flag1 = j > 0;
++
++ // CraftBukkit start - Interact Pressure Plate
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.plugin.PluginManager manager = world.getServer().getPluginManager();
++
++ if (flag != flag1) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), i, j);
++ manager.callEvent(eventRedstone);
++
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ j = eventRedstone.getNewCurrent();
++ }
++ // CraftBukkit end
+
+ if (i != j) {
+ iblockdata = this.a(iblockdata, j);
diff --git a/nms-patches/BlockPressurePlateBinary.patch b/nms-patches/BlockPressurePlateBinary.patch
new file mode 100644
index 00000000..a1d38a16
--- /dev/null
+++ b/nms-patches/BlockPressurePlateBinary.patch
@@ -0,0 +1,38 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPressurePlateBinary.java 2014-11-27 08:59:46.557422481 +1100
++++ src/main/java/net/minecraft/server/BlockPressurePlateBinary.java 2014-11-27 08:42:10.152850911 +1100
+@@ -3,6 +3,8 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit
++
+ public class BlockPressurePlateBinary extends BlockPressurePlateAbstract {
+
+ public static final BlockStateBoolean POWERED = BlockStateBoolean.of("powered");
+@@ -44,6 +46,26 @@
+
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
++
++ // CraftBukkit start - Call interact event when turning on a pressure plate
++ if (this.e(world.getType(blockposition)) == 0) {
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.plugin.PluginManager manager = world.getServer().getPluginManager();
++ org.bukkit.event.Cancellable cancellable;
++
++ if (entity instanceof EntityHuman) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityHuman) entity, org.bukkit.event.block.Action.PHYSICAL, blockposition, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ manager.callEvent((EntityInteractEvent) cancellable);
++ }
++
++ // We only want to block turning the plate on if all events are cancelled
++ if (cancellable.isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
+
+ if (!entity.aH()) {
+ return 15;
diff --git a/nms-patches/BlockPressurePlateWeighted.patch b/nms-patches/BlockPressurePlateWeighted.patch
new file mode 100644
index 00000000..bd07b6a6
--- /dev/null
+++ b/nms-patches/BlockPressurePlateWeighted.patch
@@ -0,0 +1,43 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPressurePlateWeighted.java 2014-11-27 08:59:46.561422463 +1100
++++ src/main/java/net/minecraft/server/BlockPressurePlateWeighted.java 2014-11-27 08:42:10.160850895 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit
++
+ public class BlockPressurePlateWeighted extends BlockPressurePlateAbstract {
+
+ public static final BlockStateInteger POWER = BlockStateInteger.of("power", 0, 15);
+@@ -12,7 +14,31 @@
+ }
+
+ protected int e(World world, BlockPosition blockposition) {
+- int i = Math.min(world.a(Entity.class, this.a(blockposition)).size(), this.b);
++ // CraftBukkit start
++ //int i = Math.min(world.a(Entity.class, this.a(blockposition)).size(), this.b);
++ int i = 0;
++ java.util.Iterator iterator = world.a(Entity.class, this.a(blockposition)).iterator();
++
++ while (iterator.hasNext()) {
++ Entity entity = (Entity) iterator.next();
++
++ org.bukkit.event.Cancellable cancellable;
++
++ if (entity instanceof EntityHuman) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityHuman) entity, org.bukkit.event.block.Action.PHYSICAL, blockposition, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ // We only want to block turning the plate on if all events are cancelled
++ if (!cancellable.isCancelled()) {
++ i++;
++ }
++ }
++
++ i = Math.min(i, this.b);
++ // CraftBukkit end
+
+ if (i > 0) {
+ float f = (float) Math.min(this.b, i) / (float) this.b;
diff --git a/nms-patches/BlockPumpkin.patch b/nms-patches/BlockPumpkin.patch
new file mode 100644
index 00000000..abf3e858
--- /dev/null
+++ b/nms-patches/BlockPumpkin.patch
@@ -0,0 +1,117 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockPumpkin.java 2014-11-27 08:59:46.561422463 +1100
++++ src/main/java/net/minecraft/server/BlockPumpkin.java 2014-11-27 08:42:10.108850996 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.BlockStateListPopulator;
++import org.bukkit.event.block.BlockRedstoneEvent;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
++
+ public class BlockPumpkin extends BlockDirectional {
+
+ private ShapeDetector snowGolemPart;
+@@ -29,31 +35,45 @@
+ int j;
+
+ if ((shapedetectorcollection = this.getDetectorSnowGolem().a(world, blockposition)) != null) {
++ BlockStateListPopulator blockList = new BlockStateListPopulator(world.getWorld()); // CraftBukkit - Use BlockStateListPopulator
+ for (i = 0; i < this.getDetectorSnowGolem().b(); ++i) {
+ ShapeDetectorBlock shapedetectorblock = shapedetectorcollection.a(0, i, 0);
+
+- world.setTypeAndData(shapedetectorblock.d(), Blocks.AIR.getBlockData(), 2);
++ // CraftBukkit start
++ // world.setTypeAndData(shapedetectorblock.d(), Blocks.AIR.getBlockData(), 2);
++ BlockPosition pos = shapedetectorblock.d();
++ blockList.setTypeId(pos.getX(), pos.getY(), pos.getZ(), 0);
++ // CraftBukkit end
+ }
+
+ EntitySnowman entitysnowman = new EntitySnowman(world);
+ BlockPosition blockposition1 = shapedetectorcollection.a(0, 2, 0).d();
+
+ entitysnowman.setPositionRotation((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.05D, (double) blockposition1.getZ() + 0.5D, 0.0F, 0.0F);
+- world.addEntity(entitysnowman);
++ // CraftBukkit start
++ if (world.addEntity(entitysnowman, SpawnReason.BUILD_SNOWMAN)) {
++ blockList.updateList();
+
+- for (j = 0; j < 120; ++j) {
+- world.addParticle(EnumParticle.SNOW_SHOVEL, (double) blockposition1.getX() + world.random.nextDouble(), (double) blockposition1.getY() + world.random.nextDouble() * 2.5D, (double) blockposition1.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
+- }
++ for (j = 0; j < 120; ++j) {
++ world.addParticle(EnumParticle.SNOW_SHOVEL, (double) blockposition1.getX() + world.random.nextDouble(), (double) blockposition1.getY() + world.random.nextDouble() * 2.5D, (double) blockposition1.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
++ }
+
+- for (j = 0; j < this.getDetectorSnowGolem().b(); ++j) {
+- ShapeDetectorBlock shapedetectorblock1 = shapedetectorcollection.a(0, j, 0);
++ for (j = 0; j < this.getDetectorSnowGolem().b(); ++j) {
++ ShapeDetectorBlock shapedetectorblock1 = shapedetectorcollection.a(0, j, 0);
+
+- world.update(shapedetectorblock1.d(), Blocks.AIR);
++ world.update(shapedetectorblock1.d(), Blocks.AIR);
++ }
+ }
++ // CraftBukkit end
+ } else if ((shapedetectorcollection = this.getDetectorIronGolem().a(world, blockposition)) != null) {
++ BlockStateListPopulator blockList = new BlockStateListPopulator(world.getWorld()); // CraftBukkit - Use BlockStateListPopulator
+ for (i = 0; i < this.getDetectorIronGolem().c(); ++i) {
+ for (int k = 0; k < this.getDetectorIronGolem().b(); ++k) {
+- world.setTypeAndData(shapedetectorcollection.a(i, k, 0).d(), Blocks.AIR.getBlockData(), 2);
++ // CraftBukkit start
++ // world.setTypeAndData(shapedetectorcollection.a(i, k, 0).d(), Blocks.AIR.getBlockData(), 2);
++ BlockPosition pos = shapedetectorcollection.a(i, k, 0).d();
++ blockList.setTypeId(pos.getX(), pos.getY(), pos.getZ(), 0);
++ // CraftBukkit end
+ }
+ }
+
+@@ -62,22 +82,38 @@
+
+ entityirongolem.setPlayerCreated(true);
+ entityirongolem.setPositionRotation((double) blockposition2.getX() + 0.5D, (double) blockposition2.getY() + 0.05D, (double) blockposition2.getZ() + 0.5D, 0.0F, 0.0F);
+- world.addEntity(entityirongolem);
+-
+- for (j = 0; j < 120; ++j) {
+- world.addParticle(EnumParticle.SNOWBALL, (double) blockposition2.getX() + world.random.nextDouble(), (double) blockposition2.getY() + world.random.nextDouble() * 3.9D, (double) blockposition2.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
+- }
++ // CraftBukkit start
++ if (world.addEntity(entityirongolem, SpawnReason.BUILD_IRONGOLEM)) {
++ blockList.updateList();
++
++ for (j = 0; j < 120; ++j) {
++ world.addParticle(EnumParticle.SNOWBALL, (double) blockposition2.getX() + world.random.nextDouble(), (double) blockposition2.getY() + world.random.nextDouble() * 3.9D, (double) blockposition2.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
++ }
+
+- for (j = 0; j < this.getDetectorIronGolem().c(); ++j) {
+- for (int l = 0; l < this.getDetectorIronGolem().b(); ++l) {
+- ShapeDetectorBlock shapedetectorblock2 = shapedetectorcollection.a(j, l, 0);
++ for (j = 0; j < this.getDetectorIronGolem().c(); ++j) {
++ for (int l = 0; l < this.getDetectorIronGolem().b(); ++l) {
++ ShapeDetectorBlock shapedetectorblock2 = shapedetectorcollection.a(j, l, 0);
+
+- world.update(shapedetectorblock2.d(), Blocks.AIR);
++ world.update(shapedetectorblock2.d(), Blocks.AIR);
++ }
+ }
+ }
++ // CraftBukkit end
+ }
++ }
+
++ // CraftBukkit start
++ @Override
++ public void doPhysics(World world, BlockPosition position, IBlockData data, Block block) {
++ if (block != null && block.isPowerSource()) {
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ());
++ int power = bukkitBlock.getBlockPower();
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, power, power);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++ }
+ }
++ // CraftBukkit end
+
+ public boolean canPlace(World world, BlockPosition blockposition) {
+ return world.getType(blockposition).getBlock().material.isReplaceable() && World.a((IBlockAccess) world, blockposition.down());
diff --git a/nms-patches/BlockRedstoneLamp.patch b/nms-patches/BlockRedstoneLamp.patch
new file mode 100644
index 00000000..66c81280
--- /dev/null
+++ b/nms-patches/BlockRedstoneLamp.patch
@@ -0,0 +1,47 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockRedstoneLamp.java 2014-11-27 08:59:46.565422446 +1100
++++ src/main/java/net/minecraft/server/BlockRedstoneLamp.java 2014-11-27 08:42:10.140850934 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockRedstoneLamp extends Block {
+
+ private final boolean a;
+@@ -20,6 +22,11 @@
+ if (this.a && !world.isBlockIndirectlyPowered(blockposition)) {
+ world.setTypeAndData(blockposition, Blocks.REDSTONE_LAMP.getBlockData(), 2);
+ } else if (!this.a && world.isBlockIndirectlyPowered(blockposition)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, Blocks.LIT_REDSTONE_LAMP.getBlockData(), 2);
+ }
+
+@@ -31,6 +38,11 @@
+ if (this.a && !world.isBlockIndirectlyPowered(blockposition)) {
+ world.a(blockposition, (Block) this, 4);
+ } else if (!this.a && world.isBlockIndirectlyPowered(blockposition)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, Blocks.LIT_REDSTONE_LAMP.getBlockData(), 2);
+ }
+
+@@ -40,6 +52,11 @@
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (!world.isStatic) {
+ if (this.a && !world.isBlockIndirectlyPowered(blockposition)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, Blocks.REDSTONE_LAMP.getBlockData(), 2);
+ }
+
diff --git a/nms-patches/BlockRedstoneOre.patch b/nms-patches/BlockRedstoneOre.patch
new file mode 100644
index 00000000..fcc0a637
--- /dev/null
+++ b/nms-patches/BlockRedstoneOre.patch
@@ -0,0 +1,102 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockRedstoneOre.java 2014-11-27 08:59:46.565422446 +1100
++++ src/main/java/net/minecraft/server/BlockRedstoneOre.java 2014-11-27 08:42:10.112850989 +1100
+@@ -2,6 +2,11 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
++
+ public class BlockRedstoneOre extends Block {
+
+ private final boolean a;
+@@ -20,23 +25,44 @@
+ }
+
+ public void attack(World world, BlockPosition blockposition, EntityHuman entityhuman) {
+- this.d(world, blockposition);
++ this.d(world, blockposition, entityhuman); // CraftBukkit - add entityhuman
+ super.attack(world, blockposition, entityhuman);
+ }
+
+- public void a(World world, BlockPosition blockposition, Entity entity) {
+- this.d(world, blockposition);
+- super.a(world, blockposition, entity);
++ public void a(World world, BlockPosition blockposition, Entity entity) {
++ // CraftBukkit start
++ // this.d(world, blockposition);
++ // super.a(world, blockposition, entity);
++ if (entity instanceof EntityHuman) {
++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityHuman) entity, org.bukkit.event.block.Action.PHYSICAL, blockposition, null, null);
++ if (!event.isCancelled()) {
++ this.d(world, blockposition, entity); // add entity
++ super.a(world, blockposition, entity);
++ }
++ } else {
++ EntityInteractEvent event = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ this.d(world, blockposition, entity); // add entity
++ super.a(world, blockposition, entity);
++ }
++ }
++ // CraftBukkit end
+ }
+
+ public boolean interact(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman, EnumDirection enumdirection, float f, float f1, float f2) {
+- this.d(world, blockposition);
++ this.d(world, blockposition, entityhuman); // CraftBukkit - add entityhuman
+ return super.interact(world, blockposition, iblockdata, entityhuman, enumdirection, f, f1, f2);
+ }
+
+- private void d(World world, BlockPosition blockposition) {
++ private void d(World world, BlockPosition blockposition, Entity entity) { // CraftBukkit - add Entity
+ this.e(world, blockposition);
+ if (this == Blocks.REDSTONE_ORE) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition.getX(), blockposition.getY(), blockposition.getZ(), Blocks.LIT_REDSTONE_ORE, 0).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, Blocks.LIT_REDSTONE_ORE.getBlockData());
+ }
+
+@@ -44,6 +70,11 @@
+
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (this == Blocks.LIT_REDSTONE_ORE) {
++ // CraftBukkit start
++ if (CraftEventFactory.callBlockFadeEvent(world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), Blocks.REDSTONE_ORE).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, Blocks.REDSTONE_ORE.getBlockData());
+ }
+
+@@ -63,12 +94,24 @@
+
+ public void dropNaturally(World world, BlockPosition blockposition, IBlockData iblockdata, float f, int i) {
+ super.dropNaturally(world, blockposition, iblockdata, f, i);
++ /* CraftBukkit start - Delegated to getExpDrop
+ if (this.getDropType(iblockdata, world.random, i) != Item.getItemOf(this)) {
+ int j = 1 + world.random.nextInt(5);
+
+ this.dropExperience(world, blockposition, j);
+ }
++ // */
++ }
+
++ @Override
++ public int getExpDrop(World world, IBlockData data, int i) {
++ if (this.getDropType(data, world.random, i) != Item.getItemOf(this)) {
++ int j = 1 + world.random.nextInt(5);
++
++ return j;
++ }
++ return 0;
++ // CraftBukkit end
+ }
+
+ private void e(World world, BlockPosition blockposition) {
diff --git a/nms-patches/BlockRedstoneTorch.patch b/nms-patches/BlockRedstoneTorch.patch
new file mode 100644
index 00000000..60516593
--- /dev/null
+++ b/nms-patches/BlockRedstoneTorch.patch
@@ -0,0 +1,55 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockRedstoneTorch.java 2014-11-27 08:59:46.565422446 +1100
++++ src/main/java/net/minecraft/server/BlockRedstoneTorch.java 2014-11-27 08:42:10.156850903 +1100
+@@ -6,6 +6,8 @@
+ import java.util.Map;
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockRedstoneTorch extends BlockTorch {
+
+ private static Map b = Maps.newHashMap();
+@@ -95,9 +97,26 @@
+ while (list != null && !list.isEmpty() && world.getTime() - ((RedstoneUpdateInfo) list.get(0)).b > 60L) {
+ list.remove(0);
+ }
++
++ // CraftBukkit start
++ org.bukkit.plugin.PluginManager manager = world.getServer().getPluginManager();
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ int oldCurrent = this.isOn ? 15 : 0;
++
++ BlockRedstoneEvent event = new BlockRedstoneEvent(block, oldCurrent, oldCurrent);
++ // CraftBukkit end
+
+ if (this.isOn) {
+ if (flag) {
++ // CraftBukkit start
++ if (oldCurrent != 0) {
++ event.setNewCurrent(0);
++ manager.callEvent(event);
++ if (event.getNewCurrent() != 0) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, Blocks.UNLIT_REDSTONE_TORCH.getBlockData().set(BlockRedstoneTorch.FACING, iblockdata.get(BlockRedstoneTorch.FACING)), 3);
+ if (this.a(world, blockposition, true)) {
+ world.makeSound((double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.5F), (double) ((float) blockposition.getZ() + 0.5F), "random.fizz", 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
+@@ -114,6 +133,16 @@
+ }
+ }
+ } else if (!flag && !this.a(world, blockposition, false)) {
++ // CraftBukkit start
++ if (oldCurrent != 15) {
++ event.setNewCurrent(15);
++ manager.callEvent(event);
++ if (event.getNewCurrent() != 15) {
++ return;
++ }
++ }
++ // CraftBukkit end
++
+ world.setTypeAndData(blockposition, Blocks.REDSTONE_TORCH.getBlockData().set(BlockRedstoneTorch.FACING, iblockdata.get(BlockRedstoneTorch.FACING)), 3);
+ }
+
diff --git a/nms-patches/BlockRedstoneWire.patch b/nms-patches/BlockRedstoneWire.patch
new file mode 100644
index 00000000..79e7b08f
--- /dev/null
+++ b/nms-patches/BlockRedstoneWire.patch
@@ -0,0 +1,27 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockRedstoneWire.java 2014-11-27 08:59:46.569422428 +1100
++++ src/main/java/net/minecraft/server/BlockRedstoneWire.java 2014-11-27 08:42:10.136850942 +1100
+@@ -8,6 +8,8 @@
+ import java.util.Random;
+ import java.util.Set;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockRedstoneWire extends Block {
+
+ public static final BlockStateEnum NORTH = BlockStateEnum.of("north", EnumRedstoneWireConnection.class);
+@@ -123,6 +125,15 @@
+ if (k > j - 1) {
+ j = k;
+ }
++
++ // CraftBukkit start
++ if (i != j) {
++ BlockRedstoneEvent event = new BlockRedstoneEvent(world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), i, j);
++ world.getServer().getPluginManager().callEvent(event);
++
++ j = event.getNewCurrent();
++ }
++ // CraftBukkit end
+
+ if (i != j) {
+ iblockdata = iblockdata.set(BlockRedstoneWire.POWER, Integer.valueOf(j));
diff --git a/nms-patches/BlockReed.patch b/nms-patches/BlockReed.patch
new file mode 100644
index 00000000..667af180
--- /dev/null
+++ b/nms-patches/BlockReed.patch
@@ -0,0 +1,18 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockReed.java 2014-11-27 08:59:46.569422428 +1100
++++ src/main/java/net/minecraft/server/BlockReed.java 2014-11-27 08:42:10.120850973 +1100
+@@ -29,8 +29,13 @@
+ int j = ((Integer) iblockdata.get(BlockReed.AGE)).intValue();
+
+ if (j == 15) {
+- world.setTypeUpdate(blockposition.up(), this.getBlockData());
+- world.setTypeAndData(blockposition, iblockdata.set(BlockReed.AGE, Integer.valueOf(0)), 4);
++ // CraftBukkit start
++ // world.setTypeUpdate(blockposition.up(), this.getBlockData());
++ // world.setTypeAndData(blockposition, iblockdata.set(BlockReed.AGE, Integer.valueOf(0)), 4);
++ BlockPosition upPos = blockposition.up();
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, upPos.getX(), upPos.getY(), upPos.getZ(), this, 0);
++ // CraftBukkit end
++
+ } else {
+ world.setTypeAndData(blockposition, iblockdata.set(BlockReed.AGE, Integer.valueOf(j + 1)), 4);
+ }
diff --git a/nms-patches/BlockSapling.patch b/nms-patches/BlockSapling.patch
new file mode 100644
index 00000000..5212c9d9
--- /dev/null
+++ b/nms-patches/BlockSapling.patch
@@ -0,0 +1,125 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockSapling.java 2014-11-27 08:59:46.573422410 +1100
++++ src/main/java/net/minecraft/server/BlockSapling.java 2014-11-27 08:42:10.108850996 +1100
+@@ -2,10 +2,20 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.Location;
++import org.bukkit.TreeType;
++import org.bukkit.block.BlockState;
++import org.bukkit.event.world.StructureGrowEvent;
++// CraftBukkit end
++
+ public class BlockSapling extends BlockPlant implements IBlockFragilePlantElement {
+
+ public static final BlockStateEnum TYPE = BlockStateEnum.of("type", EnumLogVariant.class);
+ public static final BlockStateInteger STAGE = BlockStateInteger.of("stage", 0, 1);
++ public static TreeType treeType; // CraftBukkit
+
+ protected BlockSapling() {
+ this.j(this.blockStateList.getBlockData().set(BlockSapling.TYPE, EnumLogVariant.OAK).set(BlockSapling.STAGE, Integer.valueOf(0)));
+@@ -19,7 +29,30 @@
+ if (!world.isStatic) {
+ super.b(world, blockposition, iblockdata, random);
+ if (world.getLightLevel(blockposition.up()) >= 9 && random.nextInt(7) == 0) {
++ // CraftBukkit start
++ world.captureTreeGeneration = true;
++ // CraftBukkit end
+ this.grow(world, blockposition, iblockdata, random);
++ // CraftBukkit start
++ world.captureTreeGeneration = false;
++ if (world.capturedBlockStates.size() > 0) {
++ TreeType treeType = BlockSapling.treeType;
++ BlockSapling.treeType = null;
++ Location location = new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ List<BlockState> blocks = (List<BlockState>) world.capturedBlockStates.clone();
++ world.capturedBlockStates.clear();
++ StructureGrowEvent event = null;
++ if (treeType != null) {
++ event = new StructureGrowEvent(location, treeType, false, null, blocks);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ }
++ if (event == null || !event.isCancelled()) {
++ for (BlockState blockstate : blocks) {
++ blockstate.update(true);
++ }
++ }
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -35,7 +68,17 @@
+ }
+
+ public void e(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+- Object object = random.nextInt(10) == 0 ? new WorldGenBigTree(true) : new WorldGenTrees(true);
++ // CraftBukkit start - Turn ternary operator into if statement to set treeType
++ // Object object = random.nextInt(10) == 0 ? new WorldGenBigTree(true) : new WorldGenTrees(true);
++ Object object;
++ if (random.nextInt(10) == 0) {
++ treeType = TreeType.BIG_TREE;
++ object = new WorldGenBigTree(true);
++ } else {
++ treeType = TreeType.TREE;
++ object = new WorldGenTrees(true);
++ }
++ // CraftBukkit end
+ int i = 0;
+ int j = 0;
+ boolean flag = false;
+@@ -46,6 +89,7 @@
+ for (i = 0; i >= -1; --i) {
+ for (j = 0; j >= -1; --j) {
+ if (this.a(world, blockposition.a(i, 0, j), EnumLogVariant.SPRUCE) && this.a(world, blockposition.a(i + 1, 0, j), EnumLogVariant.SPRUCE) && this.a(world, blockposition.a(i, 0, j + 1), EnumLogVariant.SPRUCE) && this.a(world, blockposition.a(i + 1, 0, j + 1), EnumLogVariant.SPRUCE)) {
++ treeType = TreeType.MEGA_REDWOOD; // CraftBukkit
+ object = new WorldGenMegaTree(false, random.nextBoolean());
+ flag = true;
+ break label78;
+@@ -56,11 +100,13 @@
+ if (!flag) {
+ j = 0;
+ i = 0;
++ treeType = TreeType.REDWOOD; // CraftBukkit
+ object = new WorldGenTaiga2(true);
+ }
+ break;
+
+ case 2:
++ treeType = TreeType.BIRCH; // CraftBukkit
+ object = new WorldGenForest(true, false);
+ break;
+
+@@ -69,6 +115,7 @@
+ for (i = 0; i >= -1; --i) {
+ for (j = 0; j >= -1; --j) {
+ if (this.a(world, blockposition.a(i, 0, j), EnumLogVariant.JUNGLE) && this.a(world, blockposition.a(i + 1, 0, j), EnumLogVariant.JUNGLE) && this.a(world, blockposition.a(i, 0, j + 1), EnumLogVariant.JUNGLE) && this.a(world, blockposition.a(i + 1, 0, j + 1), EnumLogVariant.JUNGLE)) {
++ treeType = TreeType.JUNGLE; // CraftBukkit
+ object = new WorldGenJungleTree(true, 10, 20, EnumLogVariant.JUNGLE.a(), EnumLogVariant.JUNGLE.a());
+ flag = true;
+ break label93;
+@@ -79,11 +126,13 @@
+ if (!flag) {
+ j = 0;
+ i = 0;
++ treeType = TreeType.SMALL_JUNGLE; // CraftBukkit
+ object = new WorldGenTrees(true, 4 + random.nextInt(7), EnumLogVariant.JUNGLE.a(), EnumLogVariant.JUNGLE.a(), false);
+ }
+ break;
+
+ case 4:
++ treeType = TreeType.ACACIA; // CraftBukki
+ object = new WorldGenAcaciaTree(true);
+ break;
+
+@@ -92,6 +141,7 @@
+ for (i = 0; i >= -1; --i) {
+ for (j = 0; j >= -1; --j) {
+ if (this.a(world, blockposition.a(i, 0, j), EnumLogVariant.DARK_OAK) && this.a(world, blockposition.a(i + 1, 0, j), EnumLogVariant.DARK_OAK) && this.a(world, blockposition.a(i, 0, j + 1), EnumLogVariant.DARK_OAK) && this.a(world, blockposition.a(i + 1, 0, j + 1), EnumLogVariant.DARK_OAK)) {
++ treeType = TreeType.DARK_OAK; // CraftBukkit
+ object = new WorldGenForestTree(true);
+ flag = true;
+ break label108;
diff --git a/nms-patches/BlockSkull.patch b/nms-patches/BlockSkull.patch
new file mode 100644
index 00000000..0bebb84d
--- /dev/null
+++ b/nms-patches/BlockSkull.patch
@@ -0,0 +1,133 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockSkull.java 2014-11-27 08:59:46.573422410 +1100
++++ src/main/java/net/minecraft/server/BlockSkull.java 2014-11-27 08:42:10.156850903 +1100
+@@ -4,6 +4,11 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.BlockStateListPopulator;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
++
+ public class BlockSkull extends BlockContainer {
+
+ public static final BlockStateDirection FACING = BlockStateDirection.of("facing");
+@@ -69,8 +74,25 @@
+
+ return tileentity instanceof TileEntitySkull ? ((TileEntitySkull) tileentity).getSkullType() : super.getDropData(world, blockposition);
+ }
++
++ // CraftBukkit start - Special case dropping so we can get info from the tile entity
++ public void dropNaturally(World world, BlockPosition blockposition, IBlockData iblockdata, float f, int i) {
++ if (world.random.nextFloat() < f) {
++ ItemStack itemstack = new ItemStack(Items.SKULL, 1, this.getDropData(world, blockposition));
++ TileEntitySkull tileentityskull = (TileEntitySkull) world.getTileEntity(blockposition);
++
++ if (tileentityskull.getSkullType() == 3 && tileentityskull.getGameProfile() != null) {
++ itemstack.setTag(new NBTTagCompound());
++ NBTTagCompound nbttagcompound = new NBTTagCompound();
++
++ GameProfileSerializer.serialize(nbttagcompound, tileentityskull.getGameProfile());
++ itemstack.getTag().set("SkullOwner", nbttagcompound);
++ }
+
+- public void dropNaturally(World world, BlockPosition blockposition, IBlockData iblockdata, float f, int i) {}
++ a(world, blockposition, itemstack);
++ }
++ }
++ // CraftBukkit end
+
+ public void a(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman) {
+ if (entityhuman.abilities.canInstantlyBuild) {
+@@ -83,7 +105,10 @@
+
+ public void remove(World world, BlockPosition blockposition, IBlockData iblockdata) {
+ if (!world.isStatic) {
+- if (!((Boolean) iblockdata.get(BlockSkull.NODROP)).booleanValue()) {
++ // CraftBukkit start - Drop item in code above, not here
++ // if (!((Boolean) iblockdata.get(BlockSkull.NODROP)).booleanValue()) {
++ if (false) {
++ // CraftBukkit end
+ TileEntity tileentity = world.getTileEntity(blockposition);
+
+ if (tileentity instanceof TileEntitySkull) {
+@@ -120,19 +145,30 @@
+ ShapeDetectorCollection shapedetectorcollection = shapedetector.a(world, blockposition);
+
+ if (shapedetectorcollection != null) {
++ // CraftBukkit start - Use BlockStateListPopulator
++ BlockStateListPopulator blockList = new BlockStateListPopulator(world.getWorld());
+ int i;
+
+ for (i = 0; i < 3; ++i) {
+ ShapeDetectorBlock shapedetectorblock = shapedetectorcollection.a(i, 0, 0);
+
+- world.setTypeAndData(shapedetectorblock.d(), shapedetectorblock.a().set(BlockSkull.NODROP, Boolean.valueOf(true)), 2);
++ // CraftBukkit start
++ // world.setTypeAndData(shapedetectorblock.d(), shapedetectorblock.a().set(BlockSkull.NODROP, Boolean.valueOf(true)), 2);
++ BlockPosition pos = shapedetectorblock.d();
++ IBlockData data = shapedetectorblock.a().set(BlockSkull.NODROP, Boolean.valueOf(true));
++ blockList.setTypeAndData(pos.getX(), pos.getY(), pos.getZ(), data.getBlock(), data.getBlock().toLegacyData(data), 2);
++ // CraftBukkit end
+ }
+
+ for (i = 0; i < shapedetector.c(); ++i) {
+ for (int j = 0; j < shapedetector.b(); ++j) {
+ ShapeDetectorBlock shapedetectorblock1 = shapedetectorcollection.a(i, j, 0);
+
+- world.setTypeAndData(shapedetectorblock1.d(), Blocks.AIR.getBlockData(), 2);
++ // CraftBukkit start
++ // world.setTypeAndData(shapedetectorblock1.d(), Blocks.AIR.getBlockData(), 2);
++ BlockPosition pos = shapedetectorblock1.d();
++ blockList.setTypeAndData(pos.getX(), pos.getY(), pos.getZ(), Blocks.AIR, 0, 2);
++ // CraftBukkit end
+ }
+ }
+
+@@ -145,28 +181,31 @@
+ entitywither.n();
+ Iterator iterator = world.a(EntityHuman.class, entitywither.getBoundingBox().grow(50.0D, 50.0D, 50.0D)).iterator();
+
+- while (iterator.hasNext()) {
+- EntityHuman entityhuman = (EntityHuman) iterator.next();
++ // CraftBukkit start
++ if (world.addEntity(entitywither, SpawnReason.BUILD_WITHER)) {
++ while (iterator.hasNext()) {
++ EntityHuman entityhuman = (EntityHuman) iterator.next();
+
+- entityhuman.b((Statistic) AchievementList.I);
+- }
+-
+- world.addEntity(entitywither);
++ entityhuman.b((Statistic) AchievementList.I);
++ }
++
++ blockList.updateList();
+
+- int k;
++ int k;
+
+- for (k = 0; k < 120; ++k) {
+- world.addParticle(EnumParticle.SNOWBALL, (double) blockposition1.getX() + world.random.nextDouble(), (double) (blockposition1.getY() - 2) + world.random.nextDouble() * 3.9D, (double) blockposition1.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
+- }
++ for (k = 0; k < 120; ++k) {
++ world.addParticle(EnumParticle.SNOWBALL, (double) blockposition1.getX() + world.random.nextDouble(), (double) (blockposition1.getY() - 2) + world.random.nextDouble() * 3.9D, (double) blockposition1.getZ() + world.random.nextDouble(), 0.0D, 0.0D, 0.0D, new int[0]);
++ }
+
+- for (k = 0; k < shapedetector.c(); ++k) {
+- for (int l = 0; l < shapedetector.b(); ++l) {
+- ShapeDetectorBlock shapedetectorblock2 = shapedetectorcollection.a(k, l, 0);
++ for (k = 0; k < shapedetector.c(); ++k) {
++ for (int l = 0; l < shapedetector.b(); ++l) {
++ ShapeDetectorBlock shapedetectorblock2 = shapedetectorcollection.a(k, l, 0);
+
+- world.update(shapedetectorblock2.d(), Blocks.AIR);
++ world.update(shapedetectorblock2.d(), Blocks.AIR);
++ }
+ }
+ }
+-
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/BlockSnow.patch b/nms-patches/BlockSnow.patch
new file mode 100644
index 00000000..5727d5a5
--- /dev/null
+++ b/nms-patches/BlockSnow.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockSnow.java 2014-11-27 08:59:46.577422392 +1100
++++ src/main/java/net/minecraft/server/BlockSnow.java 2014-11-27 08:42:10.144850927 +1100
+@@ -85,6 +85,11 @@
+
+ public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
+ if (world.b(EnumSkyBlock.BLOCK, blockposition) > 11) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), Blocks.AIR).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.b(world, blockposition, world.getType(blockposition), 0);
+ world.setAir(blockposition);
+ }
diff --git a/nms-patches/BlockSoil.patch b/nms-patches/BlockSoil.patch
new file mode 100644
index 00000000..cd0c3d50
--- /dev/null
+++ b/nms-patches/BlockSoil.patch
@@ -0,0 +1,52 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockSoil.java 2014-11-27 08:59:46.577422392 +1100
++++ src/main/java/net/minecraft/server/BlockSoil.java 2014-11-27 08:42:10.168850880 +1100
+@@ -3,6 +3,11 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.event.entity.EntityInteractEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class BlockSoil extends Block {
+
+ public static final BlockStateInteger MOISTURE = BlockStateInteger.of("moisture", 0, 7);
+@@ -34,6 +39,12 @@
+ if (i > 0) {
+ world.setTypeAndData(blockposition, iblockdata.set(BlockSoil.MOISTURE, Integer.valueOf(i - 1)), 2);
+ } else if (!this.d(world, blockposition)) {
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ if (CraftEventFactory.callBlockFadeEvent(block, Blocks.DIRT).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData());
+ }
+ } else if (i < 7) {
+@@ -49,6 +60,24 @@
+ return;
+ }
+
++ // CraftBukkit start - Interact soil
++ org.bukkit.event.Cancellable cancellable;
++ if (entity instanceof EntityHuman) {
++ cancellable = CraftEventFactory.callPlayerInteractEvent((EntityHuman) entity, org.bukkit.event.block.Action.PHYSICAL, blockposition, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ world.getServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ if (cancellable.isCancelled()) {
++ return;
++ }
++
++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition.getX(), blockposition.getY(), blockposition.getZ(), Blocks.DIRT, 0).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++
+ world.setTypeUpdate(blockposition, Blocks.DIRT.getBlockData());
+ }
+
diff --git a/nms-patches/BlockStationary.patch b/nms-patches/BlockStationary.patch
new file mode 100644
index 00000000..5ffd0a2e
--- /dev/null
+++ b/nms-patches/BlockStationary.patch
@@ -0,0 +1,40 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockStationary.java 2014-11-27 08:59:46.577422392 +1100
++++ src/main/java/net/minecraft/server/BlockStationary.java 2014-11-27 08:42:10.152850911 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockStationary extends BlockFluids {
+
+ protected BlockStationary(Material material) {
+@@ -41,6 +43,13 @@
+
+ if (block.material == Material.AIR) {
+ if (this.e(world, blockposition1)) {
++ // CraftBukkit start - Prevent lava putting something on fire
++ if (world.getType(blockposition1) != Blocks.FIRE) {
++ if (CraftEventFactory.callBlockIgniteEvent(world, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ(), blockposition.getX(), blockposition.getY(), blockposition.getZ()).isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition1, Blocks.FIRE.getBlockData());
+ return;
+ }
+@@ -53,6 +62,14 @@
+ BlockPosition blockposition2 = blockposition.a(random.nextInt(3) - 1, 0, random.nextInt(3) - 1);
+
+ if (world.isEmpty(blockposition2.up()) && this.m(world, blockposition2)) {
++ // CraftBukkit start - Prevent lava putting something on fire
++ BlockPosition up = blockposition2.up();
++ if (world.getType(up) != Blocks.FIRE) {
++ if (CraftEventFactory.callBlockIgniteEvent(world, up.getX(), up.getY(), up.getZ(), blockposition.getX(), blockposition.getY(), blockposition.getZ()).isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
+ world.setTypeUpdate(blockposition2.up(), Blocks.FIRE.getBlockData());
+ }
+ }
diff --git a/nms-patches/BlockStem.patch b/nms-patches/BlockStem.patch
new file mode 100644
index 00000000..b20e09ca
--- /dev/null
+++ b/nms-patches/BlockStem.patch
@@ -0,0 +1,41 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockStem.java 2014-11-27 08:59:46.581422375 +1100
++++ src/main/java/net/minecraft/server/BlockStem.java 2014-11-27 08:42:10.152850911 +1100
+@@ -4,6 +4,8 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockStem extends BlockPlant implements IBlockFragilePlantElement {
+
+ public static final BlockStateInteger AGE = BlockStateInteger.of("age", 0, 7);
+@@ -50,7 +52,8 @@
+
+ if (i < 7) {
+ iblockdata = iblockdata.set(BlockStem.AGE, Integer.valueOf(i + 1));
+- world.setTypeAndData(blockposition, iblockdata, 2);
++ // world.setTypeAndData(blockposition, iblockdata, 2); // CraftBukkit
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, toLegacyData(iblockdata)); // CraftBukkit
+ } else {
+ Iterator iterator = EnumDirectionLimit.HORIZONTAL.iterator();
+
+@@ -66,7 +69,8 @@
+ Block block = world.getType(blockposition.down()).getBlock();
+
+ if (world.getType(blockposition).getBlock().material == Material.AIR && (block == Blocks.FARMLAND || block == Blocks.DIRT || block == Blocks.GRASS)) {
+- world.setTypeUpdate(blockposition, this.blockFruit.getBlockData());
++ // world.setTypeUpdate(blockposition, this.blockFruit.getBlockData()); // CraftBukkit
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.blockFruit, 0); // CraftBukkit
+ }
+ }
+ }
+@@ -77,7 +81,8 @@
+ public void g(World world, BlockPosition blockposition, IBlockData iblockdata) {
+ int i = ((Integer) iblockdata.get(BlockStem.AGE)).intValue() + MathHelper.nextInt(world.random, 2, 5);
+
+- world.setTypeAndData(blockposition, iblockdata.set(BlockStem.AGE, Integer.valueOf(Math.min(7, i))), 2);
++ // world.setTypeAndData(blockposition, iblockdata.set(BlockStem.AGE, Integer.valueOf(Math.min(7, i))), 2);
++ CraftEventFactory.handleBlockGrowEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this, Math.min(7, i)); // CraftBukkit
+ }
+
+ public void h() {
diff --git a/nms-patches/BlockTrapdoor.patch b/nms-patches/BlockTrapdoor.patch
new file mode 100644
index 00000000..8b2992a9
--- /dev/null
+++ b/nms-patches/BlockTrapdoor.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockTrapdoor.java 2014-11-27 08:59:46.581422375 +1100
++++ src/main/java/net/minecraft/server/BlockTrapdoor.java 2014-11-27 08:42:10.124850965 +1100
+@@ -2,6 +2,8 @@
+
+ import com.google.common.base.Predicate;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockTrapdoor extends Block {
+
+ public static final BlockStateDirection FACING = BlockStateDirection.of("facing", (Predicate) EnumDirectionLimit.HORIZONTAL);
+@@ -101,6 +103,19 @@
+ boolean flag = world.isBlockIndirectlyPowered(blockposition);
+
+ if (flag || block.isPowerSource()) {
++ // CraftBukkit start
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.block.Block bblock = bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ int power = bblock.getBlockPower();
++ int oldPower = (Boolean) iblockdata.get(OPEN) ? 15 : 0;
++
++ if (oldPower == 0 ^ power == 0 || block.isPowerSource()) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bblock, oldPower, power);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++ flag = eventRedstone.getNewCurrent() > 0;
++ }
++ // CraftBukkit end
+ boolean flag1 = ((Boolean) iblockdata.get(BlockTrapdoor.OPEN)).booleanValue();
+
+ if (flag1 != flag) {
diff --git a/nms-patches/BlockTripwire.patch b/nms-patches/BlockTripwire.patch
new file mode 100644
index 00000000..f3954aa5
--- /dev/null
+++ b/nms-patches/BlockTripwire.patch
@@ -0,0 +1,52 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockTripwire.java 2014-11-27 08:59:46.585422357 +1100
++++ src/main/java/net/minecraft/server/BlockTripwire.java 2014-11-27 08:42:10.140850934 +1100
+@@ -4,6 +4,8 @@
+ import java.util.List;
+ import java.util.Random;
+
++import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit
++
+ public class BlockTripwire extends Block {
+
+ public static final BlockStateBoolean POWERED = BlockStateBoolean.of("powered");
+@@ -154,6 +156,40 @@
+ }
+ }
+ }
++
++ // CraftBukkit start - Call interact even when triggering connected tripwire
++ if (flag != flag1 && flag1 && (Boolean)iblockdata.get(ATTACHED)) {
++ org.bukkit.World bworld = world.getWorld();
++ org.bukkit.plugin.PluginManager manager = world.getServer().getPluginManager();
++ org.bukkit.block.Block block = bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ boolean allowed = false;
++
++ // If all of the events are cancelled block the tripwire trigger, else allow
++ for (Object object : list) {
++ if (object != null) {
++ org.bukkit.event.Cancellable cancellable;
++
++ if (object instanceof EntityHuman) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityHuman) object, org.bukkit.event.block.Action.PHYSICAL, blockposition, null, null);
++ } else if (object instanceof Entity) {
++ cancellable = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block);
++ manager.callEvent((EntityInteractEvent) cancellable);
++ } else {
++ continue;
++ }
++
++ if (!cancellable.isCancelled()) {
++ allowed = true;
++ break;
++ }
++ }
++ }
++
++ if (!allowed) {
++ return;
++ }
++ }
++ // CraftBukkit end
+
+ if (flag1 != flag) {
+ iblockdata = iblockdata.set(BlockTripwire.POWERED, Boolean.valueOf(flag1));
diff --git a/nms-patches/BlockTripwireHook.patch b/nms-patches/BlockTripwireHook.patch
new file mode 100644
index 00000000..d4da9326
--- /dev/null
+++ b/nms-patches/BlockTripwireHook.patch
@@ -0,0 +1,29 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockTripwireHook.java 2014-11-27 08:59:46.585422357 +1100
++++ src/main/java/net/minecraft/server/BlockTripwireHook.java 2014-11-27 08:42:10.144850927 +1100
+@@ -5,6 +5,8 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class BlockTripwireHook extends Block {
+
+ public static final BlockStateDirection FACING = BlockStateDirection.of("facing", (Predicate) EnumDirectionLimit.HORIZONTAL);
+@@ -141,6 +143,17 @@
+ this.a(world, blockposition1, flag5, flag6, flag2, flag3);
+ }
+
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0);
++ world.getServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() > 0) {
++ return;
++ }
++ // CraftBukkit end
++
+ this.a(world, blockposition, flag5, flag6, flag2, flag3);
+ if (!flag) {
+ world.setTypeAndData(blockposition, iblockdata3.set(BlockTripwireHook.FACING, enumdirection), 3);
diff --git a/nms-patches/BlockVine.patch b/nms-patches/BlockVine.patch
new file mode 100644
index 00000000..5e920272
--- /dev/null
+++ b/nms-patches/BlockVine.patch
@@ -0,0 +1,76 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/BlockVine.java 2014-11-27 08:59:46.589422340 +1100
++++ src/main/java/net/minecraft/server/BlockVine.java 2014-11-27 08:42:10.156850903 +1100
+@@ -3,6 +3,8 @@
+ import java.util.Iterator;
+ import java.util.Random;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class BlockVine extends Block {
+
+ public static final BlockStateBoolean UP = BlockStateBoolean.of("up");
+@@ -203,7 +205,13 @@
+ }
+
+ if (((Boolean) iblockdata1.get(BlockVine.NORTH)).booleanValue() || ((Boolean) iblockdata1.get(BlockVine.EAST)).booleanValue() || ((Boolean) iblockdata1.get(BlockVine.SOUTH)).booleanValue() || ((Boolean) iblockdata1.get(BlockVine.WEST)).booleanValue()) {
+- world.setTypeAndData(blockposition.up(), iblockdata1, 2);
++ // CraftBukkit start - Call BlockSpreadEvent
++ // world.setTypeAndData(blockposition.up(), iblockdata1, 2);
++ BlockPosition target = blockposition.up();
++ org.bukkit.block.Block source = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(target.getX(), target.getY(), target.getZ());
++ CraftEventFactory.handleBlockSpreadEvent(block, source, this, toLegacyData(iblockdata1));
++ // CraftBukkit end
+ }
+
+ }
+@@ -222,18 +230,30 @@
+ boolean flag2 = ((Boolean) iblockdata.get(a(enumdirection2))).booleanValue();
+ BlockPosition blockposition2 = blockposition1.shift(enumdirection1);
+ BlockPosition blockposition3 = blockposition1.shift(enumdirection2);
++
++ // CraftBukkit start - Call BlockSpreadEvent
++ org.bukkit.block.Block source = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
+
+ if (flag1 && this.c(world.getType(blockposition2).getBlock())) {
+- world.setTypeAndData(blockposition1, this.getBlockData().set(a(enumdirection1), Boolean.valueOf(true)), 2);
++ // world.setTypeAndData(blockposition1, this.getBlockData().set(a(enumdirection1), Boolean.valueOf(true)), 2);
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, block, toLegacyData(this.getBlockData().set(a(enumdirection1), Boolean.valueOf(true))));
+ } else if (flag2 && this.c(world.getType(blockposition3).getBlock())) {
+- world.setTypeAndData(blockposition1, this.getBlockData().set(a(enumdirection2), Boolean.valueOf(true)), 2);
++ // world.setTypeAndData(blockposition1, this.getBlockData().set(a(enumdirection2), Boolean.valueOf(true)), 2);
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, block, toLegacyData(this.getBlockData().set(a(enumdirection2), Boolean.valueOf(true))));
+ } else if (flag1 && world.isEmpty(blockposition2) && this.c(world.getType(blockposition.shift(enumdirection1)).getBlock())) {
+- world.setTypeAndData(blockposition2, this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true)), 2);
++ // world.setTypeAndData(blockposition2, this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true)), 2);
++ bukkitBlock = world.getWorld().getBlockAt(blockposition2.getX(), blockposition2.getY(), blockposition2.getZ());
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, block, toLegacyData(this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true))));
+ } else if (flag2 && world.isEmpty(blockposition3) && this.c(world.getType(blockposition.shift(enumdirection2)).getBlock())) {
+- world.setTypeAndData(blockposition3, this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true)), 2);
++ // world.setTypeAndData(blockposition3, this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true)), 2);
++ bukkitBlock = world.getWorld().getBlockAt(blockposition3.getX(), blockposition3.getY(), blockposition3.getZ());
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, block, toLegacyData(this.getBlockData().set(a(enumdirection.opposite()), Boolean.valueOf(true))));
+ } else if (this.c(world.getType(blockposition1.up()).getBlock())) {
+- world.setTypeAndData(blockposition1, this.getBlockData(), 2);
++ // world.setTypeAndData(blockposition1, this.getBlockData(), 2);
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, block, toLegacyData(this.getBlockData()));
+ }
++ // CraftBukkit end
+ } else if (block.material.k() && block.d()) {
+ world.setTypeAndData(blockposition, iblockdata.set(a(enumdirection), Boolean.valueOf(true)), 2);
+ }
+@@ -260,7 +280,12 @@
+ }
+
+ if (((Boolean) iblockdata3.get(BlockVine.NORTH)).booleanValue() || ((Boolean) iblockdata3.get(BlockVine.EAST)).booleanValue() || ((Boolean) iblockdata3.get(BlockVine.SOUTH)).booleanValue() || ((Boolean) iblockdata3.get(BlockVine.WEST)).booleanValue()) {
+- world.setTypeAndData(blockposition1, iblockdata3, 2);
++ // CraftBukkit start - Call BlockSpreadEvent
++ // world.setTypeAndData(blockposition1, iblockdata3, 2);
++ org.bukkit.block.Block source = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, toLegacyData(iblockdata3));
++ // CraftBukkit end
+ }
+ } else if (block1 == this) {
+ iblockdata3 = iblockdata2;
diff --git a/nms-patches/Chunk.patch b/nms-patches/Chunk.patch
new file mode 100644
index 00000000..d2f4392f
--- /dev/null
+++ b/nms-patches/Chunk.patch
@@ -0,0 +1,169 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Chunk.java 2014-11-27 08:59:46.589422340 +1100
++++ src/main/java/net/minecraft/server/Chunk.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,6 +1,7 @@
+ package net.minecraft.server;
+
+ import com.google.common.base.Predicate;
++import com.google.common.collect.Lists; // CraftBukkit
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Queues;
+ import java.util.Arrays;
+@@ -14,6 +15,8 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import org.bukkit.Bukkit; // CraftBukkit
++
+ public class Chunk {
+
+ private static final Logger c = LogManager.getLogger();
+@@ -23,7 +26,7 @@
+ private final boolean[] g;
+ private boolean h;
+ public final World world;
+- private final int[] heightMap;
++ public final int[] heightMap; // CraftBukkit - make public
+ public final int locX;
+ public final int locZ;
+ private boolean k;
+@@ -40,6 +43,34 @@
+ private int v;
+ private ConcurrentLinkedQueue w;
+
++ // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
++ private int neighbors = 0x1 << 12;
++
++ public boolean areNeighborsLoaded(final int radius) {
++ switch (radius) {
++ case 2:
++ return this.neighbors == Integer.MAX_VALUE >> 6;
++ case 1:
++ final int mask =
++ // x z offset x z offset x z offset
++ (0x1 << (1 * 5 + 1 + 12)) | (0x1 << (0 * 5 + 1 + 12)) | (0x1 << (-1 * 5 + 1 + 12)) |
++ (0x1 << (1 * 5 + 0 + 12)) | (0x1 << (0 * 5 + 0 + 12)) | (0x1 << (-1 * 5 + 0 + 12)) |
++ (0x1 << (1 * 5 + -1 + 12)) | (0x1 << (0 * 5 + -1 + 12)) | (0x1 << (-1 * 5 + -1 + 12));
++ return (this.neighbors & mask) == mask;
++ default:
++ throw new UnsupportedOperationException(String.valueOf(radius));
++ }
++ }
++
++ public void setNeighborLoaded(final int x, final int z) {
++ this.neighbors |= 0x1 << (x * 5 + 12 + z);
++ }
++
++ public void setNeighborUnloaded(final int x, final int z) {
++ this.neighbors &= ~(0x1 << (x * 5 + 12 + z));
++ }
++ // CraftBukkit end
++
+ public Chunk(World world, int i, int j) {
+ this.sections = new ChunkSection[16];
+ this.e = new byte[256];
+@@ -60,8 +91,17 @@
+
+ Arrays.fill(this.f, -999);
+ Arrays.fill(this.e, (byte) -1);
++
++ // CraftBukkit start
++ if (!(this instanceof EmptyChunk)) {
++ this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
++ }
+ }
+
++ public org.bukkit.Chunk bukkitChunk;
++ public boolean mustSave;
++ // CraftBukkit end
++
+ public Chunk(World world, ChunkSnapshot chunksnapshot, int i, int j) {
+ this(world, i, j);
+ short short0 = 256;
+@@ -465,7 +505,13 @@
+ flag = j >= i1;
+ }
+
+- chunksection.setType(i, j & 15, k, iblockdata);
++ // CraftBukkit start - Delay removing containers until after they're cleaned up
++ if (!(block1 instanceof IContainer)) {
++ chunksection.setType(i, j & 15, k, iblockdata);
++ }
++ // CraftBukkit end
++
++
+ if (block1 != block) {
+ if (!this.world.isStatic) {
+ block1.remove(this.world, blockposition, iblockdata1);
+@@ -474,6 +520,12 @@
+ }
+ }
+
++ // CraftBukkit start - Delay removing containers until after they're cleaned up
++ if (block1 instanceof IContainer) {
++ chunksection.setType(i, j & 15, k, iblockdata);
++ }
++ // CraftBukkit end
++
+ if (chunksection.b(i, j & 15, k) != block) {
+ return null;
+ } else {
+@@ -505,7 +557,8 @@
+ }
+ }
+
+- if (!this.world.isStatic && block1 != block) {
++ // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
++ if (!this.world.isStatic && block1 != block && (!this.world.captureBlockStates || block instanceof BlockContainer)) {
+ block.onPlace(this.world, blockposition, iblockdata);
+ }
+
+@@ -586,7 +639,11 @@
+ int j = MathHelper.floor(entity.locZ / 16.0D);
+
+ if (i != this.locX || j != this.locZ) {
+- Chunk.c.warn("Wrong location! (" + i + ", " + j + ") should be (" + this.locX + ", " + this.locZ + "), " + entity, new Object[] { entity});
++ // CraftBukkit start
++ Bukkit.getLogger().warning("Wrong location for " + entity + " in world '" + world.getWorld().getName() + "'!");
++ // Chunk.c.warn("Wrong location! (" + i + ", " + j + ") should be (" + this.locX + ", " + this.locZ + "), " + entity, new Object[] { entity});
++ Bukkit.getLogger().warning("Entity is at " + entity.locX + "," + entity.locZ + " (chunk " + i + "," + j + ") but was stored in chunk " + this.locX + "," + this.locZ);
++ // CraftBukkit end
+ entity.die();
+ }
+
+@@ -673,6 +730,13 @@
+
+ tileentity.D();
+ this.tileEntities.put(blockposition, tileentity);
++ // CraftBukkit start
++ } else {
++ System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()
++ + " (" + org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(getType(blockposition)) + ") where there was no entity tile!");
++ System.out.println("Chunk coordinates: " + (this.locX * 16) + "," + (this.locZ * 16));
++ new Exception().printStackTrace();
++ // CraftBukkit end
+ }
+ }
+
+@@ -716,7 +780,21 @@
+ }
+
+ for (int i = 0; i < this.entitySlices.length; ++i) {
+- this.world.c((Collection) this.entitySlices[i]);
++ // CraftBukkit start
++ List<Entity> newList = Lists.newArrayList(this.entitySlices[i]);
++ java.util.Iterator<Entity> iter = newList.iterator();
++ while (iter.hasNext()) {
++ Entity entity = iter.next();
++
++ // 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)
++ if (entity instanceof EntityPlayer) {
++ iter.remove();
++ }
++ }
++
++ this.world.c((Collection) newList);
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/ChunkProviderServer.patch b/nms-patches/ChunkProviderServer.patch
new file mode 100644
index 00000000..bbe662e2
--- /dev/null
+++ b/nms-patches/ChunkProviderServer.patch
@@ -0,0 +1,348 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ChunkProviderServer.java 2014-11-27 08:59:46.593422322 +1100
++++ src/main/java/net/minecraft/server/ChunkProviderServer.java 2014-11-27 08:42:10.124850965 +1100
+@@ -10,17 +10,28 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.util.Random;
++import java.util.logging.Level;
++
++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;
++import org.bukkit.event.world.ChunkUnloadEvent;
++// CraftBukkit end
++
+ public class ChunkProviderServer implements IChunkProvider {
+
+ private static final Logger b = LogManager.getLogger();
+- public Set unloadQueue = Collections.newSetFromMap(new ConcurrentHashMap());
++ public LongHashSet unloadQueue = new LongHashSet(); // CraftBukkit - LongHashSet
+ public Chunk emptyChunk;
+ public IChunkProvider chunkProvider;
+ private IChunkLoader chunkLoader;
+- public boolean forceChunkLoad = true;
+- public LongHashMap chunks = new LongHashMap();
+- private List chunkList = Lists.newArrayList();
+- private WorldServer world;
++ public boolean forceChunkLoad = false; // CraftBukkit - true -> false
++ public LongObjectHashMap<Chunk> chunks = new LongObjectHashMap<Chunk>();
++ public WorldServer world; // CraftBukkit- public
+
+ public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, IChunkProvider ichunkprovider) {
+ this.emptyChunk = new EmptyChunk(worldserver, 0, 0);
+@@ -30,40 +41,93 @@
+ }
+
+ public boolean isChunkLoaded(int i, int j) {
+- return this.chunks.contains(ChunkCoordIntPair.a(i, j));
++ return this.chunks.containsKey(LongHash.toLong(i, j)); // CraftBukkit
+ }
+
+- public List a() {
+- return this.chunkList;
++ // CraftBukkit start - Change return type to Collection and return the values of our chunk map
++ public java.util.Collection a() {
++ // return this.chunkList;
++ return this.chunks.values();
++ // CraftBukkit end
+ }
+
+ public void queueUnload(int i, int j) {
+ if (this.world.worldProvider.e()) {
+ if (!this.world.c(i, j)) {
+- this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(i, j)));
++ // CraftBukkit start
++ this.unloadQueue.add(i, j);
++
++ Chunk c = chunks.get(LongHash.toLong(i, j));
++ if (c != null) {
++ c.mustSave = true;
++ }
++ // CraftBukkit end
+ }
+ } else {
+- this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(i, j)));
++ // CraftBukkit start
++ this.unloadQueue.add(i, j);
++
++ Chunk c = chunks.get(LongHash.toLong(i, j));
++ if (c != null) {
++ c.mustSave = true;
++ }
++ // CraftBukkit end
+ }
+
+ }
+
+ public void b() {
+- Iterator iterator = this.chunkList.iterator();
++ Iterator iterator = this.chunks.values().iterator();
+
+ while (iterator.hasNext()) {
+ Chunk chunk = (Chunk) iterator.next();
+
+ this.queueUnload(chunk.locX, chunk.locZ);
+ }
+-
++ }
++
++ // CraftBukkit start - Add async variant, provide compatibility
++ public Chunk getChunkIfLoaded(int x, int z) {
++ return chunks.get(LongHash.toLong(x, z));
+ }
+
+ public Chunk getChunkAt(int i, int j) {
+- long k = ChunkCoordIntPair.a(i, j);
+-
+- this.unloadQueue.remove(Long.valueOf(k));
+- Chunk chunk = (Chunk) this.chunks.getEntry(k);
++ return getChunkAt(i, j, null);
++ }
++
++ public Chunk getChunkAt(int i, int j, Runnable runnable) {
++ unloadQueue.remove(i, j);
++ Chunk chunk = chunks.get(LongHash.toLong(i, j));
++ ChunkRegionLoader loader = null;
++
++ if (this.chunkLoader instanceof ChunkRegionLoader) {
++ loader = (ChunkRegionLoader) this.chunkLoader;
++
++ }
++ // We can only use the queue for already generated chunks
++ if (chunk == null && loader != null && loader.chunkExists(world, i, j)) {
++ if (runnable != null) {
++ ChunkIOExecutor.queueChunkLoad(world, loader, this, i, j, runnable);
++ return null;
++ } else {
++ chunk = ChunkIOExecutor.syncChunkLoad(world, loader, this, i, j);
++ }
++ } else if (chunk == null) {
++ chunk = originalGetChunkAt(i, j);
++ }
++
++ // If we didn't load the chunk async and have a callback run it now
++ if (runnable != null) {
++ runnable.run();
++ }
++
++ 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) {
+ chunk = this.loadChunk(i, j);
+@@ -78,16 +142,44 @@
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated");
+
+ crashreportsystemdetails.a("Location", (Object) String.format("%d,%d", new Object[] { Integer.valueOf(i), Integer.valueOf(j)}));
+- crashreportsystemdetails.a("Position hash", (Object) Long.valueOf(k));
++ crashreportsystemdetails.a("Position hash", (Object) Long.valueOf(LongHash.toLong(i, j))); // CraftBukkit - Use LongHash
+ crashreportsystemdetails.a("Generator", (Object) this.chunkProvider.getName());
+ throw new ReportedException(crashreport);
+ }
+ }
++ newChunk = true; // CraftBukkit
+ }
+
+- this.chunks.put(k, chunk);
+- this.chunkList.add(chunk);
++ this.chunks.put(LongHash.toLong(i, j), chunk);
+ chunk.addEntities();
++
++ // CraftBukkit start
++ Server server = world.getServer();
++ if (server != null) {
++ /*
++ * If it's a new world, the first few chunks are generated inside
++ * the World constructor. We can't reliably alter that, so we have
++ * no way of creating a CraftWorld/CraftServer at that point.
++ */
++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, newChunk));
++ }
++
++ // Update neighbor counts
++ for (int x = -2; x < 3; x++) {
++ for (int z = -2; z < 3; z++) {
++ if (x == 0 && z == 0) {
++ continue;
++ }
++
++ Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
++ if (neighbor != null) {
++ neighbor.setNeighborLoaded(-x, -z);
++ chunk.setNeighborLoaded(x, z);
++ }
++ }
++ }
++ // CraftBukkit end
++
+ chunk.loadNearby(this, this, i, j);
+ }
+
+@@ -95,9 +187,22 @@
+ }
+
+ public Chunk getOrCreateChunk(int i, int j) {
+- Chunk chunk = (Chunk) this.chunks.getEntry(ChunkCoordIntPair.a(i, j));
++ // CraftBukkit start
++ Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j));
+
+- return chunk == null ? (!this.world.ad() && !this.forceChunkLoad ? this.emptyChunk : this.getChunkAt(i, j)) : chunk;
++ chunk = chunk == null ? (!this.world.ad() && !this.forceChunkLoad ? this.emptyChunk : this.getChunkAt(i, j)) : chunk;
++
++ if (chunk == emptyChunk) return chunk;
++ if (i != chunk.locX || j != chunk.locZ) {
++ b.error("Chunk (" + chunk.locX + ", " + chunk.locZ + ") stored at (" + i + ", " + j + ") in world '" + world.getWorld().getName() + "'");
++ b.error(chunk.getClass().getName());
++ Throwable ex = new Throwable();
++ ex.fillInStackTrace();
++ ex.printStackTrace();
++ }
++
++ return chunk;
++ // CraftBukkit end
+ }
+
+ public Chunk loadChunk(int i, int j) {
+@@ -138,10 +243,13 @@
+ try {
+ chunk.setLastSaved(this.world.getTime());
+ this.chunkLoader.a(this.world, chunk);
+- } catch (IOException ioexception) {
++ // CraftBukkit start - IOException to Exception
++ } catch (Exception ioexception) {
+ ChunkProviderServer.b.error("Couldn\'t save chunk", ioexception);
++ /* Remove extra exception
+ } catch (ExceptionWorldConflict exceptionworldconflict) {
+ ChunkProviderServer.b.error("Couldn\'t save chunk; already in use by another instance of Minecraft?", exceptionworldconflict);
++ // CraftBukkit end */
+ }
+
+ }
+@@ -154,6 +262,30 @@
+ chunk.n();
+ if (this.chunkProvider != null) {
+ this.chunkProvider.getChunkAt(ichunkprovider, i, j);
++
++ // CraftBukkit start
++ BlockSand.instaFall = true;
++ Random random = new Random();
++ random.setSeed(world.getSeed());
++ long xRand = random.nextLong() / 2L * 2L + 1L;
++ long zRand = random.nextLong() / 2L * 2L + 1L;
++ random.setSeed((long) i * xRand + (long) j * zRand ^ world.getSeed());
++
++ org.bukkit.World world = this.world.getWorld();
++ if (world != null) {
++ this.world.populating = true;
++ try {
++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
++ populator.populate(world, random, chunk.bukkitChunk);
++ }
++ } finally {
++ this.world.populating = false;
++ }
++ }
++ BlockSand.instaFall = false;
++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(chunk.bukkitChunk));
++ // CraftBukkit end
++
+ chunk.e();
+ }
+ }
+@@ -173,9 +305,12 @@
+
+ public boolean saveChunks(boolean flag, IProgressUpdate iprogressupdate) {
+ int i = 0;
+-
+- for (int j = 0; j < this.chunkList.size(); ++j) {
+- Chunk chunk = (Chunk) this.chunkList.get(j);
++
++ // CraftBukkit start
++ Iterator iterator = this.chunks.values().iterator();
++ while (iterator.hasNext()) {
++ Chunk chunk = (Chunk) iterator.next();
++ // CraftBukkit end
+
+ if (flag) {
+ this.saveChunkNOP(chunk);
+@@ -203,22 +338,42 @@
+
+ public boolean unloadChunks() {
+ if (!this.world.savingDisabled) {
+- for (int i = 0; i < 100; ++i) {
+- if (!this.unloadQueue.isEmpty()) {
+- Long olong = (Long) this.unloadQueue.iterator().next();
+- Chunk chunk = (Chunk) this.chunks.getEntry(olong.longValue());
+-
++ // CraftBukkit start
++ Server server = this.world.getServer();
++ for (int i = 0; i < 100 && !this.unloadQueue.isEmpty(); ++i) {
++ long chunkcoordinates = this.unloadQueue.popFirst();
++ Chunk chunk = this.chunks.get(chunkcoordinates);
++ if (chunk == null) continue;
++
++ ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
++ server.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
+ if (chunk != null) {
+ chunk.removeEntities();
+ this.saveChunk(chunk);
+ this.saveChunkNOP(chunk);
+- this.chunks.remove(olong.longValue());
+- this.chunkList.remove(chunk);
++ this.chunks.remove(chunkcoordinates); // CraftBukkit
+ }
+
+- this.unloadQueue.remove(olong);
++ // this.unloadQueue.remove(olong);
++
++ // Update neighbor counts
++ for (int x = -2; x < 3; x++) {
++ for (int z = -2; z < 3; z++) {
++ if (x == 0 && z == 0) {
++ continue;
++ }
++
++ Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
++ if (neighbor != null) {
++ neighbor.setNeighborUnloaded(-x, -z);
++ chunk.setNeighborUnloaded(x, z);
++ }
++ }
++ }
+ }
+- }
++ }
++ // CraftBukkit end
+
+ if (this.chunkLoader != null) {
+ this.chunkLoader.a();
+@@ -233,7 +388,8 @@
+ }
+
+ public String getName() {
+- return "ServerChunkCache: " + this.chunks.count() + " Drop: " + this.unloadQueue.size();
++ // CraftBukkit - this.chunks.count() -> .size()
++ return "ServerChunkCache: " + this.chunks.size() + " Drop: " + this.unloadQueue.size();
+ }
+
+ public List getMobsFor(EnumCreatureType enumcreaturetype, BlockPosition blockposition) {
+@@ -245,7 +401,8 @@
+ }
+
+ public int getLoadedChunks() {
+- return this.chunks.count();
++ // CraftBukkit - this.chunks.count() -> this.chunks.size()
++ return this.chunks.size();
+ }
+
+ public void recreateStructures(Chunk chunk, int i, int j) {}
diff --git a/nms-patches/ChunkRegionLoader.patch b/nms-patches/ChunkRegionLoader.patch
new file mode 100644
index 00000000..35c373ed
--- /dev/null
+++ b/nms-patches/ChunkRegionLoader.patch
@@ -0,0 +1,131 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ChunkRegionLoader.java 2014-11-27 08:59:46.593422322 +1100
++++ src/main/java/net/minecraft/server/ChunkRegionLoader.java 2014-11-27 08:42:10.136850942 +1100
+@@ -23,8 +23,40 @@
+ public ChunkRegionLoader(File file) {
+ this.e = file;
+ }
++
++ // CraftBukkit start
++ public boolean chunkExists(World world, int i, int j) {
++ 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;
++ }
++ }
++ }
++ }
++
++ return RegionFileCache.a(this.e, i, j).chunkExists(i & 31, j & 31);
++ }
++ // CraftBukkit end
+
++ // CraftBukkit start - Add async variant, provide compatibility
+ public Chunk a(World world, int i, int j) {
++ Object[] data = loadChunk(world, i, j);
++ if (data != null) {
++ Chunk chunk = (Chunk) data[0];
++ NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
++ loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
++ return chunk;
++ }
++
++ return null;
++ }
++
++ public Object[] loadChunk(World world, int i, int j) {
++ // CraftBukkit end
+ NBTTagCompound nbttagcompound = null;
+ ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
+ Object object = this.d;
+@@ -53,7 +85,7 @@
+ return this.a(world, i, j, nbttagcompound);
+ }
+
+- protected Chunk a(World world, int i, int j, NBTTagCompound nbttagcompound) {
++ protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
+ if (!nbttagcompound.hasKeyOfType("Level", 10)) {
+ ChunkRegionLoader.a.error("Chunk file at " + i + "," + j + " is missing level data, skipping");
+ return null;
+@@ -64,18 +96,42 @@
+ Chunk chunk = this.a(world, nbttagcompound.getCompound("Level"));
+
+ if (!chunk.a(i, j)) {
+- ChunkRegionLoader.a.error("Chunk file at " + i + "," + j + " is in the wrong location; relocating. (Expected " + i + ", " + j + ", got " + chunk.locX + ", " + chunk.locZ + ")");
+- nbttagcompound.setInt("xPos", i);
+- nbttagcompound.setInt("zPos", j);
++ a.error("Chunk file at " + i + "," + j + " is in the wrong location; relocating. (Expected " + i + ", " + j + ", got " + chunk.locX + ", " + chunk.locZ + ")");
++ nbttagcompound.getCompound("Level").setInt("xPos", i);
++ nbttagcompound.getCompound("Level").setInt("zPos", j);
++
++ // CraftBukkit start - Have to move tile entities since we don't load them at this stage
++ NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
++ if (tileEntities != null) {
++ for (int te = 0; te < tileEntities.size(); te++) {
++ NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
++ int x = tileEntity.getInt("x") - chunk.locX * 16;
++ int z = tileEntity.getInt("z") - chunk.locZ * 16;
++ tileEntity.setInt("x", i * 16 + x);
++ tileEntity.setInt("z", j * 16 + z);
++ }
++ }
++ // CraftBukkit end
+ chunk = this.a(world, nbttagcompound.getCompound("Level"));
+ }
+
+- return chunk;
++ // CraftBukkit start
++ Object[] data = new Object[2];
++ data[0] = chunk;
++ data[1] = nbttagcompound;
++ return data;
++ // CraftBukkit end
+ }
+ }
+
+ public void a(World world, Chunk chunk) {
+- world.checkSession();
++ // CraftBukkit start - "handle" exception
++ try {
++ world.checkSession();
++ } catch (ExceptionWorldConflict ex) {
++ ex.printStackTrace();
++ }
++ // CraftBukkit end
+
+ try {
+ NBTTagCompound nbttagcompound = new NBTTagCompound();
+@@ -133,7 +189,7 @@
+ return true;
+ }
+
+- public void a(PendingChunkToSave pendingchunktosave) {
++ public void a(PendingChunkToSave pendingchunktosave) throws java.io.IOException { // CraftBukkit - added throws
+ DataOutputStream dataoutputstream = RegionFileCache.d(this.e, pendingchunktosave.a.x, pendingchunktosave.a.z);
+
+ NBTCompressedStreamTools.a(pendingchunktosave.b, (DataOutput) dataoutputstream);
+@@ -320,7 +376,13 @@
+ if (nbttagcompound.hasKeyOfType("Biomes", 7)) {
+ chunk.a(nbttagcompound.getByteArray("Biomes"));
+ }
++
++ // CraftBukkit start - End this method here and split off entity loading to another method
++ return chunk;
++ }
+
++ public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
++ // CraftBukkit end
+ NBTTagList nbttaglist1 = nbttagcompound.getList("Entities", 10);
+
+ if (nbttaglist1 != null) {
+@@ -379,6 +441,6 @@
+ }
+ }
+
+- return chunk;
++ // return chunk; // CraftBukkit
+ }
+ }
diff --git a/nms-patches/ChunkSection.patch b/nms-patches/ChunkSection.patch
new file mode 100644
index 00000000..4063d6cc
--- /dev/null
+++ b/nms-patches/ChunkSection.patch
@@ -0,0 +1,21 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ChunkSection.java 2014-11-27 08:59:46.597422305 +1100
++++ src/main/java/net/minecraft/server/ChunkSection.java 2014-11-27 08:42:10.172850872 +1100
+@@ -19,6 +19,18 @@
+
+ }
+
++ // CraftBukkit start
++ public ChunkSection(int y, boolean flag, char[] blockIds) {
++ this.yPos = y;
++ this.blockIds = blockIds;
++ this.emittedLight = new NibbleArray();
++ if (flag) {
++ this.skyLight = new NibbleArray();
++ }
++ recalcBlockCounts();
++ }
++ // CraftBukkit end
++
+ public IBlockData getType(int i, int j, int k) {
+ IBlockData iblockdata = (IBlockData) Block.d.a(this.blockIds[j << 8 | k << 4 | i]);
+
diff --git a/nms-patches/CommandBlockListenerAbstract.patch b/nms-patches/CommandBlockListenerAbstract.patch
new file mode 100644
index 00000000..c49fad99
--- /dev/null
+++ b/nms-patches/CommandBlockListenerAbstract.patch
@@ -0,0 +1,162 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/CommandBlockListenerAbstract.java 2014-11-27 08:59:46.597422305 +1100
++++ src/main/java/net/minecraft/server/CommandBlockListenerAbstract.java 2014-11-27 08:42:10.172850872 +1100
+@@ -4,6 +4,13 @@
+ import java.util.Date;
+ import java.util.concurrent.Callable;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import org.apache.logging.log4j.Level;
++import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
++import com.google.common.base.Joiner;
++// CraftBukkit end
++
+ public abstract class CommandBlockListenerAbstract implements ICommandListener {
+
+ private static final SimpleDateFormat a = new SimpleDateFormat("HH:mm:ss");
+@@ -13,6 +20,7 @@
+ public String e = "";
+ private String f = "@";
+ private final CommandObjectiveExecutor g = new CommandObjectiveExecutor();
++ protected org.bukkit.command.CommandSender sender; // CraftBukkit - add sender
+
+ public CommandBlockListenerAbstract() {}
+
+@@ -79,7 +87,109 @@
+
+ try {
+ this.d = null;
+- this.b = icommandhandler.a(this, this.e);
++ // this.b = icommandhandler.a(this, this.e);
++ // CraftBukkit start - Handle command block commands using Bukkit dispatcher
++ org.bukkit.command.SimpleCommandMap commandMap = minecraftserver.server.getCommandMap();
++ Joiner joiner = Joiner.on(" ");
++ String command = this.e;
++ if (this.e.startsWith("/")) {
++ command = this.e.substring(1);
++ }
++ String[] args = command.split(" ");
++ ArrayList<String[]> commands = new ArrayList<String[]>();
++
++ // Block disallowed commands
++ if (args[0].equalsIgnoreCase("stop") || args[0].equalsIgnoreCase("kick") || args[0].equalsIgnoreCase("op") ||
++ args[0].equalsIgnoreCase("deop") || args[0].equalsIgnoreCase("ban") || args[0].equalsIgnoreCase("ban-ip") ||
++ args[0].equalsIgnoreCase("pardon") || args[0].equalsIgnoreCase("pardon-ip") || args[0].equalsIgnoreCase("reload")) {
++ this.b = 0;
++ return;
++ }
++
++ // If the world has no players don't run
++ if (this.getWorld().players.isEmpty()) {
++ this.b = 0;
++ return;
++ }
++
++ // Handle vanilla commands;
++ if (minecraftserver.server.getCommandBlockOverride(args[0])) {
++ org.bukkit.command.Command commandBlockCommand = commandMap.getCommand("minecraft:" + args[0]);
++ if (commandBlockCommand instanceof VanillaCommandWrapper) {
++ this.b = ((VanillaCommandWrapper) commandBlockCommand).dispatchVanillaCommandBlock(this, this.e);
++ return;
++ }
++ }
++
++ // Make sure this is a valid command
++ if (commandMap.getCommand(args[0]) == null) {
++ this.b = 0;
++ return;
++ }
++
++ // testfor command requires special handling
++ if (args[0].equalsIgnoreCase("testfor")) {
++ if (args.length < 2) {
++ this.b = 0;
++ return;
++ }
++
++ EntityPlayer[] players = ((java.util.List<EntityPlayer>)PlayerSelector.getPlayers(this, args[1], EntityPlayer.class)).toArray(new EntityPlayer[0]);
++
++ if (players != null && players.length > 0) {
++ this.b = players.length;
++ return;
++ } else {
++ EntityPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(args[1]);
++ if (player == null) {
++ this.b = 0;
++ return;
++ } else {
++ this.b = 1;
++ return;
++ }
++ }
++ }
++
++ commands.add(args);
++
++ // Find positions of command block syntax, if any
++ ArrayList<String[]> newCommands = new ArrayList<String[]>();
++ for (int i = 0; i < args.length; i++) {
++ if (PlayerSelector.isPattern(args[i])) {
++ for (int j = 0; j < commands.size(); j++) {
++ newCommands.addAll(this.buildCommands(commands.get(j), i));
++ }
++ ArrayList<String[]> temp = commands;
++ commands = newCommands;
++ newCommands = temp;
++ newCommands.clear();
++ }
++ }
++
++ int completed = 0;
++
++ // Now dispatch all of the commands we ended up with
++ for (int i = 0; i < commands.size(); i++) {
++ try {
++ if (commandMap.dispatch(sender, joiner.join(java.util.Arrays.asList(commands.get(i))))) {
++ completed++;
++ }
++ } catch (Throwable exception) {
++ if(this instanceof TileEntityCommandListener) {
++ TileEntityCommandListener listener = (TileEntityCommandListener) this;
++ MinecraftServer.getLogger().log(Level.WARN, String.format("CommandBlock at (%d,%d,%d) failed to handle command", listener.getChunkCoordinates().getX(), listener.getChunkCoordinates().getY(), listener.getChunkCoordinates().getZ()), exception);
++ } else if (this instanceof EntityMinecartCommandBlockListener) {
++ EntityMinecartCommandBlockListener listener = (EntityMinecartCommandBlockListener) this;
++ MinecraftServer.getLogger().log(Level.WARN, String.format("MinecartCommandBlock at (%d,%d,%d) failed to handle command", listener.getChunkCoordinates().getX(), listener.getChunkCoordinates().getY(), listener.getChunkCoordinates().getZ()), exception);
++ } else {
++ MinecraftServer.getLogger().log(Level.WARN, String.format("Unknown CommandBlock failed to handle command"), exception);
++ }
++ }
++ }
++
++ this.b = completed;
++ // CraftBukkit end
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.a(throwable, "Executing command block");
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Command to be executed");
+@@ -91,8 +201,26 @@
+ } else {
+ this.b = 0;
+ }
++ }
++
++ // CraftBukkit start
++ private ArrayList<String[]> buildCommands(String[] args, int pos) {
++ ArrayList<String[]> commands = new ArrayList<String[]>();
++ EntityPlayer[] players = ((java.util.List<EntityPlayer>)PlayerSelector.getPlayers(this, args[pos], EntityPlayer.class)).toArray(new EntityPlayer[0]);
++ if (players != null) {
++ for (EntityPlayer player : players) {
++ if (player.world != this.getWorld()) {
++ continue;
++ }
++ String[] command = args.clone();
++ command[pos] = player.getName();
++ commands.add(command);
++ }
++ }
+
++ return commands;
+ }
++ // CraftBukkit end
+
+ public String getName() {
+ return this.f;
diff --git a/nms-patches/Container.patch b/nms-patches/Container.patch
new file mode 100644
index 00000000..4891ecc5
--- /dev/null
+++ b/nms-patches/Container.patch
@@ -0,0 +1,204 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Container.java 2014-11-27 08:59:46.617422217 +1100
++++ src/main/java/net/minecraft/server/Container.java 2014-11-27 08:42:10.156850903 +1100
+@@ -7,6 +7,17 @@
+ import java.util.List;
+ import java.util.Set;
+
++// CraftBukkit start
++import java.util.HashMap;
++import java.util.Map;
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.Event.Result;
++import org.bukkit.event.inventory.InventoryDragEvent;
++import org.bukkit.event.inventory.InventoryType;
++import org.bukkit.inventory.InventoryView;
++// CraftBukkit end
++
+ public abstract class Container {
+
+ public List b = Lists.newArrayList();
+@@ -17,6 +28,18 @@
+ private final Set h = Sets.newHashSet();
+ protected List listeners = Lists.newArrayList();
+ private Set i = Sets.newHashSet();
++
++ // CraftBukkit start
++ public boolean checkReachable = true;
++ public abstract InventoryView getBukkitView();
++ public void transferTo(Container other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++ InventoryView source = this.getBukkitView(), destination = other.getBukkitView();
++ ((CraftInventory) source.getTopInventory()).getInventory().onClose(player);
++ ((CraftInventory) source.getBottomInventory()).getInventory().onClose(player);
++ ((CraftInventory) destination.getTopInventory()).getInventory().onOpen(player);
++ ((CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player);
++ }
++ // CraftBukkit end
+
+ public Container() {}
+
+@@ -124,6 +147,7 @@
+ l = playerinventory.getCarried().count;
+ Iterator iterator = this.h.iterator();
+
++ Map<Integer, ItemStack> draggedSlots = new HashMap<Integer, ItemStack>(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack)
+ while (iterator.hasNext()) {
+ Slot slot1 = (Slot) iterator.next();
+
+@@ -141,16 +165,49 @@
+ }
+
+ l -= itemstack2.count - j1;
+- slot1.set(itemstack2);
++ // slot1.set(itemstack2);
++ draggedSlots.put(slot1.rawSlotIndex, itemstack2); // CraftBukkit - Put in map instead of setting
+ }
+ }
++
++ // CraftBukkit start - InventoryDragEvent
++ InventoryView view = getBukkitView();
++ org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack1);
++ newcursor.setAmount(l);
++ Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new HashMap<Integer, org.bukkit.inventory.ItemStack>();
++ for (Map.Entry<Integer, ItemStack> ditem : draggedSlots.entrySet()) {
++ eventmap.put(ditem.getKey(), CraftItemStack.asBukkitCopy(ditem.getValue()));
++ }
++
++ // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory.
++ ItemStack oldCursor = playerinventory.getCarried();
++ playerinventory.setCarried(CraftItemStack.asNMSCopy(newcursor));
++
++ InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.dragType == 1, eventmap);
++ entityhuman.world.getServer().getPluginManager().callEvent(event);
++
++ // Whether or not a change was made to the inventory that requires an update.
++ boolean needsUpdate = event.getResult() != Result.DEFAULT;
++
++ if (event.getResult() != Result.DENY) {
++ for (Map.Entry<Integer, ItemStack> dslot : draggedSlots.entrySet()) {
++ view.setItem(dslot.getKey(), CraftItemStack.asBukkitCopy(dslot.getValue()));
++ }
++ // The only time the carried item will be set to null is if the inventory is closed by the server.
++ // If the inventory is closed by the server, then the cursor items are dropped. This is why we change the cursor early.
++ if (playerinventory.getCarried() != null) {
++ playerinventory.setCarried(CraftItemStack.asNMSCopy(event.getCursor()));
++ needsUpdate = true;
+
+- itemstack1.count = l;
+- if (itemstack1.count <= 0) {
+- itemstack1 = null;
++ }
++ } else {
++ playerinventory.setCarried(oldCursor);
+ }
+
+- playerinventory.setCarried(itemstack1);
++ if (needsUpdate && entityhuman instanceof EntityPlayer) {
++ ((EntityPlayer) entityhuman).updateInventory(this);
++ }
++ // CraftBukkit end
+ }
+
+ this.d();
+@@ -173,8 +230,14 @@
+ }
+
+ if (j == 1) {
+- entityhuman.drop(playerinventory.getCarried().a(1), true);
+- if (playerinventory.getCarried().count == 0) {
++ // CraftBukkit start - Store a reference
++ ItemStack itemstack4 = playerinventory.getCarried();
++ if (itemstack4.count > 0) {
++ entityhuman.drop(itemstack4.a(1), true);
++ }
++
++ if (itemstack4.count == 0) {
++ // CraftBukkit end
+ playerinventory.setCarried((ItemStack) null);
+ }
+ }
+@@ -223,7 +286,11 @@
+
+ if (itemstack4.count == 0) {
+ playerinventory.setCarried((ItemStack) null);
++ // CraftBukkit start - Update client cursor if we didn't empty it
++ } else if (entityhuman instanceof EntityPlayer) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, entityhuman.inventory.getCarried()));
+ }
++ // CraftBukkit end
+ }
+ } else if (slot2.isAllowed(entityhuman)) {
+ if (itemstack4 == null) {
+@@ -249,7 +316,11 @@
+ itemstack4.a(k1);
+ if (itemstack4.count == 0) {
+ playerinventory.setCarried((ItemStack) null);
++ // CraftBukkit start - Update client cursor if we didn't empty it
++ } else if (entityhuman instanceof EntityPlayer) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, entityhuman.inventory.getCarried()));
+ }
++ // CraftBukkit end
+
+ itemstack1.count += k1;
+ } else if (itemstack4.count <= slot2.getMaxStackSize(itemstack4)) {
+@@ -258,7 +329,9 @@
+ }
+ } else if (itemstack1.getItem() == itemstack4.getItem() && itemstack4.getMaxStackSize() > 1 && (!itemstack1.usesData() || itemstack1.getData() == itemstack4.getData()) && ItemStack.equals(itemstack1, itemstack4)) {
+ k1 = itemstack1.count;
+- if (k1 > 0 && k1 + itemstack4.count <= itemstack4.getMaxStackSize()) {
++ // CraftBukkit start - itemstack4.getMaxStackSize() -> maxStack
++ int maxStack = Math.min(itemstack4.getMaxStackSize(), slot2.getMaxStackSize());
++ if (k1 > 0 && k1 + itemstack4.count <= maxStack) {
+ itemstack4.count += k1;
+ itemstack1 = slot2.a(k1);
+ if (itemstack1.count == 0) {
+@@ -266,11 +339,24 @@
+ }
+
+ slot2.a(entityhuman, playerinventory.getCarried());
++ // CraftBukkit start - Update client cursor if we didn't empty it
++ } else if (entityhuman instanceof EntityPlayer) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, entityhuman.inventory.getCarried()));
+ }
++ // CraftBukkit end
+ }
+ }
+
+ slot2.f();
++ // CraftBukkit start - Make sure the client has the right slot contents
++ if (entityhuman instanceof EntityPlayer && slot2.getMaxStackSize() != 64) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutSetSlot(this.windowId, slot2.rawSlotIndex, slot2.getItem()));
++ // Updating a crafting inventory makes the client reset the result slot, have to send it again
++ if (this.getBukkitView().getType() == InventoryType.WORKBENCH || this.getBukkitView().getType() == InventoryType.CRAFTING) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutSetSlot(this.windowId, 0, this.getSlot(0).getItem()));
++ }
++ }
++ // CraftBukkit end
+ }
+ }
+ } else if (k == 2 && j >= 0 && j < 9) {
+@@ -411,17 +497,20 @@
+ if (itemstack1 != null && itemstack1.getItem() == itemstack.getItem() && (!itemstack.usesData() || itemstack.getData() == itemstack1.getData()) && ItemStack.equals(itemstack, itemstack1)) {
+ int l = itemstack1.count + itemstack.count;
+
+- if (l <= itemstack.getMaxStackSize()) {
++ // CraftBukkit start - itemstack.getMaxStackSize() -> maxStack
++ int maxStack = Math.min(itemstack.getMaxStackSize(), slot.getMaxStackSize());
++ if (l <= maxStack) {
+ itemstack.count = 0;
+ itemstack1.count = l;
+ slot.f();
+ flag1 = true;
+- } else if (itemstack1.count < itemstack.getMaxStackSize()) {
+- itemstack.count -= itemstack.getMaxStackSize() - itemstack1.count;
+- itemstack1.count = itemstack.getMaxStackSize();
++ } else if (itemstack1.count < maxStack) {
++ itemstack.count -= maxStack - itemstack1.count;
++ itemstack1.count = maxStack;
+ slot.f();
+ flag1 = true;
+ }
++ // CraftBukkit end
+ }
+
+ if (flag) {
diff --git a/nms-patches/ContainerAnvil.patch b/nms-patches/ContainerAnvil.patch
new file mode 100644
index 00000000..1c0c583a
--- /dev/null
+++ b/nms-patches/ContainerAnvil.patch
@@ -0,0 +1,51 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerAnvil.java 2014-11-27 08:59:46.601422287 +1100
++++ src/main/java/net/minecraft/server/ContainerAnvil.java 2014-11-27 08:42:10.144850927 +1100
+@@ -6,6 +6,8 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
++
+ public class ContainerAnvil extends Container {
+
+ private static final Logger f = LogManager.getLogger();
+@@ -17,8 +19,13 @@
+ private int k;
+ private String l;
+ private final EntityHuman m;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerAnvil(PlayerInventory playerinventory, World world, BlockPosition blockposition, EntityHuman entityhuman) {
++ this.player = playerinventory; // CraftBukkit
+ this.j = blockposition;
+ this.i = world;
+ this.m = entityhuman;
+@@ -265,6 +272,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.i.getType(this.j).getBlock() != Blocks.ANVIL ? false : entityhuman.e((double) this.j.getX() + 0.5D, (double) this.j.getY() + 0.5D, (double) this.j.getZ() + 0.5D) <= 64.0D;
+ }
+
+@@ -328,4 +336,17 @@
+ static int b(ContainerAnvil containeranvil) {
+ return containeranvil.k;
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryAnvil(this.h, this.g);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerAnvilInventory.patch b/nms-patches/ContainerAnvilInventory.patch
new file mode 100644
index 00000000..16649334
--- /dev/null
+++ b/nms-patches/ContainerAnvilInventory.patch
@@ -0,0 +1,58 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerAnvilInventory.java 2014-11-27 08:59:46.597422305 +1100
++++ src/main/java/net/minecraft/server/ContainerAnvilInventory.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,8 +1,43 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class ContainerAnvilInventory extends InventorySubcontainer {
+
+ final ContainerAnvil a;
++
++ // CraftBukkit start
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ public org.bukkit.entity.Player player;
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return this.player;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
+
+ ContainerAnvilInventory(ContainerAnvil containeranvil, String s, boolean flag, int i) {
+ super(s, flag, i);
+@@ -13,4 +48,11 @@
+ super.update();
+ this.a.a((IInventory) this);
+ }
++
++ // CraftBukkit start - override inherited maxStack from InventorySubcontainer
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerBeacon.patch b/nms-patches/ContainerBeacon.patch
new file mode 100644
index 00000000..178e143c
--- /dev/null
+++ b/nms-patches/ContainerBeacon.patch
@@ -0,0 +1,47 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerBeacon.java 2014-11-27 08:59:46.601422287 +1100
++++ src/main/java/net/minecraft/server/ContainerBeacon.java 2014-11-27 08:42:10.156850903 +1100
+@@ -1,11 +1,18 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
++
+ public class ContainerBeacon extends Container {
+
+ private IInventory a;
+ private final SlotBeacon f;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerBeacon(IInventory iinventory, IInventory iinventory1) {
++ player = (PlayerInventory) iinventory; // CraftBukkit - TODO: check this
+ this.a = iinventory1;
+ this.a((Slot) (this.f = new SlotBeacon(this, iinventory1, 0, 136, 110)));
+ byte b0 = 36;
+@@ -35,6 +42,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.a.a(entityhuman);
+ }
+
+@@ -83,4 +91,17 @@
+
+ return itemstack;
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon((TileEntityBeacon) this.a); // TODO - check this
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerBrewingStand.patch b/nms-patches/ContainerBrewingStand.patch
new file mode 100644
index 00000000..b7aba135
--- /dev/null
+++ b/nms-patches/ContainerBrewingStand.patch
@@ -0,0 +1,52 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerBrewingStand.java 2014-11-27 08:59:46.605422269 +1100
++++ src/main/java/net/minecraft/server/ContainerBrewingStand.java 2014-11-27 08:42:10.172850872 +1100
+@@ -1,12 +1,23 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryBrewer;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerBrewingStand extends Container {
+
+ private IInventory brewingStand;
+ private final Slot f;
+ private int g;
++
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerBrewingStand(PlayerInventory playerinventory, IInventory iinventory) {
++ player = playerinventory; // CraftBukkit
+ this.brewingStand = iinventory;
+ this.a((Slot) (new SlotPotionBottle(playerinventory.player, iinventory, 0, 56, 46)));
+ this.a((Slot) (new SlotPotionBottle(playerinventory.player, iinventory, 1, 79, 53)));
+@@ -47,6 +58,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.brewingStand.a(entityhuman);
+ }
+
+@@ -101,4 +113,17 @@
+
+ return itemstack;
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryBrewer inventory = new CraftInventoryBrewer(this.brewingStand);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerChest.patch b/nms-patches/ContainerChest.patch
new file mode 100644
index 00000000..7021c61b
--- /dev/null
+++ b/nms-patches/ContainerChest.patch
@@ -0,0 +1,59 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerChest.java 2014-11-27 08:59:46.605422269 +1100
++++ src/main/java/net/minecraft/server/ContainerChest.java 2014-11-27 08:42:10.172850872 +1100
+@@ -1,15 +1,48 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerChest extends Container {
+
+ public IInventory container;
+ private int f;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory;
++ if (this.container instanceof PlayerInventory) {
++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryPlayer((PlayerInventory) this.container);
++ } else if (this.container instanceof InventoryLargeChest) {
++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) this.container);
++ } else {
++ inventory = new CraftInventory(this.container);
++ }
++
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+
+ public ContainerChest(IInventory iinventory, IInventory iinventory1, EntityHuman entityhuman) {
+ this.container = iinventory1;
+ this.f = iinventory1.getSize() / 9;
+ iinventory1.startOpen(entityhuman);
+ int i = (this.f - 4) * 18;
++
++ // CraftBukkit start - Save player
++ // TODO: Should we check to make sure it really is an InventoryPlayer?
++ this.player = (PlayerInventory) iinventory;
++ // CraftBukkit end
+
+ int j;
+ int k;
+@@ -33,6 +66,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.a(entityhuman);
+ }
+
diff --git a/nms-patches/ContainerDispenser.patch b/nms-patches/ContainerDispenser.patch
new file mode 100644
index 00000000..cee54779
--- /dev/null
+++ b/nms-patches/ContainerDispenser.patch
@@ -0,0 +1,54 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerDispenser.java 2014-11-27 08:59:46.605422269 +1100
++++ src/main/java/net/minecraft/server/ContainerDispenser.java 2014-11-27 08:42:10.148850918 +1100
+@@ -1,11 +1,24 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerDispenser extends Container {
+
+- private IInventory items;
++ public IInventory items; // CraftBukkit - private -> public
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerDispenser(IInventory iinventory, IInventory iinventory1) {
+ this.items = iinventory1;
++ // CraftBukkit start - Save player
++ // TODO: Should we check to make sure it really is an InventoryPlayer?
++ this.player = (PlayerInventory)iinventory;
++ // CraftBukkit end
+
+ int i;
+ int j;
+@@ -29,6 +42,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.items.a(entityhuman);
+ }
+
+@@ -63,4 +77,17 @@
+
+ return itemstack;
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory = new CraftInventory(this.items);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerEnchantTable.patch b/nms-patches/ContainerEnchantTable.patch
new file mode 100644
index 00000000..f2d866d8
--- /dev/null
+++ b/nms-patches/ContainerEnchantTable.patch
@@ -0,0 +1,170 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerEnchantTable.java 2014-11-27 08:59:46.609422252 +1100
++++ src/main/java/net/minecraft/server/ContainerEnchantTable.java 2014-11-27 08:42:10.120850973 +1100
+@@ -3,15 +3,31 @@
+ import java.util.List;
+ import java.util.Random;
+
++// CraftBukkit start
++import java.util.Map;
++
++import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.enchantment.EnchantItemEvent;
++import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
++import org.bukkit.entity.Player;
++// CraftBukkit end
++
+ public class ContainerEnchantTable extends Container {
+
+- public IInventory enchantSlots = new ContainerEnchantTableInventory(this, "Enchant", true, 2);
++ // CraftBukkit - make type specific (changed from IInventory)
++ public ContainerEnchantTableInventory enchantSlots = new ContainerEnchantTableInventory(this, "Enchant", true, 2);
+ private World world;
+ private BlockPosition position;
+ private Random k = new Random();
+ public int f;
+ public int[] costs = new int[3];
+ public int[] h = new int[] { -1, -1, -1};
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++ // CraftBukkit end
+
+ public ContainerEnchantTable(PlayerInventory playerinventory, World world, BlockPosition blockposition) {
+ this.world = world;
+@@ -31,7 +47,11 @@
+ for (i = 0; i < 9; ++i) {
+ this.a(new Slot(playerinventory, i, 8 + i * 18, 142));
+ }
+-
++
++ // CraftBukkit start
++ player = (Player) playerinventory.player.getBukkitEntity();
++ enchantSlots.player = player;
++ // CraftBukkit end
+ }
+
+ public void addSlotListener(ICrafting icrafting) {
+@@ -67,7 +87,7 @@
+ ItemStack itemstack = iinventory.getItem(0);
+ int i;
+
+- if (itemstack != null && itemstack.v()) {
++ if (itemstack != null) { // CraftBukkit - relax condition
+ if (!this.world.isStatic) {
+ i = 0;
+
+@@ -114,6 +134,20 @@
+ this.costs[j] = 0;
+ }
+ }
++
++ // CraftBukkit start
++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
++ PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, this.getBukkitView(), this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, this.costs, i);
++ event.setCancelled(!itemstack.v());
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ for (i = 0; i < 3; ++i) {
++ this.costs[i] = 0;
++ }
++ return;
++ }
++ // CraftBukkit end
+
+ for (j = 0; j < 3; ++j) {
+ if (this.costs[j] > 0) {
+@@ -149,24 +183,56 @@
+ } else if (this.costs[i] > 0 && itemstack != null && (entityhuman.expLevel >= j && entityhuman.expLevel >= this.costs[i] || entityhuman.abilities.canInstantlyBuild)) {
+ if (!this.world.isStatic) {
+ List list = this.a(itemstack, i, this.costs[i]);
++ // CraftBukkit start - Provide an empty enchantment list
++ if (list == null) {
++ list = new java.util.ArrayList<WeightedRandomEnchant>();
++ }
++ // CraftBukkit end
+ boolean flag = itemstack.getItem() == Items.BOOK;
+
+ if (list != null) {
+- entityhuman.b(j);
++ // CraftBukkit start
++ Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
++ for (Object obj : list) {
++ WeightedRandomEnchant instance = (WeightedRandomEnchant) obj;
++ enchants.put(org.bukkit.enchantments.Enchantment.getById(instance.enchantment.id), instance.level);
++ }
++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
++
++ EnchantItemEvent event = new EnchantItemEvent((Player) entityhuman.getBukkitEntity(), this.getBukkitView(), this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, this.costs[i], enchants, i);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ int level = event.getExpLevelCost();
++ if (event.isCancelled() || (level > entityhuman.expLevel && !entityhuman.abilities.canInstantlyBuild) || event.getEnchantsToAdd().isEmpty()) {
++ return false;
++ }
++
+ if (flag) {
+ itemstack.setItem(Items.ENCHANTED_BOOK);
+ }
++
++ for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
++ try {
++ if (flag) {
++ int enchantId = entry.getKey().getId();
++ if (Enchantment.getById(enchantId) == null) {
++ continue;
++ }
+
+- for (int k = 0; k < list.size(); ++k) {
+- WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) list.get(k);
+-
+- if (flag) {
+- Items.ENCHANTED_BOOK.a(itemstack, weightedrandomenchant);
+- } else {
+- itemstack.addEnchantment(weightedrandomenchant.enchantment, weightedrandomenchant.level);
++ WeightedRandomEnchant enchantment = new WeightedRandomEnchant(Enchantment.getById(enchantId), entry.getValue());
++ Items.ENCHANTED_BOOK.a(itemstack, enchantment);
++ } else {
++ item.addUnsafeEnchantment(entry.getKey(), entry.getValue());
++ }
++ } catch (IllegalArgumentException e) {
++ /* Just swallow invalid enchantments */
+ }
+ }
++
++ entityhuman.b(level);
++ // CraftBukkit end
+
++ // CraftBukkit - TODO: let plugins change this
+ if (!entityhuman.abilities.canInstantlyBuild) {
+ itemstack1.count -= j;
+ if (itemstack1.count <= 0) {
+@@ -212,6 +278,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.world.getType(this.position).getBlock() != Blocks.ENCHANTING_TABLE ? false : entityhuman.e((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D) <= 64.0D;
+ }
+
+@@ -263,5 +330,18 @@
+ }
+
+ return itemstack;
++ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
+ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerEnchantTableInventory.patch b/nms-patches/ContainerEnchantTableInventory.patch
new file mode 100644
index 00000000..f2f80b3e
--- /dev/null
+++ b/nms-patches/ContainerEnchantTableInventory.patch
@@ -0,0 +1,58 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerEnchantTableInventory.java 2014-11-27 08:59:46.609422252 +1100
++++ src/main/java/net/minecraft/server/ContainerEnchantTableInventory.java 2014-11-27 08:42:10.088851036 +1100
+@@ -1,8 +1,45 @@
+ package net.minecraft.server;
+
+-class ContainerEnchantTableInventory extends InventorySubcontainer {
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
++// CraftBukkit -> public
++public class ContainerEnchantTableInventory extends InventorySubcontainer {
+
+ final ContainerEnchantTable enchantTable;
++
++ // CraftBukkit start
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ public org.bukkit.entity.Player player;
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return this.player;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ ContainerEnchantTableInventory(ContainerEnchantTable containerenchanttable, String s, boolean flag, int i) {
+ super(s, flag, i);
+@@ -10,7 +47,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public void update() {
diff --git a/nms-patches/ContainerFurnace.patch b/nms-patches/ContainerFurnace.patch
new file mode 100644
index 00000000..b299ee20
--- /dev/null
+++ b/nms-patches/ContainerFurnace.patch
@@ -0,0 +1,51 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerFurnace.java 2014-11-27 08:59:46.613422234 +1100
++++ src/main/java/net/minecraft/server/ContainerFurnace.java 2014-11-27 08:42:10.116850981 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryFurnace;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerFurnace extends Container {
+
+ private final IInventory furnace;
+@@ -7,12 +12,29 @@
+ private int g;
+ private int h;
+ private int i;
++
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryFurnace inventory = new CraftInventoryFurnace((TileEntityFurnace) this.furnace);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+
+ public ContainerFurnace(PlayerInventory playerinventory, IInventory iinventory) {
+ this.furnace = iinventory;
+ this.a(new Slot(iinventory, 0, 56, 17));
+ this.a((Slot) (new SlotFurnaceFuel(iinventory, 1, 56, 53)));
+ this.a((Slot) (new SlotFurnaceResult(playerinventory.player, iinventory, 2, 116, 35)));
++ this.player = playerinventory; // CraftBukkit - save player
+
+ int i;
+
+@@ -63,6 +85,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.furnace.a(entityhuman);
+ }
+
diff --git a/nms-patches/ContainerHopper.patch b/nms-patches/ContainerHopper.patch
new file mode 100644
index 00000000..4654d33a
--- /dev/null
+++ b/nms-patches/ContainerHopper.patch
@@ -0,0 +1,44 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerHopper.java 2014-11-27 08:59:46.613422234 +1100
++++ src/main/java/net/minecraft/server/ContainerHopper.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,11 +1,33 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerHopper extends Container {
+
+ private final IInventory hopper;
++
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory = new CraftInventory(this.hopper);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+
+ public ContainerHopper(PlayerInventory playerinventory, IInventory iinventory, EntityHuman entityhuman) {
+ this.hopper = iinventory;
++ this.player = playerinventory; // CraftBukkit - save player
+ iinventory.startOpen(entityhuman);
+ byte b0 = 51;
+
+@@ -28,6 +50,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.hopper.a(entityhuman);
+ }
+
diff --git a/nms-patches/ContainerHorse.patch b/nms-patches/ContainerHorse.patch
new file mode 100644
index 00000000..9c9fa57a
--- /dev/null
+++ b/nms-patches/ContainerHorse.patch
@@ -0,0 +1,36 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerHorse.java 2014-11-27 08:59:46.613422234 +1100
++++ src/main/java/net/minecraft/server/ContainerHorse.java 2014-11-27 08:42:10.124850965 +1100
+@@ -1,11 +1,33 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.inventory.InventoryView;
++// CraftBukkit end
++
+ public class ContainerHorse extends Container {
+
+ private IInventory a;
+ private EntityHorse f;
++
++ // CraftBukkit start
++ org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
++ PlayerInventory player;
++
++ @Override
++ public InventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryHorse(this.a);
++ return bukkitEntity = new CraftInventoryView(player.player.getBukkitEntity(), inventory, this);
++ }
+
+ public ContainerHorse(IInventory iinventory, IInventory iinventory1, EntityHorse entityhorse, EntityHuman entityhuman) {
++ player = (PlayerInventory) iinventory;
++ // CraftBukkit end
+ this.a = iinventory1;
+ this.f = entityhorse;
+ byte b0 = 3;
diff --git a/nms-patches/ContainerMerchant.patch b/nms-patches/ContainerMerchant.patch
new file mode 100644
index 00000000..d62ffe9e
--- /dev/null
+++ b/nms-patches/ContainerMerchant.patch
@@ -0,0 +1,36 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerMerchant.java 2014-11-27 08:59:46.617422217 +1100
++++ src/main/java/net/minecraft/server/ContainerMerchant.java 2014-11-27 08:42:10.136850942 +1100
+@@ -1,10 +1,25 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
++
+ public class ContainerMerchant extends Container {
+
+ private IMerchant merchant;
+ private InventoryMerchant f;
+ private final World g;
++
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity == null) {
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant((InventoryMerchant) f), this);
++ }
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+
+ public ContainerMerchant(PlayerInventory playerinventory, IMerchant imerchant, World world) {
+ this.merchant = imerchant;
+@@ -13,6 +28,7 @@
+ this.a(new Slot(this.f, 0, 36, 53));
+ this.a(new Slot(this.f, 1, 62, 53));
+ this.a((Slot) (new SlotMerchantResult(playerinventory.player, imerchant, this.f, 2, 120, 53)));
++ this.player = playerinventory; // CraftBukkit - save player
+
+ int i;
+
diff --git a/nms-patches/ContainerPlayer.patch b/nms-patches/ContainerPlayer.patch
new file mode 100644
index 00000000..8b2c347b
--- /dev/null
+++ b/nms-patches/ContainerPlayer.patch
@@ -0,0 +1,74 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerPlayer.java 2014-11-27 08:59:46.621422199 +1100
++++ src/main/java/net/minecraft/server/ContainerPlayer.java 2014-11-27 08:42:10.104851005 +1100
+@@ -1,15 +1,28 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerPlayer extends Container {
+
+ public InventoryCrafting craftInventory = new InventoryCrafting(this, 2, 2);
+ public IInventory resultInventory = new InventoryCraftResult();
+ public boolean g;
+ private final EntityHuman h;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerPlayer(PlayerInventory playerinventory, boolean flag, EntityHuman entityhuman) {
+ this.g = flag;
+ this.h = entityhuman;
++ this.resultInventory = new InventoryCraftResult(); // CraftBukkit - moved to before InventoryCrafting construction
++ this.craftInventory = new InventoryCrafting(this, 2, 2, playerinventory.player); // CraftBukkit - pass player
++ this.craftInventory.resultInventory = this.resultInventory; // CraftBukkit - let InventoryCrafting know about its result slot
++ this.player = playerinventory; // CraftBukkit - save player
+ this.a((Slot) (new SlotResult(playerinventory.player, this.craftInventory, this.resultInventory, 0, 144, 36)));
+
+ int i;
+@@ -35,11 +48,22 @@
+ this.a(new Slot(playerinventory, i, 8 + i * 18, 142));
+ }
+
+- this.a((IInventory) this.craftInventory);
++ // this.a((IInventory) this.craftInventory); // CraftBukkit - unneeded since it just sets result slot to empty
+ }
+
+ public void a(IInventory iinventory) {
+- this.resultInventory.setItem(0, CraftingManager.getInstance().craft(this.craftInventory, this.h.world));
++ // this.resultInventory.setItem(0, CraftingManager.getInstance().craft(this.craftInventory, this.h.world));
++ // CraftBukkit start (Note: the following line would cause an error if called during construction)
++ CraftingManager.getInstance().lastCraftView = getBukkitView();
++ ItemStack craftResult = CraftingManager.getInstance().craft(this.craftInventory, this.h.world);
++ this.resultInventory.setItem(0, craftResult);
++ if (super.listeners.size() < 1) {
++ return;
++ }
++
++ EntityPlayer player = (EntityPlayer) super.listeners.get(0); // TODO: Is this _always_ correct? Seems like it.
++ player.playerConnection.sendPacket(new PacketPlayOutSetSlot(player.activeContainer.windowId, 0, craftResult));
++ // CraftBukkit end
+ }
+
+ public void b(EntityHuman entityhuman) {
+@@ -119,4 +143,17 @@
+ public boolean a(ItemStack itemstack, Slot slot) {
+ return slot.inventory != this.resultInventory && super.a(itemstack, slot);
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftInventory, this.resultInventory);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ContainerWorkbench.patch b/nms-patches/ContainerWorkbench.patch
new file mode 100644
index 00000000..045167c1
--- /dev/null
+++ b/nms-patches/ContainerWorkbench.patch
@@ -0,0 +1,79 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ContainerWorkbench.java 2014-11-27 08:59:46.621422199 +1100
++++ src/main/java/net/minecraft/server/ContainerWorkbench.java 2014-11-27 08:42:10.156850903 +1100
+@@ -1,13 +1,28 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ContainerWorkbench extends Container {
+
+- public InventoryCrafting craftInventory = new InventoryCrafting(this, 3, 3);
+- public IInventory resultInventory = new InventoryCraftResult();
++ public InventoryCrafting craftInventory; // CraftBukkit - move initialization into constructor
++ public IInventory resultInventory; // CraftBukkit - move initialization into constructor
+ private World g;
+ private BlockPosition h;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private PlayerInventory player;
++ // CraftBukkit end
+
+ public ContainerWorkbench(PlayerInventory playerinventory, World world, BlockPosition blockposition) {
++ // CraftBukkit start - Switched order of IInventory construction and stored player
++ this.resultInventory = new InventoryCraftResult();
++ this.craftInventory = new InventoryCrafting(this, 3, 3, playerinventory.player); // CraftBukkit - pass player
++ this.craftInventory.resultInventory = this.resultInventory;
++ this.player = playerinventory;
++ // CraftBukkit end
+ this.g = world;
+ this.h = blockposition;
+ this.a((Slot) (new SlotResult(playerinventory.player, this.craftInventory, this.resultInventory, 0, 124, 35)));
+@@ -35,7 +50,18 @@
+ }
+
+ public void a(IInventory iinventory) {
+- this.resultInventory.setItem(0, CraftingManager.getInstance().craft(this.craftInventory, this.g));
++ // this.resultInventory.setItem(0, CraftingManager.getInstance().craft(this.craftInventory, this.g));
++ // CraftBukkit start
++ CraftingManager.getInstance().lastCraftView = getBukkitView();
++ ItemStack craftResult = CraftingManager.getInstance().craft(this.craftInventory, this.g);
++ this.resultInventory.setItem(0, craftResult);
++ if (super.listeners.size() < 1) {
++ return;
++ }
++
++ EntityPlayer player = (EntityPlayer) super.listeners.get(0); // TODO: Is this _always_ correct? Seems like it.
++ player.playerConnection.sendPacket(new PacketPlayOutSetSlot(player.activeContainer.windowId, 0, craftResult));
++ // CraftBukkit end
+ }
+
+ public void b(EntityHuman entityhuman) {
+@@ -53,6 +79,7 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.g.getType(this.h).getBlock() != Blocks.CRAFTING_TABLE ? false : entityhuman.e((double) this.h.getX() + 0.5D, (double) this.h.getY() + 0.5D, (double) this.h.getZ() + 0.5D) <= 64.0D;
+ }
+
+@@ -101,4 +128,17 @@
+ public boolean a(ItemStack itemstack, Slot slot) {
+ return slot.inventory != this.resultInventory && super.a(itemstack, slot);
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftInventory, this.resultInventory);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/ControllerLook.patch b/nms-patches/ControllerLook.patch
new file mode 100644
index 00000000..212d063e
--- /dev/null
+++ b/nms-patches/ControllerLook.patch
@@ -0,0 +1,23 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ControllerLook.java 2014-11-27 08:59:46.621422199 +1100
++++ src/main/java/net/minecraft/server/ControllerLook.java 2014-11-27 08:42:10.172850872 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.TrigMath; // CraftBukkit
++
+ public class ControllerLook {
+
+ private EntityInsentient a;
+@@ -45,8 +47,10 @@
+ double d1 = this.f - (this.a.locY + (double) this.a.getHeadHeight());
+ double d2 = this.g - this.a.locZ;
+ double d3 = (double) MathHelper.sqrt(d0 * d0 + d2 * d2);
+- float f = (float) (Math.atan2(d2, d0) * 180.0D / 3.1415927410125732D) - 90.0F;
+- float f1 = (float) (-(Math.atan2(d1, d3) * 180.0D / 3.1415927410125732D));
++ // CraftBukkit start - Math -> TrigMath
++ float f = (float) (TrigMath.atan2(d2, d0) * 180.0D / 3.1415927410125732D) - 90.0F;
++ float f1 = (float) (-(TrigMath.atan2(d1, d3) * 180.0D / 3.1415927410125732D));
++ // CraftBukkit end
+
+ this.a.pitch = this.a(this.a.pitch, f1, this.c);
+ this.a.aI = this.a(this.a.aI, f, this.b);
diff --git a/nms-patches/ControllerMove.patch b/nms-patches/ControllerMove.patch
new file mode 100644
index 00000000..9963db77
--- /dev/null
+++ b/nms-patches/ControllerMove.patch
@@ -0,0 +1,12 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ControllerMove.java 2014-11-27 08:59:46.625422182 +1100
++++ src/main/java/net/minecraft/server/ControllerMove.java 2014-11-27 08:42:10.164850887 +1100
+@@ -43,7 +43,8 @@
+ double d3 = d0 * d0 + d2 * d2 + d1 * d1;
+
+ if (d3 >= 2.500000277905201E-7D) {
+- float f = (float) (Math.atan2(d1, d0) * 180.0D / 3.1415927410125732D) - 90.0F;
++ // CraftBukkit - Math -> TrigMath
++ float f = (float) (org.bukkit.craftbukkit.TrigMath.atan2(d1, d0) * 180.0D / 3.1415927410125732D) - 90.0F;
+
+ this.a.yaw = this.a(this.a.yaw, f, 30.0F);
+ this.a.j((float) (this.e * this.a.getAttributeInstance(GenericAttributes.d).getValue()));
diff --git a/nms-patches/CraftingManager.patch b/nms-patches/CraftingManager.patch
new file mode 100644
index 00000000..8d0f4bde
--- /dev/null
+++ b/nms-patches/CraftingManager.patch
@@ -0,0 +1,57 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/CraftingManager.java 2014-11-27 08:59:46.625422182 +1100
++++ src/main/java/net/minecraft/server/CraftingManager.java 2014-11-27 08:42:10.112850989 +1100
+@@ -8,10 +8,16 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class CraftingManager {
+
+ private static final CraftingManager a = new CraftingManager();
+ public List recipes = Lists.newArrayList();
++ // CraftBukkit start
++ public IRecipe lastRecipe;
++ public org.bukkit.inventory.InventoryView lastCraftView;
++ // CraftBukkit end
+
+ public static CraftingManager getInstance() {
+ return CraftingManager.a;
+@@ -166,8 +172,15 @@
+ this.registerShapedRecipe(new ItemStack(Blocks.DAYLIGHT_DETECTOR), new Object[] { "GGG", "QQQ", "WWW", Character.valueOf('G'), Blocks.GLASS, Character.valueOf('Q'), Items.QUARTZ, Character.valueOf('W'), Blocks.WOODEN_SLAB});
+ this.registerShapedRecipe(new ItemStack(Blocks.HOPPER), new Object[] { "I I", "ICI", " I ", Character.valueOf('I'), Items.IRON_INGOT, Character.valueOf('C'), Blocks.CHEST});
+ this.registerShapedRecipe(new ItemStack(Items.ARMOR_STAND, 1), new Object[] { "///", " / ", "/_/", Character.valueOf('/'), Items.STICK, Character.valueOf('_'), new ItemStack(Blocks.STONE_SLAB, 1, EnumStoneSlabVariant.STONE.a())});
++ // Collections.sort(this.recipes, new RecipeSorter(this)); // CraftBukkit - moved below
++ sort();
++ }
++
++ // CraftBukkit start
++ public void sort() {
+ Collections.sort(this.recipes, new RecipeSorter(this));
+ }
++ // CraftBukkit end
+
+ public ShapedRecipes registerShapedRecipe(ItemStack itemstack, Object... aobject) {
+ String s = "";
+@@ -265,13 +278,18 @@
+
+ do {
+ if (!iterator.hasNext()) {
++ inventorycrafting.currentRecipe = null; // CraftBukkit - Clear recipe when no recipe is found
+ return null;
+ }
+
+ irecipe = (IRecipe) iterator.next();
+- } while (!irecipe.a(inventorycrafting, world));
+-
+- return irecipe.a(inventorycrafting);
++ } while (!irecipe.a(inventorycrafting, world));
++
++ // CraftBukkit start - INVENTORY_PRE_CRAFT event
++ inventorycrafting.currentRecipe = irecipe;
++ ItemStack result = irecipe.a(inventorycrafting);
++ return CraftEventFactory.callPreCraftEvent(inventorycrafting, result, lastCraftView, false);
++ // CraftBukkit end
+ }
+
+ public ItemStack[] b(InventoryCrafting inventorycrafting, World world) {
diff --git a/nms-patches/CrashReport.patch b/nms-patches/CrashReport.patch
new file mode 100644
index 00000000..3518bd3e
--- /dev/null
+++ b/nms-patches/CrashReport.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/CrashReport.java 2014-11-27 08:59:46.629422164 +1100
++++ src/main/java/net/minecraft/server/CrashReport.java 2014-11-27 08:42:10.172850872 +1100
+@@ -40,6 +40,7 @@
+ this.d.a("Memory", (Callable) (new CrashReportMemory(this)));
+ this.d.a("JVM Flags", (Callable) (new CrashReportJVMFlags(this)));
+ this.d.a("IntCache", (Callable) (new CrashReportIntCacheSize(this)));
++ this.d.a("CraftBukkit Information", (Callable) (new org.bukkit.craftbukkit.CraftCrashReport())); // CraftBukkit
+ }
+
+ public String a() {
diff --git a/nms-patches/DedicatedServer.patch b/nms-patches/DedicatedServer.patch
new file mode 100644
index 00000000..814b166b
--- /dev/null
+++ b/nms-patches/DedicatedServer.patch
@@ -0,0 +1,162 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DedicatedServer.java 2014-11-27 08:59:46.629422164 +1100
++++ src/main/java/net/minecraft/server/DedicatedServer.java 2014-11-27 08:42:10.172850872 +1100
+@@ -13,6 +13,14 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.io.PrintStream;
++import org.apache.logging.log4j.Level;
++
++import org.bukkit.craftbukkit.LoggerOutputStream;
++import org.bukkit.event.server.ServerCommandEvent;
++// CraftBukkit end
++
+ public class DedicatedServer extends MinecraftServer implements IMinecraftServer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+@@ -25,23 +33,48 @@
+ private EnumGamemode q;
+ private boolean r;
+
+- public DedicatedServer(File file) {
+- super(file, Proxy.NO_PROXY, DedicatedServer.a);
++ // CraftBukkit start - Signature changed
++ public DedicatedServer(joptsimple.OptionSet options) {
++ super(options, Proxy.NO_PROXY, a);
++ // super(file, Proxy.NO_PROXY, a);
++ // CraftBukkit end
+ new ThreadSleepForever(this, "Server Infinisleeper");
+ }
+
+- protected boolean init() {
++ protected boolean init() throws java.net.UnknownHostException { // CraftBukkit - throws UnknownHostException
+ ThreadCommandReader threadcommandreader = new ThreadCommandReader(this, "Server console handler");
+
+ threadcommandreader.setDaemon(true);
+ threadcommandreader.start();
++
++ // CraftBukkit start - TODO: handle command-line logging arguments
++ java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
++ global.setUseParentHandlers(false);
++ for (java.util.logging.Handler handler : global.getHandlers()) {
++ global.removeHandler(handler);
++ }
++ global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
++
++ final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
++ for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
++ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
++ logger.removeAppender(appender);
++ }
++ }
++
++ new Thread(new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader)).start();
++
++ System.setOut(new PrintStream(new LoggerOutputStream(logger, Level.INFO), true));
++ System.setErr(new PrintStream(new LoggerOutputStream(logger, Level.WARN), true));
++ // CraftBukkit end
++
+ DedicatedServer.LOGGER.info("Starting minecraft server version 1.8");
+ if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
+ DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
+ }
+
+ DedicatedServer.LOGGER.info("Loading properties");
+- this.propertyManager = new PropertyManager(new File("server.properties"));
++ this.propertyManager = new PropertyManager(this.options); // CraftBukkit - CLI argument support
+ this.o = new EULA(new File("eula.txt"));
+ if (!this.o.a()) {
+ DedicatedServer.LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
+@@ -90,13 +123,15 @@
+
+ try {
+ this.ao().a(inetaddress, this.Q());
+- } catch (IOException ioexception) {
++ } catch (Throwable ioexception) { // CraftBukkit - IOException -> Throwable
+ DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!");
+ DedicatedServer.LOGGER.warn("The exception was: {}", new Object[] { ioexception.toString()});
+ DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
+ return false;
+ }
+
++ this.a((PlayerList) (new DedicatedPlayerList(this))); // CraftBukkit
++
+ if (!this.getOnlineMode()) {
+ DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
+ DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
+@@ -111,7 +146,8 @@
+ if (!NameReferencingFileConverter.a(this.propertyManager)) {
+ return false;
+ } else {
+- this.a((PlayerList) (new DedicatedPlayerList(this)));
++ // this.a((PlayerList) (new DedicatedPlayerList(this))); // CraftBukkit - moved up
++ this.convertable = new WorldLoaderServer(server.getWorldContainer()); // CraftBukkit - moved from MinecraftServer constructor
+ long j = System.nanoTime();
+
+ if (this.T() == null) {
+@@ -166,7 +202,18 @@
+ DedicatedServer.LOGGER.info("Starting remote control listener");
+ this.m = new RemoteControlListener(this);
+ this.m.a();
++ this.remoteConsole = new org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender(); // CraftBukkit
++ }
++
++ // CraftBukkit start
++ if (this.server.getBukkitSpawnRadius() > -1) {
++ DedicatedServer.LOGGER.info("'settings.spawn-radius' in bukkit.yml has been moved to 'spawn-protection' in server.properties. I will move your config for you.");
++ this.propertyManager.properties.remove("spawn-protection");
++ this.propertyManager.getInt("spawn-protection", this.server.getBukkitSpawnRadius());
++ this.server.removeBukkitSpawnRadius();
++ this.propertyManager.savePropertiesFile();
+ }
++ // CraftBukkit end
+
+ if (this.aQ() > 0L) {
+ Thread thread = new Thread(new ThreadWatchdog(this));
+@@ -181,6 +228,12 @@
+ }
+ }
+
++ // CraftBukkit start
++ public PropertyManager getPropertyManager() {
++ return this.propertyManager;
++ }
++ // CraftBukkit end
++
+ public void setGamemode(EnumGamemode enumgamemode) {
+ super.setGamemode(enumgamemode);
+ this.q = enumgamemode;
+@@ -203,6 +256,7 @@
+ }
+
+ protected void a(CrashReport crashreport) {
++ /* CraftBukkit start - not sure why you would want to continue running commands once the server crashed
+ while (this.isRunning()) {
+ this.aM();
+
+@@ -212,7 +266,7 @@
+ ;
+ }
+ }
+-
++ // CraftBukkit end */
+ }
+
+ public CrashReport b(CrashReport crashreport) {
+@@ -257,7 +311,14 @@
+ while (!this.k.isEmpty()) {
+ ServerCommand servercommand = (ServerCommand) this.k.remove(0);
+
+- this.getCommandHandler().a(servercommand.source, servercommand.command);
++ // CraftBukkit start - ServerCommand for preprocessing
++ ServerCommandEvent event = new ServerCommandEvent(console, servercommand.command);
++ server.getPluginManager().callEvent(event);
++ servercommand = new ServerCommand(event.getCommand(), servercommand.source);
++
++ // this.getCommandHandler().a(servercommand.source, servercommand.command); // Called in dispatchServerCommand
++ server.dispatchServerCommand(console, servercommand);
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/DispenseBehaviorArmor.patch b/nms-patches/DispenseBehaviorArmor.patch
new file mode 100644
index 00000000..6c420e65
--- /dev/null
+++ b/nms-patches/DispenseBehaviorArmor.patch
@@ -0,0 +1,59 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorArmor.java 2014-11-27 08:59:46.629422164 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorArmor.java 2014-11-27 08:42:10.144850927 +1100
+@@ -3,6 +3,11 @@
+ import com.google.common.base.Predicates;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorArmor extends DispenseBehaviorItem {
+
+ DispenseBehaviorArmor() {}
+@@ -19,15 +24,42 @@
+ EntityLiving entityliving = (EntityLiving) list.get(0);
+ int l = entityliving instanceof EntityHuman ? 1 : 0;
+ int i1 = EntityInsentient.c(itemstack);
+- ItemStack itemstack1 = itemstack.cloneItemStack();
++
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.a(1);
++ World world = isourceblock.i();
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
+
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
+ itemstack1.count = 1;
+ entityliving.setEquipment(i1 - l, itemstack1);
+ if (entityliving instanceof EntityInsentient) {
+ ((EntityInsentient) entityliving).a(i1, 2.0F);
+ }
+
+- --itemstack.count;
++ // --itemstack.count; // CraftBukkit - handled above
+ return itemstack;
+ } else {
+ return super.b(isourceblock, itemstack);
diff --git a/nms-patches/DispenseBehaviorBoat.patch b/nms-patches/DispenseBehaviorBoat.patch
new file mode 100644
index 00000000..19daf555
--- /dev/null
+++ b/nms-patches/DispenseBehaviorBoat.patch
@@ -0,0 +1,54 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorBoat.java 2014-11-27 08:59:46.633422146 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorBoat.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorBoat extends DispenseBehaviorItem {
+
+ private final DispenseBehaviorItem b = new DispenseBehaviorItem();
+@@ -26,10 +31,38 @@
+ d3 = 0.0D;
+ }
+
+- EntityBoat entityboat = new EntityBoat(world, d0, d1 + d3, d2);
++ // EntityBoat entityboat = new EntityBoat(world, d0, d1 + d3, d2);
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ EntityBoat entityboat = new EntityBoat(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
++ // CraftBukkit end
+
+ world.addEntity(entityboat);
+- itemstack.a(1);
++ // itemstack.a(1); // CraftBukkit - handled during event processing
+ return itemstack;
+ }
+
diff --git a/nms-patches/DispenseBehaviorBonemeal.patch b/nms-patches/DispenseBehaviorBonemeal.patch
new file mode 100644
index 00000000..32cea5b7
--- /dev/null
+++ b/nms-patches/DispenseBehaviorBonemeal.patch
@@ -0,0 +1,44 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorBonemeal.java 2014-11-27 08:59:46.633422146 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorBonemeal.java 2014-11-27 08:42:10.120850973 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorBonemeal extends DispenseBehaviorItem {
+
+ private boolean b = true;
+@@ -10,6 +15,30 @@
+ if (EnumColor.WHITE == EnumColor.fromInvColorIndex(itemstack.getData())) {
+ World world = isourceblock.i();
+ BlockPosition blockposition = isourceblock.getBlockPosition().shift(BlockDispenser.b(isourceblock.f()));
++
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asNewCraftStack(itemstack.getItem());
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
+
+ if (ItemDye.a(itemstack, world, blockposition)) {
+ if (!world.isStatic) {
diff --git a/nms-patches/DispenseBehaviorEmptyBucket.patch b/nms-patches/DispenseBehaviorEmptyBucket.patch
new file mode 100644
index 00000000..9727c367
--- /dev/null
+++ b/nms-patches/DispenseBehaviorEmptyBucket.patch
@@ -0,0 +1,44 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorEmptyBucket.java 2014-11-27 08:59:46.633422146 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorEmptyBucket.java 2014-11-27 08:42:10.124850965 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorEmptyBucket extends DispenseBehaviorItem {
+
+ private final DispenseBehaviorItem b = new DispenseBehaviorItem();
+@@ -23,6 +28,30 @@
+
+ item = Items.LAVA_BUCKET;
+ }
++
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
+
+ world.setAir(blockposition);
+ if (--itemstack.count == 0) {
diff --git a/nms-patches/DispenseBehaviorFilledBucket.patch b/nms-patches/DispenseBehaviorFilledBucket.patch
new file mode 100644
index 00000000..e3bcc62e
--- /dev/null
+++ b/nms-patches/DispenseBehaviorFilledBucket.patch
@@ -0,0 +1,65 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorFilledBucket.java 2014-11-27 08:59:46.637422129 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorFilledBucket.java 2014-11-27 08:42:10.088851036 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorFilledBucket extends DispenseBehaviorItem {
+
+ private final DispenseBehaviorItem b = new DispenseBehaviorItem();
+@@ -9,10 +14,49 @@
+ public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
+ ItemBucket itembucket = (ItemBucket) itemstack.getItem();
+ BlockPosition blockposition = isourceblock.getBlockPosition().shift(BlockDispenser.b(isourceblock.f()));
++
++ // CraftBukkit start
++ World world = isourceblock.i();
++ int x = blockposition.getX();
++ int y = blockposition.getY();
++ int z = blockposition.getZ();
++ if (world.isEmpty(blockposition) || !world.getType(blockposition).getBlock().getMaterial().isBuildable()) {
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itembucket = (ItemBucket) CraftItemStack.asNMSCopy(event.getItem()).getItem();
++ }
++ // CraftBukkit end
+
+ if (itembucket.a(isourceblock.i(), blockposition)) {
+- itemstack.setItem(Items.BUCKET);
+- itemstack.count = 1;
++ // CraftBukkit start - Handle stacked buckets
++ Item item = Items.BUCKET;
++ if (--itemstack.count == 0) {
++ itemstack.setItem(Items.BUCKET);
++ itemstack.count = 1;
++ } else if (((TileEntityDispenser) isourceblock.getTileEntity()).addItem(new ItemStack(item)) < 0) {
++ this.b.a(isourceblock, new ItemStack(item));
++ }
++ // CraftBukkit end
+ return itemstack;
+ } else {
+ return this.b.a(isourceblock, itemstack);
diff --git a/nms-patches/DispenseBehaviorFireball.patch b/nms-patches/DispenseBehaviorFireball.patch
new file mode 100644
index 00000000..1d21873f
--- /dev/null
+++ b/nms-patches/DispenseBehaviorFireball.patch
@@ -0,0 +1,55 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorFireball.java 2014-11-27 08:59:46.637422129 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorFireball.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,11 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorFireball extends DispenseBehaviorItem {
+
+ DispenseBehaviorFireball() {}
+@@ -18,8 +23,38 @@
+ double d4 = random.nextGaussian() * 0.05D + (double) enumdirection.getAdjacentY();
+ double d5 = random.nextGaussian() * 0.05D + (double) enumdirection.getAdjacentZ();
+
+- world.addEntity(new EntitySmallFireball(world, d0, d1, d2, d3, d4, d5));
+- itemstack.a(1);
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ EntitySmallFireball entitysmallfireball = new EntitySmallFireball(world, d0, d1, d2, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
++ entitysmallfireball.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource((TileEntityDispenser) isourceblock.getTileEntity());
++
++ world.addEntity(entitysmallfireball);
++ // itemstack.a(1); // Handled during event processing
++ // CraftBukkit end
+ return itemstack;
+ }
+
diff --git a/nms-patches/DispenseBehaviorFlintAndSteel.patch b/nms-patches/DispenseBehaviorFlintAndSteel.patch
new file mode 100644
index 00000000..26ab2df0
--- /dev/null
+++ b/nms-patches/DispenseBehaviorFlintAndSteel.patch
@@ -0,0 +1,57 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorFlintAndSteel.java 2014-11-27 08:59:46.641422111 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorFlintAndSteel.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorFlintAndSteel extends DispenseBehaviorItem {
+
+ private boolean b = true;
+@@ -10,11 +15,39 @@
+ World world = isourceblock.i();
+ BlockPosition blockposition = isourceblock.getBlockPosition().shift(BlockDispenser.b(isourceblock.f()));
+
+- if (world.isEmpty(blockposition)) {
+- world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
+- if (itemstack.isDamaged(1, world.random)) {
+- itemstack.count = 0;
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
++ if (world.isEmpty(blockposition)) {
++ // CraftBukkit start - Ignition by dispensing flint and steel
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ()).isCancelled()) {
++ world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ if (itemstack.isDamaged(1, world.random)) {
++ itemstack.count = 0;
++ }
+ }
++ // CraftBukkit end
+ } else if (world.getType(blockposition).getBlock() == Blocks.TNT) {
+ Blocks.TNT.postBreak(world, blockposition, Blocks.TNT.getBlockData().set(BlockTNT.EXPLODE, Boolean.valueOf(true)));
+ world.setAir(blockposition);
diff --git a/nms-patches/DispenseBehaviorItem.patch b/nms-patches/DispenseBehaviorItem.patch
new file mode 100644
index 00000000..b902b835
--- /dev/null
+++ b/nms-patches/DispenseBehaviorItem.patch
@@ -0,0 +1,76 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorItem.java 2014-11-27 08:59:46.641422111 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorItem.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ public class DispenseBehaviorItem implements IDispenseBehavior {
+
+ public DispenseBehaviorItem() {}
+@@ -17,11 +22,18 @@
+ IPosition iposition = BlockDispenser.a(isourceblock);
+ ItemStack itemstack1 = itemstack.a(1);
+
+- a(isourceblock.i(), itemstack1, 6, enumdirection, iposition);
++ // CraftBukkit start
++ if (!a(isourceblock.i(), itemstack1, 6, enumdirection, isourceblock)) {
++ itemstack.count++;
++ }
++ // CraftBukkit end
+ return itemstack;
+ }
+
+- public static void a(World world, ItemStack itemstack, int i, EnumDirection enumdirection, IPosition iposition) {
++ // CraftBukkit start - void -> boolean return, IPosition -> ISourceBlock last argument
++ public static boolean a(World world, ItemStack itemstack, int i, EnumDirection enumdirection, ISourceBlock isourceblock) {
++ IPosition iposition = BlockDispenser.a(isourceblock);
++ // CraftBukkit end
+ double d0 = iposition.getX();
+ double d1 = iposition.getY();
+ double d2 = iposition.getZ();
+@@ -41,7 +53,41 @@
+ entityitem.motX += world.random.nextGaussian() * 0.007499999832361937D * (double) i;
+ entityitem.motY += world.random.nextGaussian() * 0.007499999832361937D * (double) i;
+ entityitem.motZ += world.random.nextGaussian() * 0.007499999832361937D * (double) i;
++
++ // CraftBukkit start
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(entityitem.motX, entityitem.motY, entityitem.motZ));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ entityitem.setItemStack(CraftItemStack.asNMSCopy(event.getItem()));
++ entityitem.motX = event.getVelocity().getX();
++ entityitem.motY = event.getVelocity().getY();
++ entityitem.motZ = event.getVelocity().getZ();
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior.getClass() != DispenseBehaviorItem.class) {
++ idispensebehavior.a(isourceblock, eventStack);
++ } else {
++ world.addEntity(entityitem);
++ }
++ return false;
++ }
++
+ world.addEntity(entityitem);
++
++ return true;
++ // CraftBukkit end
+ }
+
+ protected void a(ISourceBlock isourceblock) {
diff --git a/nms-patches/DispenseBehaviorMinecart.patch b/nms-patches/DispenseBehaviorMinecart.patch
new file mode 100644
index 00000000..454c15b7
--- /dev/null
+++ b/nms-patches/DispenseBehaviorMinecart.patch
@@ -0,0 +1,58 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorMinecart.java 2014-11-27 08:59:46.641422111 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorMinecart.java 2014-11-27 08:42:10.084851043 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorMinecart extends DispenseBehaviorItem {
+
+ private final DispenseBehaviorItem b = new DispenseBehaviorItem();
+@@ -38,14 +43,42 @@
+ }
+ }
+
+- EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.a(world, d0, d1 + d3, d2, ItemMinecart.a((ItemMinecart) itemstack.getItem()));
++ // CraftBukkit start
++ // EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.a(world, d0, d1 + d3, d2, ItemMinecart.a((ItemMinecart) itemstack.getItem()));
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block2 = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
++ EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.a(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), ItemMinecart.a((ItemMinecart) itemstack1.getItem()));
+
+ if (itemstack.hasName()) {
+ entityminecartabstract.setCustomName(itemstack.getName());
+ }
+
+ world.addEntity(entityminecartabstract);
+- itemstack.a(1);
++ // itemstack.a(1); // CraftBukkit - handled during event processing
+ return itemstack;
+ }
+
diff --git a/nms-patches/DispenseBehaviorMonsterEgg.patch b/nms-patches/DispenseBehaviorMonsterEgg.patch
new file mode 100644
index 00000000..56a05a54
--- /dev/null
+++ b/nms-patches/DispenseBehaviorMonsterEgg.patch
@@ -0,0 +1,61 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorMonsterEgg.java 2014-11-27 08:59:46.645422093 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorMonsterEgg.java 2014-11-27 08:42:10.096851020 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorMonsterEgg extends DispenseBehaviorItem {
+
+ DispenseBehaviorMonsterEgg() {}
+@@ -9,13 +14,45 @@
+ double d0 = isourceblock.getX() + (double) enumdirection.getAdjacentX();
+ double d1 = (double) ((float) isourceblock.getBlockPosition().getY() + 0.2F);
+ double d2 = isourceblock.getZ() + (double) enumdirection.getAdjacentZ();
+- Entity entity = ItemMonsterEgg.a(isourceblock.i(), itemstack.getData(), d0, d1, d2);
++ // Entity entity = ItemMonsterEgg.a(isourceblock.i(), itemstack.getData(), d0, d1, d2);
++
++ // CraftBukkit start
++ World world = isourceblock.i();
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1, d2));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
++
++ Entity entity = ItemMonsterEgg.spawnCreature(isourceblock.i(), itemstack.getData(), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG);
+
+ if (entity instanceof EntityLiving && itemstack.hasName()) {
+ ((EntityInsentient) entity).setCustomName(itemstack.getName());
+ }
+
+- itemstack.a(1);
++ // itemstack.a(1); // Handled during event processing
++ // CraftBukkit end
+ return itemstack;
+ }
+ }
diff --git a/nms-patches/DispenseBehaviorProjectile.patch b/nms-patches/DispenseBehaviorProjectile.patch
new file mode 100644
index 00000000..b4e7083b
--- /dev/null
+++ b/nms-patches/DispenseBehaviorProjectile.patch
@@ -0,0 +1,54 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorProjectile.java 2014-11-27 08:59:46.645422093 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorProjectile.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ public abstract class DispenseBehaviorProjectile extends DispenseBehaviorItem {
+
+ public DispenseBehaviorProjectile() {}
+@@ -10,9 +15,38 @@
+ EnumDirection enumdirection = BlockDispenser.b(isourceblock.f());
+ IProjectile iprojectile = this.a(world, iposition);
+
+- iprojectile.shoot((double) enumdirection.getAdjacentX(), (double) ((float) enumdirection.getAdjacentY() + 0.1F), (double) enumdirection.getAdjacentZ(), this.b(), this.a());
++ // iprojectile.shoot((double) enumdirection.getAdjacentX(), (double) ((float) enumdirection.getAdjacentY() + 0.1F), (double) enumdirection.getAdjacentZ(), this.b(), this.a());
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getAdjacentX(), (double) ((float) enumdirection.getAdjacentY() + 0.1F), (double) enumdirection.getAdjacentZ()));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ iprojectile.shoot(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.b(), this.a());
++ ((Entity) iprojectile).projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource((TileEntityDispenser) isourceblock.getTileEntity());
++ // CraftBukkit end
+ world.addEntity((Entity) iprojectile);
+- itemstack.a(1);
++ // itemstack.a(1); // CraftBukkit - Handled during event processing
+ return itemstack;
+ }
+
diff --git a/nms-patches/DispenseBehaviorTNT.patch b/nms-patches/DispenseBehaviorTNT.patch
new file mode 100644
index 00000000..1dd1d42c
--- /dev/null
+++ b/nms-patches/DispenseBehaviorTNT.patch
@@ -0,0 +1,56 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/DispenseBehaviorTNT.java 2014-11-27 08:59:46.645422093 +1100
++++ src/main/java/net/minecraft/server/DispenseBehaviorTNT.java 2014-11-27 08:42:10.140850934 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ final class DispenseBehaviorTNT extends DispenseBehaviorItem {
+
+ DispenseBehaviorTNT() {}
+@@ -7,11 +12,40 @@
+ protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
+ World world = isourceblock.i();
+ BlockPosition blockposition = isourceblock.getBlockPosition().shift(BlockDispenser.b(isourceblock.f()));
+- EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null);
++ // EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null);
++
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.a(1);
++ org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX() + 0.5, blockposition.getY() + 0.5, blockposition.getZ() + 0.5));
++ if (!BlockDispenser.eventFired) {
++ world.getServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.count++;
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.count++;
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.M.get(eventStack.getItem());
++ if (idispensebehavior != IDispenseBehavior.a && idispensebehavior != this) {
++ idispensebehavior.a(isourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (EntityLiving) null);
++ // CraftBukkit end
+
+ world.addEntity(entitytntprimed);
+ world.makeSound(entitytntprimed, "game.tnt.primed", 1.0F, 1.0F);
+- --itemstack.count;
++ // --itemstack.count; // CraftBukkit - handled above
+ return itemstack;
+ }
+ }
diff --git a/nms-patches/Enchantment.patch b/nms-patches/Enchantment.patch
new file mode 100644
index 00000000..8b09c515
--- /dev/null
+++ b/nms-patches/Enchantment.patch
@@ -0,0 +1,19 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Enchantment.java 2014-11-27 08:59:46.649422075 +1100
++++ src/main/java/net/minecraft/server/Enchantment.java 2014-11-27 08:42:10.096851020 +1100
+@@ -8,6 +8,7 @@
+
+ public abstract class Enchantment {
+
++ // CraftBukkit - update CraftEnchant.getName(i) if this changes
+ private static final Enchantment[] byId = new Enchantment[256];
+ public static final Enchantment[] b;
+ private static final Map E = Maps.newHashMap();
+@@ -55,6 +56,8 @@
+ Enchantment.byId[i] = this;
+ Enchantment.E.put(minecraftkey, this);
+ }
++
++ org.bukkit.enchantments.Enchantment.registerEnchantment(new org.bukkit.craftbukkit.enchantments.CraftEnchantment(this)); // CraftBukkit
+ }
+
+ public static Enchantment getByName(String s) {
diff --git a/nms-patches/Entity.patch b/nms-patches/Entity.patch
new file mode 100644
index 00000000..95a7509b
--- /dev/null
+++ b/nms-patches/Entity.patch
@@ -0,0 +1,578 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Entity.java 2014-11-27 08:59:46.697421864 +1100
++++ src/main/java/net/minecraft/server/Entity.java 2014-11-27 08:42:10.176850864 +1100
+@@ -6,8 +6,40 @@
+ import java.util.UUID;
+ import java.util.concurrent.Callable;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.Server;
++import org.bukkit.TravelAgent;
++import org.bukkit.block.BlockFace;
++import org.bukkit.entity.Hanging;
++import org.bukkit.entity.LivingEntity;
++import org.bukkit.entity.Painting;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.hanging.HangingBreakByEntityEvent;
++import org.bukkit.event.painting.PaintingBreakByEntityEvent;
++import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
++import org.bukkit.event.vehicle.VehicleEnterEvent;
++import org.bukkit.event.vehicle.VehicleExitEvent;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.entity.CraftEntity;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityCombustEvent;
++import org.bukkit.event.entity.EntityPortalEvent;
++import org.bukkit.plugin.PluginManager;
++// CraftBukkit end
++
+ public abstract class Entity implements ICommandListener {
+
++ // CraftBukkit start
++ private static final int CURRENT_LEVEL = 2;
++ static boolean isLevelAtLeast(NBTTagCompound tag, int level) {
++ return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level;
++ }
++ // CraftBukikt end
++
+ private static final AxisAlignedBB a = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
+ private static int entityCount;
+ private int id;
+@@ -77,6 +109,8 @@
+ private boolean invulnerable;
+ public UUID uniqueID;
+ private final CommandObjectiveExecutor as;
++ public boolean valid; // CraftBukkit
++ public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only
+
+ public int getId() {
+ return this.id;
+@@ -150,6 +184,33 @@
+ }
+
+ protected void setYawPitch(float f, float f1) {
++ // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
++ if (Float.isNaN(f)) {
++ f = 0;
++ }
++
++ if (f == Float.POSITIVE_INFINITY || f == Float.NEGATIVE_INFINITY) {
++ if (this instanceof EntityPlayer) {
++ this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid yaw");
++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Nope");
++ }
++ f = 0;
++ }
++
++ // pitch was sometimes set to NaN, so we need to set it back to 0
++ if (Float.isNaN(f1)) {
++ f1 = 0;
++ }
++
++ if (f1 == Float.POSITIVE_INFINITY || f1 == Float.NEGATIVE_INFINITY) {
++ if (this instanceof EntityPlayer) {
++ this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid pitch");
++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Nope");
++ }
++ f1 = 0;
++ }
++ // CraftBukkit end
++
+ this.yaw = f % 360.0F;
+ this.pitch = f1 % 360.0F;
+ }
+@@ -186,7 +247,7 @@
+ int i = this.L();
+
+ if (this.ak) {
+- if (minecraftserver.getAllowNether()) {
++ if (true || minecraftserver.getAllowNether()) { // CraftBukkit
+ if (this.vehicle == null && this.al++ >= i) {
+ this.al = i;
+ this.portalCooldown = this.ar();
+@@ -263,6 +324,27 @@
+ protected void burnFromLava() {
+ if (!this.fireProof) {
+ this.damageEntity(DamageSource.LAVA, 4.0F);
++
++ // CraftBukkit start - Fallen in lava TODO: this event spams!
++ if (this instanceof EntityLiving) {
++ if (fireTicks <= 0) {
++ // not on fire yet
++ // TODO: shouldn't be sending null for the block
++ org.bukkit.block.Block damager = null; // ((WorldServer) this.l).getWorld().getBlockAt(i, j, k);
++ org.bukkit.entity.Entity damagee = this.getBukkitEntity();
++ EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
++ this.world.getServer().getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ this.setOnFire(combustEvent.getDuration());
++ }
++ } else {
++ // This will be called every single tick the entity is in lava, so don't throw an event
++ this.setOnFire(15);
++ }
++ return;
++ }
++ // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls
+ this.setOnFire(15);
+ }
+ }
+@@ -300,6 +382,22 @@
+ this.a(this.getBoundingBox().c(d0, d1, d2));
+ this.recalcPosition();
+ } else {
++ // CraftBukkit start - Don't do anything if we aren't moving
++ // We need to do this regardless of whether or not we are moving thanks to portals
++ try {
++ this.checkBlockCollisions();
++ } catch (Throwable throwable) {
++ CrashReport crashreport = CrashReport.a(throwable, "Checking entity block collision");
++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being checked for collision");
++
++ this.appendEntityCrashDetails(crashreportsystemdetails);
++ throw new ReportedException(crashreport);
++ }
++ // Check if we're moving
++ if (d0 == 0 && d1 == 0 && d2 == 0 && this.vehicle == null && this.passenger == null) {
++ return;
++ }
++ // CraftBukkit end
+ this.world.methodProfiler.a("move");
+ double d3 = this.locX;
+ double d4 = this.locY;
+@@ -520,6 +618,28 @@
+ block.a(this.world, this);
+ }
+
++ // CraftBukkit start
++ if (positionChanged && getBukkitEntity() instanceof Vehicle) {
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.block.Block bl = this.world.getWorld().getBlockAt(MathHelper.floor(this.locX), MathHelper.floor(this.locY - (double) this.getHeadHeight()), MathHelper.floor(this.locZ));
++
++ // PAIL: using local vars may break between updates, name them above?
++
++ if (d6 > d0) {
++ bl = bl.getRelative(BlockFace.EAST);
++ } else if (d6 < d0) {
++ bl = bl.getRelative(BlockFace.WEST);
++ } else if (d8 > d2) {
++ bl = bl.getRelative(BlockFace.SOUTH);
++ } else if (d8 < d2) {
++ bl = bl.getRelative(BlockFace.NORTH);
++ }
++
++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl);
++ world.getServer().getPluginManager().callEvent(event);
++ }
++ // CraftBukkit end
++
+ if (this.r_() && !flag && this.vehicle == null) {
+ double d21 = this.locX - d3;
+ double d22 = this.locY - d4;
+@@ -530,7 +650,7 @@
+ }
+
+ if (block != null && this.onGround) {
+- block.a(this.world, blockposition, this);
++ // block.a(this.world, blockposition, this); // CraftBukkit removed down
+ }
+
+ this.M = (float) ((double) this.M + (double) MathHelper.sqrt(d21 * d21 + d23 * d23) * 0.6D);
+@@ -548,9 +668,12 @@
+ }
+
+ this.a(blockposition, block);
++ block.a(this.world, blockposition, this); // CraftBukkit - moved from above
+ }
+ }
+
++ // CraftBukkit start - Move to the top of the method
++ /*
+ try {
+ this.checkBlockCollisions();
+ } catch (Throwable throwable) {
+@@ -560,6 +683,8 @@
+ this.appendEntityCrashDetails(crashreportsystemdetails);
+ throw new ReportedException(crashreport);
+ }
++ */
++ // CraftBukkit end
+
+ boolean flag2 = this.U();
+
+@@ -567,7 +692,16 @@
+ this.burn(1);
+ if (!flag2) {
+ ++this.fireTicks;
+- if (this.fireTicks == 0) {
++ // CraftBukkit start - Not on fire yet
++ if (this.fireTicks <= 0) { // Only throw events on the first combust, otherwise it spams
++ EntityCombustEvent event = new EntityCombustEvent(getBukkitEntity(), 8);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ setOnFire(event.getDuration());
++ }
++ } else {
++ // CraftBukkit end
+ this.setOnFire(8);
+ }
+ }
+@@ -673,7 +807,7 @@
+ return null;
+ }
+
+- protected void burn(int i) {
++ protected void burn(float i) { // CraftBukkit - int -> float
+ if (!this.fireProof) {
+ this.damageEntity(DamageSource.FIRE, (float) i);
+ }
+@@ -823,6 +957,13 @@
+ }
+
+ public void spawnIn(World world) {
++ // CraftBukkit start
++ if (world == null) {
++ die();
++ this.world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
++ return;
++ }
++ // CraftBukkit end
+ this.world = world;
+ }
+
+@@ -1015,6 +1156,18 @@
+ try {
+ nbttagcompound.set("Pos", this.a(new double[] { this.locX, this.locY, this.locZ}));
+ nbttagcompound.set("Motion", this.a(new double[] { this.motX, this.motY, this.motZ}));
++
++ // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero
++ // TODO: make sure this is the best way to address this.
++ if (Float.isNaN(this.yaw)) {
++ this.yaw = 0;
++ }
++
++ if (Float.isNaN(this.pitch)) {
++ this.pitch = 0;
++ }
++ // CraftBukkit end
++
+ nbttagcompound.set("Rotation", this.a(new float[] { this.yaw, this.pitch}));
+ nbttagcompound.setFloat("FallDistance", this.fallDistance);
+ nbttagcompound.setShort("Fire", (short) this.fireTicks);
+@@ -1025,6 +1178,11 @@
+ nbttagcompound.setInt("PortalCooldown", this.portalCooldown);
+ nbttagcompound.setLong("UUIDMost", this.getUniqueID().getMostSignificantBits());
+ nbttagcompound.setLong("UUIDLeast", this.getUniqueID().getLeastSignificantBits());
++ // CraftBukkit start
++ nbttagcompound.setLong("WorldUUIDLeast", this.world.getDataManager().getUUID().getLeastSignificantBits());
++ nbttagcompound.setLong("WorldUUIDMost", this.world.getDataManager().getUUID().getMostSignificantBits());
++ nbttagcompound.setInt("Bukkit.updateLevel", CURRENT_LEVEL);
++ // CraftBukkit end
+ if (this.getCustomName() != null && this.getCustomName().length() > 0) {
+ nbttagcompound.setString("CustomName", this.getCustomName());
+ nbttagcompound.setBoolean("CustomNameVisible", this.getCustomNameVisible());
+@@ -1062,6 +1220,7 @@
+ this.motX = nbttaglist1.d(0);
+ this.motY = nbttaglist1.d(1);
+ this.motZ = nbttaglist1.d(2);
++ /* CraftBukkit start - Moved section down
+ if (Math.abs(this.motX) > 10.0D) {
+ this.motX = 0.0D;
+ }
+@@ -1073,6 +1232,7 @@
+ if (Math.abs(this.motZ) > 10.0D) {
+ this.motZ = 0.0D;
+ }
++ // CraftBukkit end */
+
+ this.lastX = this.P = this.locX = nbttaglist.d(0);
+ this.lastY = this.Q = this.locY = nbttaglist.d(1);
+@@ -1105,7 +1265,57 @@
+ if (this.af()) {
+ this.setPosition(this.locX, this.locY, this.locZ);
+ }
++ // CraftBukkit start
++ if (this instanceof EntityLiving) {
++ EntityLiving entity = (EntityLiving) this;
++
++ // Reset the persistence for tamed animals
++ if (entity instanceof EntityTameableAnimal && !isLevelAtLeast(nbttagcompound, 2) && !nbttagcompound.getBoolean("PersistenceRequired")) {
++ EntityInsentient entityinsentient = (EntityInsentient) entity;
++ entityinsentient.persistent = !entityinsentient.isTypeNotPersistent();
++ }
++ }
++ // CraftBukkit end
++
++ // CraftBukkit start - Exempt Vehicles from notch's sanity check
++ if (!(getBukkitEntity() instanceof Vehicle)) {
++ if (Math.abs(this.motX) > 10.0D) {
++ this.motX = 0.0D;
++ }
++
++ if (Math.abs(this.motY) > 10.0D) {
++ this.motY = 0.0D;
++ }
++
++ if (Math.abs(this.motZ) > 10.0D) {
++ this.motZ = 0.0D;
++ }
++ }
++ // CraftBukkit end
++
++ // CraftBukkit start - Reset world
++ if (this instanceof EntityPlayer) {
++ Server server = Bukkit.getServer();
++ org.bukkit.World bworld = null;
+
++ // TODO: Remove World related checks, replaced with WorldUID
++ String worldName = nbttagcompound.getString("world");
++
++ if (nbttagcompound.hasKey("WorldUUIDMost") && nbttagcompound.hasKey("WorldUUIDLeast")) {
++ UUID uid = new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast"));
++ bworld = server.getWorld(uid);
++ } else {
++ bworld = server.getWorld(worldName);
++ }
++
++ if (bworld == null) {
++ EntityPlayer entityPlayer = (EntityPlayer) this;
++ bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(entityPlayer.dimension).getWorld();
++ }
++
++ spawnIn(bworld == null? null : ((CraftWorld) bworld).getHandle());
++ }
++ // CraftBukkit end
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.a(throwable, "Loading entity NBT");
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being loaded");
+@@ -1167,6 +1377,12 @@
+
+ public EntityItem a(ItemStack itemstack, float f) {
+ if (itemstack.count != 0 && itemstack.getItem() != null) {
++ // CraftBukkit start - Capture drops for death event
++ if (this instanceof EntityLiving && ((EntityLiving) this).drops != null) {
++ ((EntityLiving) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack));
++ return null;
++ }
++ // CraftBukkit end
+ EntityItem entityitem = new EntityItem(this.world, this.locX, this.locY + (double) f, this.locZ, itemstack);
+
+ entityitem.p();
+@@ -1276,16 +1492,76 @@
+ }
+
+ public void mount(Entity entity) {
++ // CraftBukkit start
++ setPassengerOf(entity);
++ }
++
++ protected CraftEntity bukkitEntity;
++
++ public CraftEntity getBukkitEntity() {
++ if (bukkitEntity == null) {
++ bukkitEntity = CraftEntity.getEntity(world.getServer(), this);
++ }
++ return bukkitEntity;
++ }
++
++ public void setPassengerOf(Entity entity) {
++ // b(null) doesn't really fly for overloaded methods,
++ // so this method is needed
++
++ Entity originalVehicle = this.vehicle;
++ Entity originalPassenger = this.vehicle == null ? null : this.vehicle.passenger;
++ PluginManager pluginManager = Bukkit.getPluginManager();
++ getBukkitEntity(); // make sure bukkitEntity is initialised
++ // CraftBukkit end
+ this.ap = 0.0D;
+ this.aq = 0.0D;
+ if (entity == null) {
+ if (this.vehicle != null) {
++ // CraftBukkit start
++ if ((this.bukkitEntity instanceof LivingEntity) && (this.vehicle.getBukkitEntity() instanceof Vehicle)) {
++ VehicleExitEvent event = new VehicleExitEvent((Vehicle) this.vehicle.getBukkitEntity(), (LivingEntity) this.bukkitEntity);
++ pluginManager.callEvent(event);
++
++ if (event.isCancelled() || vehicle != originalVehicle) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ this.setPositionRotation(this.vehicle.locX, this.vehicle.getBoundingBox().b + (double) this.vehicle.length, this.vehicle.locZ, this.yaw, this.pitch);
+ this.vehicle.passenger = null;
+ }
+
+ this.vehicle = null;
+ } else {
++ // CraftBukkit start
++ if ((this.bukkitEntity instanceof LivingEntity) && (entity.getBukkitEntity() instanceof Vehicle) && entity.world.isChunkLoaded((int) entity.locX >> 4, (int) entity.locZ >> 4, true)) {
++ // It's possible to move from one vehicle to another. We need to check if they're already in a vehicle, and fire an exit event if they are.
++ VehicleExitEvent exitEvent = null;
++ if (this.vehicle != null && this.vehicle.getBukkitEntity() instanceof Vehicle) {
++ exitEvent = new VehicleExitEvent((Vehicle) this.vehicle.getBukkitEntity(), (LivingEntity) this.bukkitEntity);
++ pluginManager.callEvent(exitEvent);
++
++ if (exitEvent.isCancelled() || this.vehicle != originalVehicle || (this.vehicle != null && this.vehicle.passenger != originalPassenger)) {
++ return;
++ }
++ }
++
++ VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) entity.getBukkitEntity(), this.bukkitEntity);
++ pluginManager.callEvent(event);
++
++ // If a plugin messes with the vehicle or the vehicle's passenger
++ if (event.isCancelled() || this.vehicle != originalVehicle || (this.vehicle != null && this.vehicle.passenger != originalPassenger)) {
++ // If we only cancelled the enterevent then we need to put the player in a decent position.
++ if (exitEvent != null && this.vehicle == originalVehicle && this.vehicle != null && this.vehicle.passenger == originalPassenger) {
++ this.setPositionRotation(this.vehicle.locX, this.vehicle.boundingBox.b + (double) this.vehicle.length, this.vehicle.locZ, this.yaw, this.pitch);
++ this.vehicle.passenger = null;
++ this.vehicle = null;
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
+ if (this.vehicle != null) {
+ this.vehicle.passenger = null;
+ }
+@@ -1406,10 +1682,50 @@
+ }
+
+ public void onLightningStrike(EntityLightning entitylightning) {
+- this.damageEntity(DamageSource.LIGHTNING, 5.0F);
++ // CraftBukkit start
++ final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
++ final org.bukkit.entity.Entity stormBukkitEntity = entitylightning.getBukkitEntity();
++ final PluginManager pluginManager = Bukkit.getPluginManager();
++
++ if (thisBukkitEntity instanceof Hanging) {
++ HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity);
++ PaintingBreakByEntityEvent paintingEvent = null;
++
++ if (thisBukkitEntity instanceof Painting) {
++ paintingEvent = new PaintingBreakByEntityEvent((Painting) thisBukkitEntity, stormBukkitEntity);
++ }
++
++ pluginManager.callEvent(hangingEvent);
++
++ if (paintingEvent != null) {
++ paintingEvent.setCancelled(hangingEvent.isCancelled());
++ pluginManager.callEvent(paintingEvent);
++ }
++
++ if (hangingEvent.isCancelled() || (paintingEvent != null && paintingEvent.isCancelled())) {
++ return;
++ }
++ }
++
++ if (this.fireProof) {
++ return;
++ }
++ CraftEventFactory.entityDamage = entitylightning;
++ if (!this.damageEntity(DamageSource.LIGHTNING, 5.0F)) {
++ CraftEventFactory.entityDamage = null;
++ return;
++ }
++ // CraftBukkit end
+ ++this.fireTicks;
+ if (this.fireTicks == 0) {
+ this.setOnFire(8);
++ // CraftBukkit start - Call a combust event when lightning strikes
++ EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8);
++ pluginManager.callEvent(entityCombustEvent);
++ if (!entityCombustEvent.isCancelled()) {
++ this.setOnFire(entityCombustEvent.getDuration());
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -1546,32 +1862,78 @@
+ if (!this.world.isStatic && !this.dead) {
+ this.world.methodProfiler.a("changeDimension");
+ MinecraftServer minecraftserver = MinecraftServer.getServer();
+- int j = this.dimension;
+- WorldServer worldserver = minecraftserver.getWorldServer(j);
+- WorldServer worldserver1 = minecraftserver.getWorldServer(i);
++ // CraftBukkit start - Move logic into new function "teleportToLocation"
++ // int j = this.dimension;
++ // WorldServer worldserver = minecraftserver.getWorldServer(j);
++ // WorldServer worldserver1 = minecraftserver.getWorldServer(i);
++ WorldServer exitWorld = null;
++ if (this.dimension < CraftWorld.CUSTOM_DIMENSION_OFFSET) { // Plugins must specify exit from custom Bukkit worlds
++ // Only target existing worlds (compensate for allow-nether/allow-end as false)
++ for (WorldServer world : minecraftserver.worlds) {
++ if (world.dimension == i) {
++ exitWorld = world;
++ }
++ }
++ }
++
++ Location enter = this.getBukkitEntity().getLocation();
++ Location exit = exitWorld != null ? minecraftserver.getPlayerList().calculateTarget(enter, minecraftserver.getWorldServer(i)) : null;
++ boolean useTravelAgent = exitWorld != null && !(this.dimension == 1 && exitWorld.dimension == 1); // don't use agent for custom worlds or return from THE_END
++
++ TravelAgent agent = exit != null ? (TravelAgent) ((CraftWorld) exit.getWorld()).getHandle().getTravelAgent() : org.bukkit.craftbukkit.CraftTravelAgent.DEFAULT; // return arbitrary TA to compensate for implementation dependent plugins
++ EntityPortalEvent event = new EntityPortalEvent(this.getBukkitEntity(), enter, exit, agent);
++ event.useTravelAgent(useTravelAgent);
++ event.getEntity().getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !this.isAlive()) {
++ return;
++ }
++ exit = event.useTravelAgent() ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo();
++ this.teleportTo(exit, true);
++ }
++ }
++
++ public void teleportTo(Location exit, boolean portal) {
++ if (true) {
++ WorldServer worldserver = ((CraftWorld) getBukkitEntity().getLocation().getWorld()).getHandle();
++ WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle();
++ int i = worldserver1.dimension;
++ // CraftBukkit end
+
+ this.dimension = i;
++ /* CraftBukkit start - TODO: Check if we need this
+ if (j == 1 && i == 1) {
+ worldserver1 = minecraftserver.getWorldServer(0);
+ this.dimension = 0;
+ }
++ // CraftBukkit end */
+
+ this.world.kill(this);
+ this.dead = false;
+ this.world.methodProfiler.a("reposition");
+- minecraftserver.getPlayerList().changeWorld(this, j, worldserver, worldserver1);
++ // CraftBukkit start - Ensure chunks are loaded in case TravelAgent is not used which would initially cause chunks to load during find/create
++ // minecraftserver.getPlayerList().changeWorld(this, j, worldserver, worldserver1);
++ boolean before = worldserver1.chunkProviderServer.forceChunkLoad;
++ worldserver1.chunkProviderServer.forceChunkLoad = true;
++ worldserver1.getMinecraftServer().getPlayerList().repositionEntity(this, exit, portal);
++ worldserver1.chunkProviderServer.forceChunkLoad = before;
++ // CraftBukkit end
+ this.world.methodProfiler.c("reloading");
+ Entity entity = EntityTypes.createEntityByName(EntityTypes.b(this), worldserver1);
+
+ if (entity != null) {
+ entity.n(this);
++ /* CraftBukkit start - We need to do this...
+ if (j == 1 && i == 1) {
+ BlockPosition blockposition = this.world.r(worldserver1.getSpawn());
+
+ entity.setPositionRotation(blockposition, entity.yaw, entity.pitch);
+ }
+-
++ // CraftBukkit end */
+ worldserver1.addEntity(entity);
++ // CraftBukkit start - Forward the CraftEntity to the new entity
++ this.getBukkitEntity().setHandle(entity);
++ entity.bukkitEntity = this.getBukkitEntity();
++ // CraftBukkit end
+ }
+
+ this.dead = true;
diff --git a/nms-patches/EntityAgeable.patch b/nms-patches/EntityAgeable.patch
new file mode 100644
index 00000000..f1fe109b
--- /dev/null
+++ b/nms-patches/EntityAgeable.patch
@@ -0,0 +1,48 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityAgeable.java 2014-11-27 08:59:46.649422075 +1100
++++ src/main/java/net/minecraft/server/EntityAgeable.java 2014-11-27 08:42:10.144850927 +1100
+@@ -7,6 +7,7 @@
+ protected int c;
+ private float bk = -1.0F;
+ private float bl;
++ public boolean ageLocked = false; // CraftBukkit
+
+ public EntityAgeable(World world) {
+ super(world);
+@@ -27,14 +28,14 @@
+ if (entityageable != null) {
+ entityageable.setAgeRaw(-24000);
+ entityageable.setPositionRotation(this.locX, this.locY, this.locZ, 0.0F, 0.0F);
+- this.world.addEntity(entityageable);
++ this.world.addEntity(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit
+ if (itemstack.hasName()) {
+ entityageable.setCustomName(itemstack.getName());
+ }
+
+ if (!entityhuman.abilities.canInstantlyBuild) {
+ --itemstack.count;
+- if (itemstack.count <= 0) {
++ if (itemstack.count == 0) { // CraftBukkit - allow less than 0 stacks as "infinite"
+ entityhuman.inventory.setItem(entityhuman.inventory.itemInHandIndex, (ItemStack) null);
+ }
+ }
+@@ -99,17 +100,19 @@
+ super.b(nbttagcompound);
+ nbttagcompound.setInt("Age", this.getAge());
+ nbttagcompound.setInt("ForcedAge", this.b);
++ nbttagcompound.setBoolean("AgeLocked", this.ageLocked); // CraftBukkit
+ }
+
+ public void a(NBTTagCompound nbttagcompound) {
+ super.a(nbttagcompound);
+ this.setAgeRaw(nbttagcompound.getInt("Age"));
+ this.b = nbttagcompound.getInt("ForcedAge");
++ this.ageLocked = nbttagcompound.getBoolean("AgeLocked"); // CraftBukkit
+ }
+
+ public void m() {
+ super.m();
+- if (this.world.isStatic) {
++ if (this.world.isStatic || ageLocked) { // CraftBukkit
+ if (this.c > 0) {
+ if (this.c % 4 == 0) {
+ this.world.addParticle(EnumParticle.VILLAGER_HAPPY, this.locX + (double) (this.random.nextFloat() * this.width * 2.0F) - (double) this.width, this.locY + 0.5D + (double) (this.random.nextFloat() * this.length), this.locZ + (double) (this.random.nextFloat() * this.width * 2.0F) - (double) this.width, 0.0D, 0.0D, 0.0D, new int[0]);
diff --git a/nms-patches/EntityArrow.patch b/nms-patches/EntityArrow.patch
new file mode 100644
index 00000000..2912f97b
--- /dev/null
+++ b/nms-patches/EntityArrow.patch
@@ -0,0 +1,106 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityArrow.java 2014-11-27 08:59:46.653422058 +1100
++++ src/main/java/net/minecraft/server/EntityArrow.java 2014-11-27 08:42:10.100851012 +1100
+@@ -2,6 +2,12 @@
+
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.entity.LivingEntity;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.player.PlayerPickupItemEvent;
++// CraftBukkit end
++
+ public class EntityArrow extends Entity implements IProjectile {
+
+ private int d = -1;
+@@ -35,6 +41,7 @@
+ super(world);
+ this.j = 10.0D;
+ this.shooter = entityliving;
++ this.projectileSource = (LivingEntity) entityliving.getBukkitEntity(); // CraftBukkit
+ if (entityliving instanceof EntityHuman) {
+ this.fromPlayer = 1;
+ }
+@@ -62,6 +69,7 @@
+ super(world);
+ this.j = 10.0D;
+ this.shooter = entityliving;
++ this.projectileSource = (LivingEntity) entityliving.getBukkitEntity(); // CraftBukkit
+ if (entityliving instanceof EntityHuman) {
+ this.fromPlayer = 1;
+ }
+@@ -175,7 +183,7 @@
+ MovingObjectPosition movingobjectposition1 = axisalignedbb1.a(vec3d, vec3d1);
+
+ if (movingobjectposition1 != null) {
+- double d1 = vec3d.f(movingobjectposition1.pos);
++ double d1 = vec3d.distanceSquared(movingobjectposition1.pos); // CraftBukkit
+
+ if (d1 < d0 || d0 == 0.0D) {
+ entity = entity1;
+@@ -202,6 +210,8 @@
+ float f4;
+
+ if (movingobjectposition != null) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this); // CraftBukkit - Call event
++
+ if (movingobjectposition.entity != null) {
+ f2 = MathHelper.sqrt(this.motX * this.motX + this.motY * this.motY + this.motZ * this.motZ);
+ int k = MathHelper.f((double) f2 * this.damage);
+@@ -217,12 +227,20 @@
+ } else {
+ damagesource = DamageSource.arrow(this, this.shooter);
+ }
++
++ // CraftBukkit start - Moved damage call
++ if (movingobjectposition.entity.damageEntity(damagesource, (float) k)) {
++ if (this.isBurning() && !(movingobjectposition.entity instanceof EntityEnderman) && (!(movingobjectposition.entity instanceof EntityPlayer) || !(this.shooter instanceof EntityPlayer) || this.world.pvpMode)) { // CraftBukkit - abide by pvp setting if destination is a player
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
+
+- if (this.isBurning() && !(movingobjectposition.entity instanceof EntityEnderman)) {
+- movingobjectposition.entity.setOnFire(5);
++ if (!combustEvent.isCancelled()) {
++ movingobjectposition.entity.setOnFire(combustEvent.getDuration());
++ }
++ // CraftBukkit end
+ }
+
+- if (movingobjectposition.entity.damageEntity(damagesource, (float) k)) {
++ // if (movingobjectposition.entity.damageEntity(damagesource, (float) k)) { // CraftBukkit - moved up
+ if (movingobjectposition.entity instanceof EntityLiving) {
+ EntityLiving entityliving = (EntityLiving) movingobjectposition.entity;
+
+@@ -382,6 +400,21 @@
+
+ public void d(EntityHuman entityhuman) {
+ if (!this.world.isStatic && this.inGround && this.shake <= 0) {
++ // CraftBukkit start
++ ItemStack itemstack = new ItemStack(Items.ARROW);
++ if (this.fromPlayer == 1 && entityhuman.inventory.canHold(itemstack) > 0) {
++ EntityItem item = new EntityItem(this.world, this.locX, this.locY, this.locZ, itemstack);
++
++ PlayerPickupItemEvent event = new PlayerPickupItemEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.world.getServer(), this, item), 0);
++ // event.setCancelled(!entityhuman.canPickUpLoot); TODO
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++
+ boolean flag = this.fromPlayer == 1 || this.fromPlayer == 2 && entityhuman.abilities.canInstantlyBuild;
+
+ if (this.fromPlayer == 1 && !entityhuman.inventory.pickup(new ItemStack(Items.ARROW, 1))) {
+@@ -433,4 +466,10 @@
+
+ return (b0 & 1) != 0;
+ }
++
++ // CraftBukkit start
++ public boolean isInGround() {
++ return inGround;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/EntityBoat.patch b/nms-patches/EntityBoat.patch
new file mode 100644
index 00000000..6ab202bf
--- /dev/null
+++ b/nms-patches/EntityBoat.patch
@@ -0,0 +1,249 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityBoat.java 2014-11-27 08:59:46.653422058 +1100
++++ src/main/java/net/minecraft/server/EntityBoat.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,16 @@
+
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.vehicle.VehicleDamageEvent;
++import org.bukkit.event.vehicle.VehicleDestroyEvent;
++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
++import org.bukkit.event.vehicle.VehicleMoveEvent;
++// CraftBukkit end
++
+ public class EntityBoat extends Entity {
+
+ private boolean a;
+@@ -12,6 +22,27 @@
+ private double f;
+ private double g;
+ private double h;
++
++ // CraftBukkit start
++ public double maxSpeed = 0.4D;
++ public double occupiedDeceleration = 0.2D;
++ public double unoccupiedDeceleration = -1;
++ public boolean landBoats = false;
++
++ @Override
++ public void collide(Entity entity) {
++ org.bukkit.entity.Entity hitEntity = (entity == null) ? null : entity.getBukkitEntity();
++
++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), hitEntity);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ super.collide(entity);
++ }
++ // CraftBukkit end
+
+ public EntityBoat(World world) {
+ super(world);
+@@ -52,6 +83,8 @@
+ this.lastX = d0;
+ this.lastY = d1;
+ this.lastZ = d2;
++
++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleCreateEvent((Vehicle) this.getBukkitEntity())); // CraftBukkit
+ }
+
+ public double an() {
+@@ -65,6 +98,19 @@
+ if (this.passenger != null && this.passenger == damagesource.getEntity() && damagesource instanceof EntityDamageSourceIndirect) {
+ return false;
+ } else {
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.entity.Entity attacker = (damagesource.getEntity() == null) ? null : damagesource.getEntity().getBukkitEntity();
++
++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, (double) f);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return true;
++ }
++ // f = event.getDamage(); // TODO Why don't we do this?
++ // CraftBukkit end
++
+ this.b(-this.m());
+ this.a(10);
+ this.setDamage(this.j() + f * 10.0F);
+@@ -72,6 +118,15 @@
+ boolean flag = damagesource.getEntity() instanceof EntityHuman && ((EntityHuman) damagesource.getEntity()).abilities.canInstantlyBuild;
+
+ if (flag || this.j() > 40.0F) {
++ // CraftBukkit start
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker);
++ this.world.getServer().getPluginManager().callEvent(destroyEvent);
++
++ if (destroyEvent.isCancelled()) {
++ this.setDamage(40F); // Maximize damage so this doesn't get triggered again right away
++ return true;
++ }
++ // CraftBukkit end
+ if (this.passenger != null) {
+ this.passenger.mount(this);
+ }
+@@ -95,6 +150,13 @@
+ }
+
+ public void s_() {
++ // CraftBukkit start
++ double prevX = this.locX;
++ double prevY = this.locY;
++ double prevZ = this.locZ;
++ float prevYaw = this.yaw;
++ float prevPitch = this.pitch;
++ // CraftBukkit end
+ super.s_();
+ if (this.l() > 0) {
+ this.a(this.l() - 1);
+@@ -196,6 +258,19 @@
+ this.motX += -Math.sin((double) (f * 3.1415927F / 180.0F)) * this.b * (double) entityliving.aY * 0.05000000074505806D;
+ this.motZ += Math.cos((double) (f * 3.1415927F / 180.0F)) * this.b * (double) entityliving.aY * 0.05000000074505806D;
+ }
++ // CraftBukkit start - Support unoccupied deceleration
++ else if (unoccupiedDeceleration >= 0) {
++ this.motX *= unoccupiedDeceleration;
++ this.motZ *= unoccupiedDeceleration;
++ // Kill lingering speed
++ if (motX <= 0.00001) {
++ motX = 0;
++ }
++ if (motZ <= 0.00001) {
++ motZ = 0;
++ }
++ }
++ // CraftBukkit end
+
+ d4 = Math.sqrt(this.motX * this.motX + this.motZ * this.motZ);
+ if (d4 > 0.35D) {
+@@ -230,16 +305,26 @@
+ Block block = this.world.getType(blockposition).getBlock();
+
+ if (block == Blocks.SNOW_LAYER) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, l, j1, j, Blocks.AIR, 0).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ this.world.setAir(blockposition);
+ this.positionChanged = false;
+ } else if (block == Blocks.WATERLILY) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, l, j1, j, Blocks.AIR, 0).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ this.world.setAir(blockposition, true);
+ this.positionChanged = false;
+ }
+ }
+ }
+
+- if (this.onGround) {
++ if (this.onGround && !this.landBoats) { // CraftBukkit
+ this.motX *= 0.5D;
+ this.motY *= 0.5D;
+ this.motZ *= 0.5D;
+@@ -247,16 +332,23 @@
+
+ this.move(this.motX, this.motY, this.motZ);
+ if (this.positionChanged && d3 > 0.2D) {
+- if (!this.world.isStatic && !this.dead) {
+- this.die();
++ if (!this.world.isStatic && !this.dead) {
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, null);
++ this.world.getServer().getPluginManager().callEvent(destroyEvent);
++ if (!destroyEvent.isCancelled()) {
++ this.die();
+
+- for (k = 0; k < 3; ++k) {
+- this.a(Item.getItemOf(Blocks.PLANKS), 1, 0.0F);
+- }
++ for (k = 0; k < 3; ++k) {
++ this.a(Item.getItemOf(Blocks.PLANKS), 1, 0.0F);
++ }
+
+- for (k = 0; k < 2; ++k) {
+- this.a(Items.STICK, 1, 0.0F);
++ for (k = 0; k < 2; ++k) {
++ this.a(Items.STICK, 1, 0.0F);
++ }
+ }
++ // CraftBukkit end
+ }
+ } else {
+ this.motX *= 0.9900000095367432D;
+@@ -284,6 +376,23 @@
+
+ this.yaw = (float) ((double) this.yaw + d12);
+ this.setYawPitch(this.yaw, this.pitch);
++
++ // CraftBukkit start
++ org.bukkit.Server server = this.world.getServer();
++ org.bukkit.World bworld = this.world.getWorld();
++
++ Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
++ Location to = new Location(bworld, this.locX, this.locY, this.locZ, this.yaw, this.pitch);
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++
++ server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++
++ if (!from.equals(to)) {
++ VehicleMoveEvent event = new VehicleMoveEvent(vehicle, from, to);
++ server.getPluginManager().callEvent(event);
++ }
++ // CraftBukkit end
++
+ if (!this.world.isStatic) {
+ List list = this.world.getEntities(this, this.getBoundingBox().grow(0.20000000298023224D, 0.0D, 0.20000000298023224D));
+
+@@ -298,6 +407,7 @@
+ }
+
+ if (this.passenger != null && this.passenger.dead) {
++ this.passenger.vehicle = null; // CraftBukkit
+ this.passenger = null;
+ }
+
+@@ -335,17 +445,24 @@
+ if (this.fallDistance > 3.0F) {
+ this.e(this.fallDistance, 1.0F);
+ if (!this.world.isStatic && !this.dead) {
+- this.die();
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, null);
++ this.world.getServer().getPluginManager().callEvent(destroyEvent);
++ if (!destroyEvent.isCancelled()) {
++ this.die();
+
+- int i;
++ int i;
+
+- for (i = 0; i < 3; ++i) {
+- this.a(Item.getItemOf(Blocks.PLANKS), 1, 0.0F);
+- }
++ for (i = 0; i < 3; ++i) {
++ this.a(Item.getItemOf(Blocks.PLANKS), 1, 0.0F);
++ }
+
+- for (i = 0; i < 2; ++i) {
+- this.a(Items.STICK, 1, 0.0F);
++ for (i = 0; i < 2; ++i) {
++ this.a(Items.STICK, 1, 0.0F);
++ }
+ }
++ // CraftBukkit end
+ }
+
+ this.fallDistance = 0.0F;
diff --git a/nms-patches/EntityChicken.patch b/nms-patches/EntityChicken.patch
new file mode 100644
index 00000000..07602829
--- /dev/null
+++ b/nms-patches/EntityChicken.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityChicken.java 2014-11-27 08:59:46.657422040 +1100
++++ src/main/java/net/minecraft/server/EntityChicken.java 2014-11-27 08:42:10.104851005 +1100
+@@ -35,6 +35,11 @@
+ }
+
+ public void m() {
++ // CraftBukkit start
++ if (this.isChickenJockey()) {
++ this.persistent = !this.isTypeNotPersistent();
++ }
++ // CraftBukkit end
+ super.m();
+ this.bo = this.bk;
+ this.bn = this.bm;
diff --git a/nms-patches/EntityCow.patch b/nms-patches/EntityCow.patch
new file mode 100644
index 00000000..c8aafbcc
--- /dev/null
+++ b/nms-patches/EntityCow.patch
@@ -0,0 +1,42 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityCow.java 2014-11-27 08:59:46.657422040 +1100
++++ src/main/java/net/minecraft/server/EntityCow.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++// CraftBukkit end
++
+ public class EntityCow extends EntityAnimal {
+
+ public EntityCow(World world) {
+@@ -69,13 +74,23 @@
+
+ public boolean a(EntityHuman entityhuman) {
+ ItemStack itemstack = entityhuman.inventory.getItemInHand();
+-
++
+ if (itemstack != null && itemstack.getItem() == Items.BUCKET && !entityhuman.abilities.canInstantlyBuild) {
+- if (itemstack.count-- == 1) {
+- entityhuman.inventory.setItem(entityhuman.inventory.itemInHandIndex, new ItemStack(Items.MILK_BUCKET));
+- } else if (!entityhuman.inventory.pickup(new ItemStack(Items.MILK_BUCKET))) {
+- entityhuman.drop(new ItemStack(Items.MILK_BUCKET, 1, 0), false);
++ // CraftBukkit start - Got milk?
++ org.bukkit.Location loc = this.getBukkitEntity().getLocation();
++ org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(entityhuman, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), null, itemstack, Items.MILK_BUCKET);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ ItemStack result = CraftItemStack.asNMSCopy(event.getItemStack());
++ if (--itemstack.count <= 0) {
++ entityhuman.inventory.setItem(entityhuman.inventory.itemInHandIndex, result);
++ } else if (!entityhuman.inventory.pickup(result)) {
++ entityhuman.drop(result, false);
+ }
++ // CraftBukkit end
+
+ return true;
+ } else {
diff --git a/nms-patches/EntityCreature.patch b/nms-patches/EntityCreature.patch
new file mode 100644
index 00000000..f9d70d5e
--- /dev/null
+++ b/nms-patches/EntityCreature.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityCreature.java 2014-11-27 08:59:46.657422040 +1100
++++ src/main/java/net/minecraft/server/EntityCreature.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,12 @@
+
+ import java.util.UUID;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftEntity;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityUnleashEvent;
++// CraftBukkit end
++
+ public abstract class EntityCreature extends EntityInsentient {
+
+ public static final UUID bi = UUID.fromString("E199AD21-BA8A-4C53-8D13-6182D5C69D3A");
+@@ -69,6 +75,7 @@
+
+ if (this instanceof EntityTameableAnimal && ((EntityTameableAnimal) this).isSitting()) {
+ if (f > 10.0F) {
++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit
+ this.unleash(true, true);
+ }
+
+@@ -100,6 +107,7 @@
+ }
+
+ if (f > 10.0F) {
++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit
+ this.unleash(true, true);
+ }
+ } else if (!this.cb() && this.bk) {
diff --git a/nms-patches/EntityCreeper.patch b/nms-patches/EntityCreeper.patch
new file mode 100644
index 00000000..47e72258
--- /dev/null
+++ b/nms-patches/EntityCreeper.patch
@@ -0,0 +1,104 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityCreeper.java 2014-11-27 08:59:46.661422023 +1100
++++ src/main/java/net/minecraft/server/EntityCreeper.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
++
+ public class EntityCreeper extends EntityMonster {
+
+ private int b;
+@@ -7,6 +12,7 @@
+ private int maxFuseTicks = 30;
+ private int explosionRadius = 3;
+ private int bm = 0;
++ private int record = -1; // CraftBukkit
+
+ public EntityCreeper(World world) {
+ super(world);
+@@ -111,19 +117,36 @@
+ }
+
+ public void die(DamageSource damagesource) {
+- super.die(damagesource);
++ // super.die(damagesource); // CraftBukkit - Moved to end
+ if (damagesource.getEntity() instanceof EntitySkeleton) {
+ int i = Item.getId(Items.RECORD_13);
+ int j = Item.getId(Items.RECORD_WAIT);
+ int k = i + this.random.nextInt(j - i + 1);
+
+- this.a(Item.getById(k), 1);
++ // CraftBukkit start - Store record for now, drop in dropDeathLoot
++ // this.a(Item.getById(k), 1);
++ this.record = k;
++ // CraftBukkit end
+ } else if (damagesource.getEntity() instanceof EntityCreeper && damagesource.getEntity() != this && ((EntityCreeper) damagesource.getEntity()).isPowered() && ((EntityCreeper) damagesource.getEntity()).cn()) {
+ ((EntityCreeper) damagesource.getEntity()).co();
+ this.a(new ItemStack(Items.SKULL, 1, 4), 0.0F);
+ }
+-
++
++ super.die(damagesource); // CraftBukkit - Moved from above
++ }
++
++ // CraftBukkit start - Whole method
++ @Override
++ protected void dropDeathLoot(boolean flag, int i) {
++ super.dropDeathLoot(flag, i);
++
++ // Drop a music disc?
++ if (this.record != -1) {
++ this.a(Item.getById(this.record), 1);
++ this.record = -1;
++ }
+ }
++ // CraftBukkit end
+
+ public boolean r(Entity entity) {
+ return true;
+@@ -147,7 +170,21 @@
+
+ public void onLightningStrike(EntityLightning entitylightning) {
+ super.onLightningStrike(entitylightning);
+- this.datawatcher.watch(17, Byte.valueOf((byte) 1));
++ // CraftBukkit start
++ if (CraftEventFactory.callCreeperPowerEvent(this, entitylightning, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) {
++ return;
++ }
++
++ this.setPowered(true);
++ }
++
++ public void setPowered(boolean powered) {
++ if (!powered) {
++ this.datawatcher.watch(17, Byte.valueOf((byte) 0));
++ } else {
++ this.datawatcher.watch(17, Byte.valueOf((byte) 1));
++ }
++ // CraftBukkit end
+ }
+
+ protected boolean a(EntityHuman entityhuman) {
+@@ -170,9 +207,16 @@
+ if (!this.world.isStatic) {
+ boolean flag = this.world.getGameRules().getBoolean("mobGriefing");
+ float f = this.isPowered() ? 2.0F : 1.0F;
+-
+- this.world.explode(this, this.locX, this.locY, this.locZ, (float) this.explosionRadius * f, flag);
+- this.die();
++
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.explosionRadius * f, false);
++ this.world.getServer().getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire(), flag);
++ this.die();
++ } else {
++ fuseTicks = 0;
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/EntityDamageSourceIndirect.patch b/nms-patches/EntityDamageSourceIndirect.patch
new file mode 100644
index 00000000..efcfd591
--- /dev/null
+++ b/nms-patches/EntityDamageSourceIndirect.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityDamageSourceIndirect.java 2014-11-27 08:59:46.661422023 +1100
++++ src/main/java/net/minecraft/server/EntityDamageSourceIndirect.java 2014-11-27 08:42:10.164850887 +1100
+@@ -24,5 +24,11 @@
+ String s1 = s + ".item";
+
+ return itemstack != null && itemstack.hasName() && LocaleI18n.c(s1) ? new ChatMessage(s1, new Object[] { entityliving.getScoreboardDisplayName(), ichatbasecomponent, itemstack.C()}) : new ChatMessage(s, new Object[] { entityliving.getScoreboardDisplayName(), ichatbasecomponent});
++ }
++
++ // CraftBukkit start
++ public Entity getProximateDamageSource() {
++ return super.getEntity();
+ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/EntityEgg.patch b/nms-patches/EntityEgg.patch
new file mode 100644
index 00000000..9b51e766
--- /dev/null
+++ b/nms-patches/EntityEgg.patch
@@ -0,0 +1,65 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityEgg.java 2014-11-27 08:59:46.665422005 +1100
++++ src/main/java/net/minecraft/server/EntityEgg.java 2014-11-27 08:42:10.112850989 +1100
+@@ -1,5 +1,12 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.entity.Ageable;
++import org.bukkit.entity.EntityType;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerEggThrowEvent;
++// CraftBukkit end
++
+ public class EntityEgg extends EntityProjectile {
+
+ public EntityEgg(World world) {
+@@ -19,21 +26,36 @@
+ movingobjectposition.entity.damageEntity(DamageSource.projectile(this, this.getShooter()), 0.0F);
+ }
+
+- if (!this.world.isStatic && this.random.nextInt(8) == 0) {
+- byte b0 = 1;
++ // CraftBukkit start - Fire PlayerEggThrowEvent
++ boolean hatching = !this.world.isStatic && this.random.nextInt(8) == 0;
++ int numHatching = (this.random.nextInt(32) == 0) ? 4 : 1;
++ if (!hatching) {
++ numHatching = 0;
++ }
++
++ EntityType hatchingType = EntityType.CHICKEN;
+
+- if (this.random.nextInt(32) == 0) {
+- b0 = 4;
+- }
+-
+- for (int i = 0; i < b0; ++i) {
+- EntityChicken entitychicken = new EntityChicken(this.world);
+-
+- entitychicken.setAgeRaw(-24000);
+- entitychicken.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
+- this.world.addEntity(entitychicken);
+- }
++ Entity shooter = this.getShooter();
++ if (shooter instanceof EntityPlayer) {
++ Player player = (shooter == null) ? null : (Player) shooter.getBukkitEntity();
++
++ PlayerEggThrowEvent event = new PlayerEggThrowEvent(player, (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) numHatching, hatchingType);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ hatching = event.isHatching();
++ numHatching = event.getNumHatches();
++ hatchingType = event.getHatchingType();
++ }
++
++ if (hatching) {
++ for (int k = 0; k < numHatching; k++) {
++ org.bukkit.entity.Entity entity = world.getWorld().spawn(new org.bukkit.Location(world.getWorld(), this.locX, this.locY, this.locZ, this.yaw, 0.0F), hatchingType.getEntityClass(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG);
++ if (entity instanceof Ageable) {
++ ((Ageable) entity).setBaby();
++ }
++ }
+ }
++ // CraftBukkit end
+
+ double d0 = 0.08D;
+
diff --git a/nms-patches/EntityEnderCrystal.patch b/nms-patches/EntityEnderCrystal.patch
new file mode 100644
index 00000000..0bf41baa
--- /dev/null
+++ b/nms-patches/EntityEnderCrystal.patch
@@ -0,0 +1,43 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityEnderCrystal.java 2014-11-27 08:59:46.665422005 +1100
++++ src/main/java/net/minecraft/server/EntityEnderCrystal.java 2014-11-27 08:42:10.172850872 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
++
+ public class EntityEnderCrystal extends Entity {
+
+ public int a;
+@@ -32,7 +37,11 @@
+ int k = MathHelper.floor(this.locZ);
+
+ if (this.world.worldProvider instanceof WorldProviderTheEnd && this.world.getType(new BlockPosition(i, j, k)).getBlock() != Blocks.FIRE) {
+- this.world.setTypeUpdate(new BlockPosition(i, j, k), Blocks.FIRE.getBlockData());
++ // CraftBukkit start
++ if (!CraftEventFactory.callBlockIgniteEvent(this.world, i, j, k, this).isCancelled()) {
++ this.world.setTypeUpdate(new BlockPosition(i, j, k), Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -54,7 +63,15 @@
+ if (this.b <= 0) {
+ this.die();
+ if (!this.world.isStatic) {
+- this.world.explode((Entity) null, this.locX, this.locY, this.locZ, 6.0F, true);
++ // CraftBukkit start
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 6.0F, false);
++ this.world.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ this.dead = false;
++ return false;
++ }
++ this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire(), true);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/EntityEnderDragon.patch b/nms-patches/EntityEnderDragon.patch
new file mode 100644
index 00000000..a58d2f8f
--- /dev/null
+++ b/nms-patches/EntityEnderDragon.patch
@@ -0,0 +1,330 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityEnderDragon.java 2014-11-27 08:59:46.665422005 +1100
++++ src/main/java/net/minecraft/server/EntityEnderDragon.java 2014-11-27 08:42:10.116850981 +1100
+@@ -5,6 +5,17 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.util.BlockStateListPopulator;
++import org.bukkit.event.entity.EntityCreatePortalEvent;
++import org.bukkit.event.entity.EntityExplodeEvent;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.Bukkit;
++// CraftBukkit end
++
+ public class EntityEnderDragon extends EntityInsentient implements IComplex, IMonster {
+
+ public double a;
+@@ -27,6 +38,7 @@
+ private Entity by;
+ public int bw;
+ public EntityEnderCrystal bx;
++ private Explosion explosionSource = new Explosion(null, this, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, true); // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
+
+ public EntityEnderDragon(World world) {
+ super(world);
+@@ -120,21 +132,21 @@
+
+ if (this.world.isStatic) {
+ if (this.ba > 0) {
+- d3 = this.locX + (this.bb - this.locX) / (double) this.ba;
+- d0 = this.locY + (this.bc - this.locY) / (double) this.ba;
+- d1 = this.locZ + (this.bd - this.locZ) / (double) this.ba;
+- d2 = MathHelper.g(this.be - (double) this.yaw);
+- this.yaw = (float) ((double) this.yaw + d2 / (double) this.ba);
++ d0 = this.locX + (this.bb - this.locX) / (double) this.ba;
++ d1 = this.locY + (this.bc - this.locY) / (double) this.ba;
++ d2 = this.locZ + (this.bd - this.locZ) / (double) this.ba;
++ d3 = MathHelper.g(this.be - (double) this.yaw);
++ this.yaw = (float) ((double) this.yaw + d3 / (double) this.ba);
+ this.pitch = (float) ((double) this.pitch + (this.bf - (double) this.pitch) / (double) this.ba);
+ --this.ba;
+- this.setPosition(d3, d0, d1);
++ this.setPosition(d0, d1, d2);
+ this.setYawPitch(this.yaw, this.pitch);
+ }
+ } else {
+- d3 = this.a - this.locX;
+- d0 = this.b - this.locY;
+- d1 = this.c - this.locZ;
+- d2 = d3 * d3 + d0 * d0 + d1 * d1;
++ d0 = this.a - this.locX;
++ d1 = this.b - this.locY;
++ d2 = this.c - this.locZ;
++ d3 = d0 * d0 + d1 * d1 + d2 * d2;
+ double d4;
+
+ if (this.by != null) {
+@@ -155,16 +167,16 @@
+ this.c += this.random.nextGaussian() * 2.0D;
+ }
+
+- if (this.bu || d2 < 100.0D || d2 > 22500.0D || this.positionChanged || this.E) {
++ if (this.bu || d3 < 100.0D || d3 > 22500.0D || this.positionChanged || this.E) {
+ this.cd();
+ }
+
+- d0 /= (double) MathHelper.sqrt(d3 * d3 + d1 * d1);
++ d1 /= (double) MathHelper.sqrt(d0 * d0 + d2 * d2);
+ f3 = 0.6F;
+- d0 = MathHelper.a(d0, (double) (-f3), (double) f3);
+- this.motY += d0 * 0.10000000149011612D;
++ d1 = MathHelper.a(d1, (double) (-f3), (double) f3);
++ this.motY += d1 * 0.10000000149011612D;
+ this.yaw = MathHelper.g(this.yaw);
+- double d8 = 180.0D - Math.atan2(d3, d1) * 180.0D / 3.1415927410125732D;
++ double d8 = 180.0D - Math.atan2(d0, d2) * 180.0D / 3.1415927410125732D;
+ double d9 = MathHelper.g(d8 - (double) this.yaw);
+
+ if (d9 > 50.0D) {
+@@ -290,12 +302,21 @@
+ if (this.bx != null) {
+ if (this.bx.dead) {
+ if (!this.world.isStatic) {
++ CraftEventFactory.entityDamage = this.bx; // CraftBukkit
+ this.a(this.bl, DamageSource.explosion((Explosion) null), 10.0F);
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+
+ this.bx = null;
+ } else if (this.ticksLived % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
+- this.setHealth(this.getHealth() + 1.0F);
++ // CraftBukkit start
++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0D, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setHealth((float) (this.getHealth() + event.getAmount()));
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -364,7 +385,19 @@
+ }
+
+ if (this.random.nextInt(2) == 0 && !arraylist.isEmpty()) {
+- this.by = (Entity) arraylist.get(this.random.nextInt(arraylist.size()));
++ // CraftBukkit start
++ Entity target = (Entity) this.world.players.get(this.random.nextInt(this.world.players.size()));
++ EntityTargetEvent event = new EntityTargetEvent(this.getBukkitEntity(), target.getBukkitEntity(), EntityTargetEvent.TargetReason.RANDOM_TARGET);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ if (event.getTarget() == null) {
++ this.by = null;
++ } else {
++ this.by = ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
++ }
++ }
++ // CraftBukkit end
+ } else {
+ boolean flag;
+
+@@ -399,6 +432,11 @@
+ int j1 = MathHelper.floor(axisalignedbb.f);
+ boolean flag = false;
+ boolean flag1 = false;
++
++ // CraftBukkit start - Create a list to hold all the destroyed blocks
++ List<org.bukkit.block.Block> destroyedBlocks = new java.util.ArrayList<org.bukkit.block.Block>();
++ org.bukkit.craftbukkit.CraftWorld craftWorld = this.world.getWorld();
++ // CraftBukkit end
+
+ for (int k1 = i; k1 <= l; ++k1) {
+ for (int l1 = j; l1 <= i1; ++l1) {
+@@ -407,7 +445,11 @@
+
+ if (block.getMaterial() != Material.AIR) {
+ if (block != Blocks.BARRIER && block != Blocks.OBSIDIAN && block != Blocks.END_STONE && block != Blocks.BEDROCK && block != Blocks.COMMAND_BLOCK && this.world.getGameRules().getBoolean("mobGriefing")) {
+- flag1 = this.world.setAir(new BlockPosition(k1, l1, i2)) || flag1;
++ // CraftBukkit start - Add blocks to list rather than destroying them
++ // flag1 = this.world.setAir(new BlockPosition(k1, l1, i2)) || flag1;
++ flag1 = true;
++ destroyedBlocks.add(craftWorld.getBlockAt(k1, l1, i2));
++ // CraftBukkit end
+ } else {
+ flag = true;
+ }
+@@ -417,6 +459,40 @@
+ }
+
+ if (flag1) {
++ // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
++ org.bukkit.entity.Entity bukkitEntity = this.getBukkitEntity();
++ EntityExplodeEvent event = new EntityExplodeEvent(bukkitEntity, bukkitEntity.getLocation(), destroyedBlocks, 0F);
++ Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down.
++ // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled.
++ return flag;
++ } else if (event.getYield() == 0F) {
++ // Yield zero ==> no drops
++ for (org.bukkit.block.Block block : event.blockList()) {
++ this.world.setAir(new BlockPosition(block.getX(), block.getY(), block.getZ()));
++ }
++ } else {
++ for (org.bukkit.block.Block block : event.blockList()) {
++ org.bukkit.Material blockId = block.getType();
++ if (blockId == org.bukkit.Material.AIR) {
++ continue;
++ }
++
++ int blockX = block.getX();
++ int blockY = block.getY();
++ int blockZ = block.getZ();
++
++ Block nmsBlock = org.bukkit.craftbukkit.util.CraftMagicNumbers.getBlock(blockId);
++ if (nmsBlock.a(explosionSource)) {
++ nmsBlock.dropNaturally(this.world, new BlockPosition(blockX, blockY, blockZ), nmsBlock.fromLegacyData(block.getData()), event.getYield(), 0);
++ }
++ nmsBlock.wasExploded(world, new BlockPosition(blockX, blockY, blockZ), explosionSource);
++
++ this.world.setAir(new BlockPosition(blockX, blockY, blockZ));
++ }
++ }
++ // CraftBukkit end
+ double d0 = axisalignedbb.a + (axisalignedbb.d - axisalignedbb.a) * (double) this.random.nextFloat();
+ double d1 = axisalignedbb.b + (axisalignedbb.e - axisalignedbb.b) * (double) this.random.nextFloat();
+ double d2 = axisalignedbb.c + (axisalignedbb.f - axisalignedbb.c) * (double) this.random.nextFloat();
+@@ -464,6 +540,7 @@
+ }
+
+ protected void aY() {
++ if (this.dead) return; // CraftBukkit - can't kill what's already dead
+ ++this.bw;
+ if (this.bw >= 180 && this.bw <= 200) {
+ float f = (this.random.nextFloat() - 0.5F) * 8.0F;
+@@ -478,7 +555,7 @@
+
+ if (!this.world.isStatic) {
+ if (this.bw > 150 && this.bw % 5 == 0 && this.world.getGameRules().getBoolean("doMobLoot")) {
+- i = 1000;
++ i = this.expToDrop / 12; // CraftBukkit - drop experience as dragon falls from sky. use experience drop from death event. This is now set in getExpReward()
+
+ while (i > 0) {
+ j = EntityExperienceOrb.getOrbValue(i);
+@@ -488,14 +565,30 @@
+ }
+
+ if (this.bw == 1) {
+- this.world.a(1018, new BlockPosition(this), 0);
++ // CraftBukkit start - Use relative location for far away sounds
++ // this.world.a(1018, new BlockPosition(this), 0);
++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16;
++ for (EntityPlayer player : (List<EntityPlayer>) this.world.players) {
++ double deltaX = this.locX - player.locX;
++ double deltaZ = this.locZ - player.locZ;
++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++ if (distanceSquared > viewDistance * viewDistance) {
++ double deltaLength = Math.sqrt(distanceSquared);
++ double relativeX = player.locX + (deltaX / deltaLength) * viewDistance;
++ double relativeZ = player.locZ + (deltaZ / deltaLength) * viewDistance;
++ player.playerConnection.sendPacket(new PacketPlayOutWorldEvent(1018, new BlockPosition((int) relativeX, (int) this.locY, (int) relativeZ), 0, true));
++ } else {
++ player.playerConnection.sendPacket(new PacketPlayOutWorldEvent(1018, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0, true));
++ }
++ }
++ // CraftBukkit end
+ }
+ }
+
+ this.move(0.0D, 0.10000000149011612D, 0.0D);
+ this.aG = this.yaw += 20.0F;
+ if (this.bw == 200 && !this.world.isStatic) {
+- i = 2000;
++ i = this.expToDrop - (10 * this.expToDrop / 12); // CraftBukkit - drop the remaining experience
+
+ while (i > 0) {
+ j = EntityExperienceOrb.getOrbValue(i);
+@@ -513,6 +606,9 @@
+ boolean flag = true;
+ double d0 = 12.25D;
+ double d1 = 6.25D;
++
++ // CraftBukkit start - Replace any "this.world" in the following with just "world"!
++ BlockStateListPopulator world = new BlockStateListPopulator(this.world.getWorld());
+
+ for (int i = -1; i <= 32; ++i) {
+ for (int j = -4; j <= 4; ++j) {
+@@ -524,31 +620,51 @@
+
+ if (i < 0) {
+ if (d2 <= 6.25D) {
+- this.world.setTypeUpdate(blockposition1, Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition1, Blocks.BEDROCK.getBlockData());
+ }
+ } else if (i > 0) {
+- this.world.setTypeUpdate(blockposition1, Blocks.AIR.getBlockData());
++ world.setTypeUpdate(blockposition1, Blocks.AIR.getBlockData());
+ } else if (d2 > 6.25D) {
+- this.world.setTypeUpdate(blockposition1, Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition1, Blocks.BEDROCK.getBlockData());
+ } else {
+- this.world.setTypeUpdate(blockposition1, Blocks.END_PORTAL.getBlockData());
++ world.setTypeUpdate(blockposition1, Blocks.END_PORTAL.getBlockData());
+ }
+ }
+ }
+ }
+ }
+
+- this.world.setTypeUpdate(blockposition, Blocks.BEDROCK.getBlockData());
+- this.world.setTypeUpdate(blockposition.up(), Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition, Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition.up(), Blocks.BEDROCK.getBlockData());
+ BlockPosition blockposition2 = blockposition.up(2);
+
+- this.world.setTypeUpdate(blockposition2, Blocks.BEDROCK.getBlockData());
+- this.world.setTypeUpdate(blockposition2.west(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.EAST));
+- this.world.setTypeUpdate(blockposition2.east(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.WEST));
+- this.world.setTypeUpdate(blockposition2.north(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.SOUTH));
+- this.world.setTypeUpdate(blockposition2.south(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.NORTH));
+- this.world.setTypeUpdate(blockposition.up(3), Blocks.BEDROCK.getBlockData());
+- this.world.setTypeUpdate(blockposition.up(4), Blocks.DRAGON_EGG.getBlockData());
++ world.setTypeUpdate(blockposition2, Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition2.west(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.EAST));
++ world.setTypeUpdate(blockposition2.east(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.WEST));
++ world.setTypeUpdate(blockposition2.north(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.SOUTH));
++ world.setTypeUpdate(blockposition2.south(), Blocks.TORCH.getBlockData().set(BlockTorch.FACING, EnumDirection.NORTH));
++ world.setTypeUpdate(blockposition.up(3), Blocks.BEDROCK.getBlockData());
++ world.setTypeUpdate(blockposition.up(4), Blocks.DRAGON_EGG.getBlockData());
++
++ EntityCreatePortalEvent event = new EntityCreatePortalEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), java.util.Collections.unmodifiableList(world.getList()), org.bukkit.PortalType.ENDER);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ for (BlockState state : event.getBlocks()) {
++ state.update(true);
++ }
++ } else {
++ for (BlockState state : event.getBlocks()) {
++ PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(this.world, new BlockPosition(state.getX(), state.getY(), state.getZ()));
++ for (Iterator it = this.world.players.iterator(); it.hasNext();) {
++ EntityHuman entity = (EntityHuman) it.next();
++ if (entity instanceof EntityPlayer) {
++ ((EntityPlayer) entity).playerConnection.sendPacket(packet);
++ }
++ }
++ }
++ }
++ // CraftBukkit end
+ }
+
+ protected void D() {}
+@@ -576,4 +692,12 @@
+ protected float bA() {
+ return 5.0F;
+ }
++
++ // CraftBukkit start
++ public int getExpReward() {
++ // This value is equal to the amount of experience dropped while falling from the sky (10 * 1000)
++ // plus what is dropped when the dragon hits the ground (2000)
++ return 12000;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/EntityEnderPearl.patch b/nms-patches/EntityEnderPearl.patch
new file mode 100644
index 00000000..de13eefd
--- /dev/null
+++ b/nms-patches/EntityEnderPearl.patch
@@ -0,0 +1,50 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityEnderPearl.java 2014-11-27 08:59:46.669421987 +1100
++++ src/main/java/net/minecraft/server/EntityEnderPearl.java 2014-11-27 08:42:10.124850965 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.player.PlayerTeleportEvent;
++// CraftBukkit end
++
+ public class EntityEnderPearl extends EntityProjectile {
+
+ public EntityEnderPearl(World world, EntityLiving entityliving) {
+@@ -29,14 +35,28 @@
+ entityendermite.setPositionRotation(entityliving.locX, entityliving.locY, entityliving.locZ, entityliving.yaw, entityliving.pitch);
+ this.world.addEntity(entityendermite);
+ }
+-
+- if (entityliving.av()) {
+- entityliving.mount((Entity) null);
++
++ // CraftBukkit start - Fire PlayerTeleportEvent
++ org.bukkit.craftbukkit.entity.CraftPlayer player = entityplayer.getBukkitEntity();
++ org.bukkit.Location location = getBukkitEntity().getLocation();
++ location.setPitch(player.getLocation().getPitch());
++ location.setYaw(player.getLocation().getYaw());
++
++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.ENDER_PEARL);
++ Bukkit.getPluginManager().callEvent(teleEvent);
++
++ if (!teleEvent.isCancelled() && !entityplayer.playerConnection.isDisconnected()) {
++ if (entityliving.av()) {
++ entityliving.mount((Entity) null);
++ }
++
++ entityplayer.playerConnection.teleport(teleEvent.getTo());
++ entityliving.fallDistance = 0.0F;
++ CraftEventFactory.entityDamage = this;
++ entityliving.damageEntity(DamageSource.FALL, 5.0F);
++ CraftEventFactory.entityDamage = null;
+ }
+-
+- entityliving.enderTeleportTo(this.locX, this.locY, this.locZ);
+- entityliving.fallDistance = 0.0F;
+- entityliving.damageEntity(DamageSource.FALL, 5.0F);
++ // CraftBukkit end
+ }
+ }
+
diff --git a/nms-patches/EntityEnderman.patch b/nms-patches/EntityEnderman.patch
new file mode 100644
index 00000000..1fb9b606
--- /dev/null
+++ b/nms-patches/EntityEnderman.patch
@@ -0,0 +1,34 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityEnderman.java 2014-11-27 08:59:46.669421987 +1100
++++ src/main/java/net/minecraft/server/EntityEnderman.java 2014-11-27 08:42:10.140850934 +1100
+@@ -4,6 +4,12 @@
+ import java.util.Set;
+ import java.util.UUID;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTeleportEvent;
++// CraftBukkit end
++
+ public class EntityEnderman extends EntityMonster {
+
+ private static final UUID b = UUID.fromString("020E0DFB-87AE-4653-9556-831010E291A0");
+@@ -165,7 +171,17 @@
+ }
+
+ if (flag1) {
+- super.enderTeleportTo(this.locX, this.locY, this.locZ);
++ // CraftBukkit start - Teleport event
++ // super.enderTeleportTo(this.locX, this.locY, this.locZ);
++ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.world.getWorld(), d3, d4, d5), new Location(this.world.getWorld(), this.locX, this.locY, this.locZ));
++ this.world.getServer().getPluginManager().callEvent(teleport);
++ if (teleport.isCancelled()) {
++ return false;
++ }
++
++ Location to = teleport.getTo();
++ this.enderTeleportTo(to.getX(), to.getY(), to.getZ());
++ // CraftBukkit end
+ if (this.world.getCubes(this, this.getBoundingBox()).isEmpty() && !this.world.containsLiquid(this.getBoundingBox())) {
+ flag = true;
+ }
diff --git a/nms-patches/EntityExperienceOrb.patch b/nms-patches/EntityExperienceOrb.patch
new file mode 100644
index 00000000..0f389895
--- /dev/null
+++ b/nms-patches/EntityExperienceOrb.patch
@@ -0,0 +1,82 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityExperienceOrb.java 2014-11-27 08:59:46.673421970 +1100
++++ src/main/java/net/minecraft/server/EntityExperienceOrb.java 2014-11-27 08:42:10.100851012 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
++
+ public class EntityExperienceOrb extends Entity {
+
+ public int a;
+@@ -34,6 +40,7 @@
+
+ public void s_() {
+ super.s_();
++ EntityHuman prevTarget = this.targetPlayer;// CraftBukkit - store old target
+ if (this.c > 0) {
+ --this.c;
+ }
+@@ -65,6 +72,16 @@
+ }
+
+ if (this.targetPlayer != null) {
++ // CraftBukkit start
++ boolean cancelled = false;
++ if (this.targetPlayer != prevTarget) {
++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this, targetPlayer, EntityTargetEvent.TargetReason.CLOSEST_PLAYER);
++ EntityLiving target = event.getTarget() == null ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++ targetPlayer = target instanceof EntityHuman ? (EntityHuman) target : null;
++ cancelled = event.isCancelled();
++ }
++
++ if (!cancelled && targetPlayer != null) {
+ double d1 = (this.targetPlayer.locX - this.locX) / d0;
+ double d2 = (this.targetPlayer.locY + (double) this.targetPlayer.getHeadHeight() - this.locY) / d0;
+ double d3 = (this.targetPlayer.locZ - this.locZ) / d0;
+@@ -77,6 +94,8 @@
+ this.motY += d2 / d4 * d5 * 0.1D;
+ this.motZ += d3 / d4 * d5 * 0.1D;
+ }
++ }
++ // CraftBukkit end
+ }
+
+ this.move(this.motX, this.motY, this.motZ);
+@@ -141,7 +160,7 @@
+ entityhuman.bn = 2;
+ this.world.makeSound(entityhuman, "random.orb", 0.1F, 0.5F * ((this.random.nextFloat() - this.random.nextFloat()) * 0.7F + 1.8F));
+ entityhuman.receive(this, 1);
+- entityhuman.giveExp(this.value);
++ entityhuman.giveExp(CraftEventFactory.callPlayerExpChangeEvent(entityhuman, this.value).getAmount()); // CraftBukkit - this.value -> event.getAmount()
+ this.die();
+ }
+
+@@ -153,6 +172,24 @@
+ }
+
+ public static int getOrbValue(int i) {
++ // CraftBukkit start
++ if (i > 162670129) return i - 100000;
++ if (i > 81335063) return 81335063;
++ if (i > 40667527) return 40667527;
++ if (i > 20333759) return 20333759;
++ if (i > 10166857) return 10166857;
++ if (i > 5083423) return 5083423;
++ if (i > 2541701) return 2541701;
++ if (i > 1270849) return 1270849;
++ if (i > 635413) return 635413;
++ if (i > 317701) return 317701;
++ if (i > 158849) return 158849;
++ if (i > 79423) return 79423;
++ if (i > 39709) return 39709;
++ if (i > 19853) return 19853;
++ if (i > 9923) return 9923;
++ if (i > 4957) return 4957;
++ // CraftBukkit end
+ return i >= 2477 ? 2477 : (i >= 1237 ? 1237 : (i >= 617 ? 617 : (i >= 307 ? 307 : (i >= 149 ? 149 : (i >= 73 ? 73 : (i >= 37 ? 37 : (i >= 17 ? 17 : (i >= 7 ? 7 : (i >= 3 ? 3 : 1)))))))));
+ }
+
diff --git a/nms-patches/EntityFallingBlock.patch b/nms-patches/EntityFallingBlock.patch
new file mode 100644
index 00000000..2b53c264
--- /dev/null
+++ b/nms-patches/EntityFallingBlock.patch
@@ -0,0 +1,44 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityFallingBlock.java 2014-11-27 08:59:46.673421970 +1100
++++ src/main/java/net/minecraft/server/EntityFallingBlock.java 2014-11-27 08:42:10.132850949 +1100
+@@ -4,6 +4,8 @@
+ import java.util.ArrayList;
+ import java.util.Iterator;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class EntityFallingBlock extends Entity {
+
+ public IBlockData block;
+@@ -56,7 +58,7 @@
+
+ if (this.ticksLived++ == 0) {
+ blockposition = new BlockPosition(this);
+- if (this.world.getType(blockposition).getBlock() == block) {
++ if (this.world.getType(blockposition).getBlock() == block && !CraftEventFactory.callEntityChangeBlockEvent(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), Blocks.AIR, 0).isCancelled()) {
+ this.world.setAir(blockposition);
+ } else if (!this.world.isStatic) {
+ this.die();
+@@ -77,7 +79,12 @@
+ this.motY *= -0.5D;
+ if (this.world.getType(blockposition).getBlock() != Blocks.PISTON_EXTENSION) {
+ this.die();
+- if (!this.e && this.world.a(block, blockposition, true, EnumDirection.UP, (Entity) null, (ItemStack) null) && !BlockFalling.canFall(this.world, blockposition.down()) && this.world.setTypeAndData(blockposition, this.block, 3)) {
++ if (!this.e && this.world.a(block, blockposition, true, EnumDirection.UP, (Entity) null, (ItemStack) null) && !BlockFalling.canFall(this.world, blockposition.down()) /* mimic the false conditions of setTypeIdAndData */ && blockposition.getX() >= -30000000 && blockposition.getZ() >= -30000000 && blockposition.getX() < 30000000 && blockposition.getZ() < 30000000 && blockposition.getY() >= 0 && blockposition.getY() < 256 && this.world.getType(blockposition) != this.block) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.block.getBlock(), this.block.getBlock().toLegacyData(this.block)).isCancelled()) {
++ return;
++ }
++ this.world.setTypeAndData(blockposition, this.block, 3);
++ // CraftBukkit end
+ if (block instanceof BlockFalling) {
+ ((BlockFalling) block).a_(this.world, blockposition);
+ }
+@@ -135,7 +142,9 @@
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
+
++ CraftEventFactory.entityDamage = this; // CraftBukkit
+ entity.damageEntity(damagesource, (float) Math.min(MathHelper.d((float) i * this.fallHurtAmount), this.fallHurtMax));
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+
+ if (flag && (double) this.random.nextFloat() < 0.05000000074505806D + (double) i * 0.05D) {
diff --git a/nms-patches/EntityFireball.patch b/nms-patches/EntityFireball.patch
new file mode 100644
index 00000000..67022e16
--- /dev/null
+++ b/nms-patches/EntityFireball.patch
@@ -0,0 +1,111 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityFireball.java 2014-11-27 08:59:46.677421952 +1100
++++ src/main/java/net/minecraft/server/EntityFireball.java 2014-11-27 08:42:10.144850927 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.List;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public abstract class EntityFireball extends Entity {
+
+ private int e = -1;
+@@ -15,6 +17,8 @@
+ public double dirX;
+ public double dirY;
+ public double dirZ;
++ public float bukkitYield = 1; // CraftBukkit
++ public boolean isIncendiary = true; // CraftBukkit
+
+ public EntityFireball(World world) {
+ super(world);
+@@ -38,10 +42,17 @@
+ public EntityFireball(World world, EntityLiving entityliving, double d0, double d1, double d2) {
+ super(world);
+ this.shooter = entityliving;
++ this.projectileSource = (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity(); // CraftBukkit
+ this.a(1.0F, 1.0F);
+ this.setPositionRotation(entityliving.locX, entityliving.locY, entityliving.locZ, entityliving.yaw, entityliving.pitch);
+ this.setPosition(this.locX, this.locY, this.locZ);
+ this.motX = this.motY = this.motZ = 0.0D;
++ // CraftBukkit start - Added setDirection method
++ this.setDirection(d0, d1, d2);
++ }
++
++ public void setDirection(double d0, double d1, double d2) {
++ // CraftBukkit end
+ d0 += this.random.nextGaussian() * 0.4D;
+ d1 += this.random.nextGaussian() * 0.4D;
+ d2 += this.random.nextGaussian() * 0.4D;
+@@ -101,7 +112,7 @@
+ MovingObjectPosition movingobjectposition1 = axisalignedbb.a(vec3d, vec3d1);
+
+ if (movingobjectposition1 != null) {
+- double d1 = vec3d.f(movingobjectposition1.pos);
++ double d1 = vec3d.distanceSquared(movingobjectposition1.pos); // CraftBukkit - distance efficiency
+
+ if (d1 < d0 || d0 == 0.0D) {
+ entity = entity1;
+@@ -117,6 +128,12 @@
+
+ if (movingobjectposition != null) {
+ this.a(movingobjectposition);
++
++ // CraftBukkit start - Fire ProjectileHitEvent
++ if (this.dead) {
++ CraftEventFactory.callProjectileHitEvent(this);
++ }
++ // CraftBukkit end
+ }
+
+ this.locX += this.motX;
+@@ -181,7 +198,8 @@
+
+ nbttagcompound.setString("inTile", minecraftkey == null ? "" : minecraftkey.toString());
+ nbttagcompound.setByte("inGround", (byte) (this.i ? 1 : 0));
+- nbttagcompound.set("direction", this.a(new double[] { this.motX, this.motY, this.motZ}));
++ // CraftBukkit - Fix direction being mismapped to invalid variables
++ nbttagcompound.set("power", this.a(new double[] { this.dirX, this.dirY, this.dirZ}));
+ }
+
+ public void a(NBTTagCompound nbttagcompound) {
+@@ -195,12 +213,14 @@
+ }
+
+ this.i = nbttagcompound.getByte("inGround") == 1;
+- if (nbttagcompound.hasKeyOfType("direction", 9)) {
+- NBTTagList nbttaglist = nbttagcompound.getList("direction", 6);
+-
+- this.motX = nbttaglist.d(0);
+- this.motY = nbttaglist.d(1);
+- this.motZ = nbttaglist.d(2);
++ // CraftBukkit start - direction -> power
++ if (nbttagcompound.hasKeyOfType("power", 9)) {
++ NBTTagList nbttaglist = nbttagcompound.getList("power", 6);
++
++ this.dirX = nbttaglist.d(0);
++ this.dirY = nbttaglist.d(1);
++ this.dirZ = nbttaglist.d(2);
++ // CraftBukkit end
+ } else {
+ this.die();
+ }
+@@ -221,6 +241,11 @@
+ } else {
+ this.ac();
+ if (damagesource.getEntity() != null) {
++ // CraftBukkit start
++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, damagesource, f)) {
++ return false;
++ }
++ // CraftBukkit end
+ Vec3D vec3d = damagesource.getEntity().ap();
+
+ if (vec3d != null) {
+@@ -234,6 +259,7 @@
+
+ if (damagesource.getEntity() instanceof EntityLiving) {
+ this.shooter = (EntityLiving) damagesource.getEntity();
++ this.projectileSource = (org.bukkit.projectiles.ProjectileSource) this.shooter.getBukkitEntity();
+ }
+
+ return true;
diff --git a/nms-patches/EntityFishingHook.patch b/nms-patches/EntityFishingHook.patch
new file mode 100644
index 00000000..b2a015b8
--- /dev/null
+++ b/nms-patches/EntityFishingHook.patch
@@ -0,0 +1,159 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityFishingHook.java 2014-11-27 08:59:46.677421952 +1100
++++ src/main/java/net/minecraft/server/EntityFishingHook.java 2014-11-27 08:42:10.104851005 +1100
+@@ -3,6 +3,12 @@
+ import java.util.Arrays;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.entity.Fish;
++import org.bukkit.event.player.PlayerFishEvent;
++// CraftBukkit end
++
+ public class EntityFishingHook extends Entity {
+
+ private static final List d = Arrays.asList(new PossibleFishingResult[] { (new PossibleFishingResult(new ItemStack(Items.LEATHER_BOOTS), 10)).a(0.9F), new PossibleFishingResult(new ItemStack(Items.LEATHER), 10), new PossibleFishingResult(new ItemStack(Items.BONE), 10), new PossibleFishingResult(new ItemStack(Items.POTION), 10), new PossibleFishingResult(new ItemStack(Items.STRING), 5), (new PossibleFishingResult(new ItemStack(Items.FISHING_ROD), 2)).a(0.9F), new PossibleFishingResult(new ItemStack(Items.BOWL), 10), new PossibleFishingResult(new ItemStack(Items.STICK), 5), new PossibleFishingResult(new ItemStack(Items.DYE, 10, EnumColor.BLACK.getInvColorIndex()), 1), new PossibleFishingResult(new ItemStack(Blocks.TRIPWIRE_HOOK), 10), new PossibleFishingResult(new ItemStack(Items.ROTTEN_FLESH), 10)});
+@@ -168,7 +174,7 @@
+ MovingObjectPosition movingobjectposition1 = axisalignedbb.a(vec3d, vec3d1);
+
+ if (movingobjectposition1 != null) {
+- d6 = vec3d.f(movingobjectposition1.pos);
++ d6 = vec3d.distanceSquared(movingobjectposition1.pos); // CraftBukkit - distance efficiency
+ if (d6 < d5 || d5 == 0.0D) {
+ entity = entity1;
+ d5 = d6;
+@@ -182,6 +188,7 @@
+ }
+
+ if (movingobjectposition != null) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this); // Craftbukkit - Call event
+ if (movingobjectposition.entity != null) {
+ if (movingobjectposition.entity.damageEntity(DamageSource.projectile(this, this.owner), 0.0F)) {
+ this.hooked = movingobjectposition.entity;
+@@ -261,8 +268,8 @@
+ } else {
+ float f3;
+ float f4;
+- double d11;
+ float f5;
++ double d11;
+ double d12;
+
+ if (this.av > 0) {
+@@ -277,20 +284,20 @@
+ } else {
+ this.aw = (float) ((double) this.aw + this.random.nextGaussian() * 4.0D);
+ f3 = this.aw * 0.017453292F;
+- f5 = MathHelper.sin(f3);
+- f4 = MathHelper.cos(f3);
+- d8 = this.locX + (double) (f5 * (float) this.av * 0.1F);
+- d12 = (double) ((float) MathHelper.floor(this.getBoundingBox().b) + 1.0F);
+- d11 = this.locZ + (double) (f4 * (float) this.av * 0.1F);
++ f4 = MathHelper.sin(f3);
++ f5 = MathHelper.cos(f3);
++ d8 = this.locX + (double) (f4 * (float) this.av * 0.1F);
++ d11 = (double) ((float) MathHelper.floor(this.getBoundingBox().b) + 1.0F);
++ d12 = this.locZ + (double) (f5 * (float) this.av * 0.1F);
+ if (this.random.nextFloat() < 0.15F) {
+- worldserver.a(EnumParticle.WATER_BUBBLE, d8, d12 - 0.10000000149011612D, d11, 1, (double) f5, 0.1D, (double) f4, 0.0D, new int[0]);
++ worldserver.a(EnumParticle.WATER_BUBBLE, d8, d11 - 0.10000000149011612D, d12, 1, (double) f4, 0.1D, (double) f5, 0.0D, new int[0]);
+ }
+
+- float f6 = f5 * 0.04F;
+- float f7 = f4 * 0.04F;
++ float f6 = f4 * 0.04F;
++ float f7 = f5 * 0.04F;
+
+- worldserver.a(EnumParticle.WATER_WAKE, d8, d12, d11, 0, (double) f7, 0.01D, (double) (-f6), 1.0D, new int[0]);
+- worldserver.a(EnumParticle.WATER_WAKE, d8, d12, d11, 0, (double) (-f7), 0.01D, (double) f6, 1.0D, new int[0]);
++ worldserver.a(EnumParticle.WATER_WAKE, d8, d11, d12, 0, (double) f7, 0.01D, (double) (-f6), 1.0D, new int[0]);
++ worldserver.a(EnumParticle.WATER_WAKE, d8, d11, d12, 0, (double) (-f7), 0.01D, (double) f6, 1.0D, new int[0]);
+ }
+ } else if (this.au > 0) {
+ this.au -= k;
+@@ -304,12 +311,12 @@
+ }
+
+ if (this.random.nextFloat() < f3) {
+- f5 = MathHelper.a(this.random, 0.0F, 360.0F) * 0.017453292F;
+- f4 = MathHelper.a(this.random, 25.0F, 60.0F);
+- d8 = this.locX + (double) (MathHelper.sin(f5) * f4 * 0.1F);
+- d12 = (double) ((float) MathHelper.floor(this.getBoundingBox().b) + 1.0F);
+- d11 = this.locZ + (double) (MathHelper.cos(f5) * f4 * 0.1F);
+- worldserver.a(EnumParticle.WATER_SPLASH, d8, d12, d11, 2 + this.random.nextInt(2), 0.10000000149011612D, 0.0D, 0.10000000149011612D, 0.0D, new int[0]);
++ f4 = MathHelper.a(this.random, 0.0F, 360.0F) * 0.017453292F;
++ f5 = MathHelper.a(this.random, 25.0F, 60.0F);
++ d8 = this.locX + (double) (MathHelper.sin(f4) * f5 * 0.1F);
++ d11 = (double) ((float) MathHelper.floor(this.getBoundingBox().b) + 1.0F);
++ d12 = this.locZ + (double) (MathHelper.cos(f4) * f5 * 0.1F);
++ worldserver.a(EnumParticle.WATER_SPLASH, d8, d11, d12, 2 + this.random.nextInt(2), 0.10000000149011612D, 0.0D, 0.10000000149011612D, 0.0D, new int[0]);
+ }
+
+ if (this.au <= 0) {
+@@ -374,6 +381,15 @@
+ byte b0 = 0;
+
+ if (this.hooked != null) {
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.owner.getBukkitEntity(), this.hooked.getBukkitEntity(), (Fish) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY);
++ this.world.getServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
++
+ double d0 = this.owner.locX - this.locX;
+ double d1 = this.owner.locY - this.locY;
+ double d2 = this.owner.locZ - this.locZ;
+@@ -386,6 +402,16 @@
+ b0 = 3;
+ } else if (this.at > 0) {
+ EntityItem entityitem = new EntityItem(this.world, this.locX, this.locY, this.locZ, this.m());
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.owner.getBukkitEntity(), entityitem.getBukkitEntity(), (Fish) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH);
++ playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1);
++ this.world.getServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
++
+ double d5 = this.owner.locX - this.locX;
+ double d6 = this.owner.locY - this.locY;
+ double d7 = this.owner.locZ - this.locZ;
+@@ -396,13 +422,32 @@
+ entityitem.motY = d6 * d9 + (double) MathHelper.sqrt(d8) * 0.08D;
+ entityitem.motZ = d7 * d9;
+ this.world.addEntity(entityitem);
+- this.owner.world.addEntity(new EntityExperienceOrb(this.owner.world, this.owner.locX, this.owner.locY + 0.5D, this.owner.locZ + 0.5D, this.random.nextInt(6) + 1));
++ // CraftBukkit - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
++ this.owner.world.addEntity(new EntityExperienceOrb(this.owner.world, this.owner.locX, this.owner.locY + 0.5D, this.owner.locZ + 0.5D, playerFishEvent.getExpToDrop()));
+ b0 = 1;
+ }
+
+ if (this.aq) {
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.owner.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND);
++ this.world.getServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
+ b0 = 2;
+ }
++
++ // CraftBukkit start
++ if (b0 == 0) {
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.owner.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT);
++ this.world.getServer().getPluginManager().callEvent(playerFishEvent);
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ }
++ // CraftBukkit end
+
+ this.die();
+ this.owner.hookedFish = null;
diff --git a/nms-patches/EntityHanging.patch b/nms-patches/EntityHanging.patch
new file mode 100644
index 00000000..d65c6dd9
--- /dev/null
+++ b/nms-patches/EntityHanging.patch
@@ -0,0 +1,111 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityHanging.java 2014-11-27 08:59:46.681421935 +1100
++++ src/main/java/net/minecraft/server/EntityHanging.java 2014-11-27 08:42:10.168850880 +1100
+@@ -4,6 +4,13 @@
+ import java.util.List;
+ import org.apache.commons.lang3.Validate;
+
++// CraftBukkit start
++import org.bukkit.entity.Hanging;
++import org.bukkit.entity.Painting;
++import org.bukkit.event.hanging.HangingBreakEvent;
++import org.bukkit.event.painting.PaintingBreakEvent;
++// CraftBukkit end
++
+ public abstract class EntityHanging extends Entity {
+
+ private int c;
+@@ -77,6 +84,33 @@
+ if (this.c++ == 100 && !this.world.isStatic) {
+ this.c = 0;
+ if (!this.dead && !this.survives()) {
++ // CraftBukkit start - fire break events
++ Material material = this.world.getType(new BlockPosition(this)).getBlock().getMaterial();
++ HangingBreakEvent.RemoveCause cause;
++
++ if (!material.equals(Material.AIR)) {
++ // TODO: This feels insufficient to catch 100% of suffocation cases
++ cause = HangingBreakEvent.RemoveCause.OBSTRUCTION;
++ } else {
++ cause = HangingBreakEvent.RemoveCause.PHYSICS;
++ }
++
++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), cause);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ PaintingBreakEvent paintingEvent = null;
++ if (this instanceof EntityPainting) {
++ // Fire old painting event until it can be removed
++ paintingEvent = new PaintingBreakEvent((Painting) this.getBukkitEntity(), PaintingBreakEvent.RemoveCause.valueOf(cause.name()));
++ paintingEvent.setCancelled(event.isCancelled());
++ this.world.getServer().getPluginManager().callEvent(paintingEvent);
++ }
++
++ if (dead || event.isCancelled() || (paintingEvent != null && paintingEvent.isCancelled())) {
++ return;
++ }
++ // CraftBukkit end
++
+ this.die();
+ this.b((Entity) null);
+ }
+@@ -138,6 +172,32 @@
+ return false;
+ } else {
+ if (!this.dead && !this.world.isStatic) {
++ // CraftBukkit start - fire break events
++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.DEFAULT);
++ PaintingBreakEvent paintingEvent = null;
++ if (damagesource.getEntity() != null) {
++ event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damagesource.getEntity() == null ? null : damagesource.getEntity().getBukkitEntity());
++
++ if (this instanceof EntityPainting) {
++ // Fire old painting event until it can be removed
++ paintingEvent = new org.bukkit.event.painting.PaintingBreakByEntityEvent((Painting) this.getBukkitEntity(), damagesource.getEntity() == null ? null : damagesource.getEntity().getBukkitEntity());
++ }
++ } else if (damagesource.isExplosion()) {
++ event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.EXPLOSION);
++ }
++
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (paintingEvent != null) {
++ paintingEvent.setCancelled(event.isCancelled());
++ this.world.getServer().getPluginManager().callEvent(paintingEvent);
++ }
++
++ if (this.dead || event.isCancelled() || (paintingEvent != null && paintingEvent.isCancelled())) {
++ return true;
++ }
++ // CraftBukkit end
++
+ this.die();
+ this.ac();
+ this.b(damagesource.getEntity());
+@@ -149,6 +209,18 @@
+
+ public void move(double d0, double d1, double d2) {
+ if (!this.world.isStatic && !this.dead && d0 * d0 + d1 * d1 + d2 * d2 > 0.0D) {
++ if (this.dead) return; // CraftBukkit
++
++ // CraftBukkit start - fire break events
++ // TODO - Does this need its own cause? Seems to only be triggered by pistons
++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.PHYSICS);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (this.dead || event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++
+ this.die();
+ this.b((Entity) null);
+ }
+@@ -156,7 +228,7 @@
+ }
+
+ public void g(double d0, double d1, double d2) {
+- if (!this.world.isStatic && !this.dead && d0 * d0 + d1 * d1 + d2 * d2 > 0.0D) {
++ if (false && !this.world.isStatic && !this.dead && d0 * d0 + d1 * d1 + d2 * d2 > 0.0D) { // CraftBukkit - not needed
+ this.die();
+ this.b((Entity) null);
+ }
diff --git a/nms-patches/EntityHorse.patch b/nms-patches/EntityHorse.patch
new file mode 100644
index 00000000..f4ff42d0
--- /dev/null
+++ b/nms-patches/EntityHorse.patch
@@ -0,0 +1,140 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityHorse.java 2014-11-27 08:59:46.681421935 +1100
++++ src/main/java/net/minecraft/server/EntityHorse.java 2014-11-27 08:42:10.148850918 +1100
+@@ -4,6 +4,8 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; // CraftBukkit
++
+ public class EntityHorse extends EntityAnimal implements IInventoryListener {
+
+ private static final Predicate bq = new EntitySelectorHorse();
+@@ -36,6 +38,7 @@
+ private String bM;
+ private String[] bN = new String[3];
+ private boolean bO = false;
++ public int maxDomestication = 100; // CraftBukkit - store max domestication value
+
+ public EntityHorse(World world) {
+ super(world);
+@@ -314,13 +317,13 @@
+ private int cX() {
+ int i = this.getType();
+
+- return this.hasChest() && (i == 1 || i == 2) ? 17 : 2;
++ return this.hasChest() /* && (i == 1 || i == 2) */ ? 17 : 2; // CraftBukkit - Remove type check
+ }
+
+ public void loadChest() {
+ InventoryHorseChest inventoryhorsechest = this.inventoryChest;
+
+- this.inventoryChest = new InventoryHorseChest("HorseChest", this.cX());
++ this.inventoryChest = new InventoryHorseChest("HorseChest", this.cX(), this); // CraftBukkit - add this horse
+ this.inventoryChest.a(this.getName());
+ if (inventoryhorsechest != null) {
+ inventoryhorsechest.b(this);
+@@ -485,7 +488,7 @@
+ }
+
+ public int getMaxDomestication() {
+- return 100;
++ return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100
+ }
+
+ protected float bA() {
+@@ -585,7 +588,7 @@
+ }
+
+ if (this.getHealth() < this.getMaxHealth() && f > 0.0F) {
+- this.heal(f);
++ this.heal(f, RegainReason.EATING); // CraftBukkit
+ flag = true;
+ }
+
+@@ -692,11 +695,24 @@
+
+ public void die(DamageSource damagesource) {
+ super.die(damagesource);
++ /* CraftBukkit start - Handle chest dropping in dropDeathLoot below
+ if (!this.world.isStatic) {
+ this.dropChest();
+ }
++ // CraftBukkit end */
++ }
+
++ // CraftBukkit start - Add method
++ @Override
++ protected void dropDeathLoot(boolean flag, int i) {
++ super.dropDeathLoot(flag, i);
++
++ // Moved from die method above
++ if (!this.world.isStatic) {
++ this.dropChest();
++ }
+ }
++ // CraftBukkit end
+
+ public void m() {
+ if (this.random.nextInt(200) == 0) {
+@@ -706,7 +722,7 @@
+ super.m();
+ if (!this.world.isStatic) {
+ if (this.random.nextInt(900) == 0 && this.deathTicks == 0) {
+- this.heal(1.0F);
++ this.heal(1.0F, RegainReason.REGEN); // CraftBukkit
+ }
+
+ if (!this.cw() && this.passenger == null && this.random.nextInt(300) == 0 && this.world.getType(new BlockPosition(MathHelper.floor(this.locX), MathHelper.floor(this.locY) - 1, MathHelper.floor(this.locZ))).getBlock() == Blocks.GRASS) {
+@@ -949,6 +965,7 @@
+ nbttagcompound.setInt("Temper", this.getTemper());
+ nbttagcompound.setBoolean("Tame", this.isTame());
+ nbttagcompound.setString("OwnerUUID", this.getOwnerUUID());
++ nbttagcompound.setInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
+ if (this.hasChest()) {
+ NBTTagList nbttaglist = new NBTTagList();
+
+@@ -1001,6 +1018,12 @@
+ this.setOwnerUUID(s);
+ }
+
++ // CraftBukkit start
++ if (nbttagcompound.hasKey("Bukkit.MaxDomestication")) {
++ this.maxDomestication = nbttagcompound.getInt("Bukkit.MaxDomestication");
++ }
++ // CraftBukkit end
++
+ AttributeInstance attributeinstance = this.getAttributeMap().a("Speed");
+
+ if (attributeinstance != null) {
+@@ -1166,18 +1189,25 @@
+
+ public void v(int i) {
+ if (this.cE()) {
++ // CraftBukkit start - fire HorseJumpEvent, use event power
+ if (i < 0) {
+ i = 0;
+- } else {
+- this.bE = true;
+- this.df();
+ }
+-
++
++ float power;
+ if (i >= 90) {
+- this.bp = 1.0F;
++ power = 1.0F;
+ } else {
+- this.bp = 0.4F + 0.4F * (float) i / 90.0F;
++ power = 0.4F + 0.4F * (float) i / 90.0F;
++ }
++
++ org.bukkit.event.entity.HorseJumpEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callHorseJumpEvent(this, power);
++ if (!event.isCancelled()) {
++ this.bE = true;
++ this.df();
++ this.bp = power;
+ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/EntityHuman.patch b/nms-patches/EntityHuman.patch
new file mode 100644
index 00000000..185b52b4
--- /dev/null
+++ b/nms-patches/EntityHuman.patch
@@ -0,0 +1,383 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityHuman.java 2014-11-27 08:59:46.685421917 +1100
++++ src/main/java/net/minecraft/server/EntityHuman.java 2014-11-27 08:42:10.120850973 +1100
+@@ -8,13 +8,25 @@
+ import java.util.List;
+ import java.util.UUID;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.entity.CraftItem;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.Player;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.player.PlayerBedEnterEvent;
++import org.bukkit.event.player.PlayerBedLeaveEvent;
++import org.bukkit.event.player.PlayerDropItemEvent;
++import org.bukkit.event.player.PlayerItemConsumeEvent;
++// CraftBukkit end
++
+ public abstract class EntityHuman extends EntityLiving {
+
+ public PlayerInventory inventory = new PlayerInventory(this);
+ private InventoryEnderChest enderChest = new InventoryEnderChest();
+ public Container defaultContainer;
+ public Container activeContainer;
+- protected FoodMetaData foodData = new FoodMetaData();
++ protected FoodMetaData foodData = new FoodMetaData(this); // CraftBukkit - add "this" to constructor
+ protected int bk;
+ public float bl;
+ public float bm;
+@@ -34,6 +46,7 @@
+ private boolean d;
+ private BlockPosition e;
+ public PlayerAbilities abilities = new PlayerAbilities();
++ public int oldLevel = -1; // CraftBukkit - add field
+ public int expLevel;
+ public int expTotal;
+ public float exp;
+@@ -46,6 +59,16 @@
+ private final GameProfile bF;
+ private boolean bG = false;
+ public EntityFishingHook hookedFish;
++
++ // CraftBukkit start
++ public boolean fauxSleeping;
++ public String spawnWorld = "";
++
++ @Override
++ public CraftHumanEntity getBukkitEntity() {
++ return (CraftHumanEntity) super.getBukkitEntity();
++ }
++ // CraftBukkit end
+
+ public EntityHuman(World world, GameProfile gameprofile) {
+ super(world);
+@@ -265,6 +288,32 @@
+ if (this.g != null) {
+ this.b(this.g, 16);
+ int i = this.g.count;
++
++ // CraftBukkit start - fire PlayerItemConsumeEvent
++ org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.g);
++ PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ // Update client
++ if (this instanceof EntityPlayer) {
++ ((EntityPlayer) this).playerConnection.sendPacket(new PacketPlayOutSetSlot((byte) 0, activeContainer.getSlot((IInventory) this.inventory, this.inventory.itemInHandIndex).index, this.g));
++ }
++ return;
++ }
++
++ // Plugin modified the item, process it but don't remove it
++ if (!craftItem.equals(event.getItem())) {
++ CraftItemStack.asNMSCopy(event.getItem()).b(this.world, this);
++
++ // Update client
++ if (this instanceof EntityPlayer) {
++ ((EntityPlayer) this).playerConnection.sendPacket(new PacketPlayOutSetSlot((byte) 0, activeContainer.getSlot((IInventory) this.inventory, this.inventory.itemInHandIndex).index, this.g));
++ }
++ return;
++ }
++ // CraftBukkit end
++
+ ItemStack itemstack = this.g.b(this.world, this);
+
+ if (itemstack != this.g || itemstack != null && itemstack.count != i) {
+@@ -324,7 +373,8 @@
+
+ if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.world.getGameRules().getBoolean("naturalRegeneration")) {
+ if (this.getHealth() < this.getMaxHealth() && this.ticksLived % 20 == 0) {
+- this.heal(1.0F);
++ // CraftBukkit - added regain reason of "REGEN" for filtering purposes.
++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN);
+ }
+
+ if (this.foodData.c() && this.ticksLived % 10 == 0) {
+@@ -348,7 +398,7 @@
+
+ this.j((float) attributeinstance.getValue());
+ float f = MathHelper.sqrt(this.motX * this.motX + this.motZ * this.motZ);
+- float f1 = (float) (Math.atan(-this.motY * 0.20000000298023224D) * 15.0D);
++ float f1 = (float) ( org.bukkit.craftbukkit.TrigMath.atan(-this.motY * 0.20000000298023224D) * 15.0D); // CraftBukkit
+
+ if (f > 0.1F) {
+ f = 0.1F;
+@@ -438,11 +488,14 @@
+
+ public void b(Entity entity, int i) {
+ this.addScore(i);
+- Collection collection = this.getScoreboard().getObjectivesForCriteria(IScoreboardCriteria.f);
++ // CraftBukkit - Get our scores instead
++ Collection<ScoreboardScore> collection = this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreboardCriteria.e, this.getName(), new java.util.ArrayList<ScoreboardScore>());
++
+
+ if (entity instanceof EntityHuman) {
+ this.b(StatisticList.B);
+- collection.addAll(this.getScoreboard().getObjectivesForCriteria(IScoreboardCriteria.e));
++ // CraftBukkit - Get our scores instead
++ this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreboardCriteria.d, this.getName(), collection);
+ collection.addAll(this.e(entity));
+ } else {
+ this.b(StatisticList.z);
+@@ -451,8 +504,7 @@
+ Iterator iterator = collection.iterator();
+
+ while (iterator.hasNext()) {
+- ScoreboardObjective scoreboardobjective = (ScoreboardObjective) iterator.next();
+- ScoreboardScore scoreboardscore = this.getScoreboard().getPlayerScoreForObjective(this.getName(), scoreboardobjective);
++ ScoreboardScore scoreboardscore = (ScoreboardScore) iterator.next(); // CraftBukkit - Use our scores instead
+
+ scoreboardscore.incrementScore();
+ }
+@@ -491,6 +543,7 @@
+ }
+
+ public EntityItem a(boolean flag) {
++ // Called only when dropped by Q or CTRL-Q
+ return this.a(this.inventory.splitStack(this.inventory.itemInHandIndex, flag && this.inventory.getItemInHand() != null ? this.inventory.getItemInHand().count : 1), false, true);
+ }
+
+@@ -532,6 +585,30 @@
+ entityitem.motY += (double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F);
+ entityitem.motZ += Math.sin((double) f1) * (double) f;
+ }
++
++ // CraftBukkit start - fire PlayerDropItemEvent
++ Player player = (Player) this.getBukkitEntity();
++ CraftItem drop = new CraftItem(this.world.getServer(), entityitem);
++
++ PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand();
++ if (flag1 && (cur == null || cur.getAmount() == 0)) {
++ // The complete stack was dropped
++ player.getInventory().setItemInHand(drop.getItemStack());
++ } else if (flag1 && cur.isSimilar(drop.getItemStack()) && drop.getItemStack().getAmount() == 1) {
++ // Only one item is dropped
++ cur.setAmount(cur.getAmount() + 1);
++ player.getInventory().setItemInHand(cur);
++ } else {
++ // Fallback
++ player.getInventory().addItem(drop.getItemStack());
++ }
++ return null;
++ }
++ // CraftBukkit end
+
+ this.a(entityitem);
+ if (flag1) {
+@@ -623,10 +700,18 @@
+ this.bv = new BlockPosition(this);
+ this.a(true, true, false);
+ }
++
++ // CraftBukkit start
++ this.spawnWorld = nbttagcompound.getString("SpawnWorld");
++ if ("".equals(spawnWorld)) {
++ this.spawnWorld = this.world.getServer().getWorlds().get(0).getName();
++ }
++ // CraftBukkit end
+
+ if (nbttagcompound.hasKeyOfType("SpawnX", 99) && nbttagcompound.hasKeyOfType("SpawnY", 99) && nbttagcompound.hasKeyOfType("SpawnZ", 99)) {
+ this.c = new BlockPosition(nbttagcompound.getInt("SpawnX"), nbttagcompound.getInt("SpawnY"), nbttagcompound.getInt("SpawnZ"));
+ this.d = nbttagcompound.getBoolean("SpawnForced");
++ nbttagcompound.setString("SpawnWorld", spawnWorld); // CraftBukkit - fixes bed spawns for multiworld worlds
+ }
+
+ this.foodData.a(nbttagcompound);
+@@ -684,7 +769,7 @@
+
+ if (damagesource.r()) {
+ if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL) {
+- f = 0.0F;
++ return false; // CraftBukkit - f = 0.0f -> return false
+ }
+
+ if (this.world.getDifficulty() == EnumDifficulty.EASY) {
+@@ -696,7 +781,7 @@
+ }
+ }
+
+- if (f == 0.0F) {
++ if (false && f == 0.0F) { // CraftBukkit - Don't filter out 0 damage
+ return false;
+ } else {
+ Entity entity = damagesource.getEntity();
+@@ -712,10 +797,29 @@
+ }
+
+ public boolean a(EntityHuman entityhuman) {
+- ScoreboardTeamBase scoreboardteambase = this.getScoreboardTeam();
+- ScoreboardTeamBase scoreboardteambase1 = entityhuman.getScoreboardTeam();
++ // CraftBukkit start - Change to check OTHER player's scoreboard team according to API
++ // To summarize this method's logic, it's "Can parameter hurt this"
++ org.bukkit.scoreboard.Team team;
++ if (entityhuman instanceof EntityPlayer) {
++ EntityPlayer thatPlayer = (EntityPlayer) entityhuman;
++ team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity());
++ if (team == null || team.allowFriendlyFire()) {
++ return true;
++ }
++ } else {
++ // This should never be called, but is implemented anyway
++ org.bukkit.OfflinePlayer thisPlayer = entityhuman.world.getServer().getOfflinePlayer(entityhuman.getName());
++ team = entityhuman.world.getServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer);
++ if (team == null || team.allowFriendlyFire()) {
++ return true;
++ }
++ }
+
+- return scoreboardteambase == null ? true : (!scoreboardteambase.isAlly(scoreboardteambase1) ? true : scoreboardteambase.allowFriendlyFire());
++ if (this instanceof EntityPlayer) {
++ return !team.hasPlayer(((EntityPlayer) this).getBukkitEntity());
++ }
++ return !team.hasPlayer(this.world.getServer().getOfflinePlayer(this.getName()));
++ // CraftBukkit end
+ }
+
+ protected void damageArmor(float f) {
+@@ -742,7 +846,12 @@
+ return (float) i / (float) this.inventory.armor.length;
+ }
+
+- protected void d(DamageSource damagesource, float f) {
++ // CraftBukkit start
++ protected boolean d(DamageSource damagesource, float f) { // void -> boolean
++ if (true) {
++ return super.d(damagesource, f);
++ }
++ // CraftBukkit end
+ if (!this.isInvulnerable(damagesource)) {
+ if (!damagesource.ignoresArmor() && this.isBlocking() && f > 0.0F) {
+ f = (1.0F + f) * 0.5F;
+@@ -766,6 +875,7 @@
+
+ }
+ }
++ return false; // CraftBukkit
+ }
+
+ public void openSign(TileEntitySign tileentitysign) {}
+@@ -800,7 +910,8 @@
+ }
+
+ if (itemstack.a(this, (EntityLiving) entity)) {
+- if (itemstack.count <= 0 && !this.abilities.canInstantlyBuild) {
++ // CraftBukkit - bypass infinite items; <= 0 -> == 0
++ if (itemstack.count == 0 && !this.abilities.canInstantlyBuild) {
+ this.bZ();
+ }
+
+@@ -866,8 +977,15 @@
+ int j = EnchantmentManager.getFireAspectEnchantmentLevel(this);
+
+ if (entity instanceof EntityLiving && j > 0 && !entity.isBurning()) {
+- flag1 = true;
+- entity.setOnFire(1);
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 1);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ flag1 = true;
++ entity.setOnFire(combustEvent.getDuration());
++ }
++ // CraftBukkit end
+ }
+
+ double d0 = entity.motX;
+@@ -922,7 +1040,8 @@
+
+ if (itemstack != null && object instanceof EntityLiving) {
+ itemstack.a((EntityLiving) object, this);
+- if (itemstack.count <= 0) {
++ // CraftBukkit - bypass infinite items; <= 0 -> == 0
++ if (itemstack.count == 0) {
+ this.bZ();
+ }
+ }
+@@ -930,7 +1049,14 @@
+ if (entity instanceof EntityLiving) {
+ this.a(StatisticList.w, Math.round(f * 10.0F));
+ if (j > 0) {
+- entity.setOnFire(j * 4);
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), j * 4);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ entity.setOnFire(combustEvent.getDuration());
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -995,6 +1121,20 @@
+ if (this.av()) {
+ this.mount((Entity) null);
+ }
++
++ // CraftBukkit start - fire PlayerBedEnterEvent
++ if (this.getBukkitEntity() instanceof Player) {
++ Player player = (Player) this.getBukkitEntity();
++ org.bukkit.block.Block bed = this.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ PlayerBedEnterEvent event = new PlayerBedEnterEvent(player, bed);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return EnumBedResult.OTHER_PROBLEM;
++ }
++ }
++ // CraftBukkit end
+
+ this.a(0.2F, 0.2F);
+ if (this.world.isLoaded(blockposition)) {
+@@ -1077,6 +1217,23 @@
+ if (!this.world.isStatic && flag1) {
+ this.world.everyoneSleeping();
+ }
++
++ // CraftBukkit start - fire PlayerBedLeaveEvent
++ if (this.getBukkitEntity() instanceof Player) {
++ Player player = (Player) this.getBukkitEntity();
++
++ org.bukkit.block.Block bed;
++ BlockPosition blockposition = this.bv;
++ if (blockposition != null) {
++ bed = this.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ } else {
++ bed = this.world.getWorld().getBlockAt(player.getLocation());
++ }
++
++ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed);
++ this.world.getServer().getPluginManager().callEvent(event);
++ }
++ // CraftBukkit end
+
+ this.sleepTicks = flag ? 0 : 100;
+ if (flag2) {
+@@ -1128,9 +1285,11 @@
+ if (blockposition != null) {
+ this.c = blockposition;
+ this.d = flag;
++ this.spawnWorld = this.world.worldData.getName(); // CraftBukkit
+ } else {
+ this.c = null;
+ this.d = false;
++ this.spawnWorld = ""; // CraftBukkit
+ }
+
+ }
+@@ -1477,6 +1636,7 @@
+ }
+
+ public IChatBaseComponent getScoreboardDisplayName() {
++ // CraftBukkit - todo: fun
+ ChatComponentText chatcomponenttext = new ChatComponentText(ScoreboardTeam.getPlayerDisplayName(this.getScoreboardTeam(), this.getName()));
+
+ chatcomponenttext.getChatModifier().setChatClickable(new ChatClickable(EnumClickAction.SUGGEST_COMMAND, "/msg " + this.getName() + " "));
diff --git a/nms-patches/EntityInsentient.patch b/nms-patches/EntityInsentient.patch
new file mode 100644
index 00000000..17867b8d
--- /dev/null
+++ b/nms-patches/EntityInsentient.patch
@@ -0,0 +1,164 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityInsentient.java 2014-11-27 08:59:46.689421900 +1100
++++ src/main/java/net/minecraft/server/EntityInsentient.java 2014-11-27 08:42:10.156850903 +1100
+@@ -4,6 +4,15 @@
+ import java.util.List;
+ import java.util.UUID;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityUnleashEvent;
++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
++// CraftBukkit end
++
+ public abstract class EntityInsentient extends EntityLiving {
+
+ public int a_;
+@@ -39,7 +48,9 @@
+ for (int i = 0; i < this.dropChances.length; ++i) {
+ this.dropChances[i] = 0.085F;
+ }
+-
++ // CraftBukkit start - default persistance to type's persistance value
++ this.persistent = !isTypeNotPersistent();
++ // CraftBukkit end
+ }
+
+ protected void aW() {
+@@ -76,7 +87,37 @@
+ }
+
+ public void setGoalTarget(EntityLiving entityliving) {
++ // CraftBukkit start - fire event
++ setGoalTarget(entityliving, EntityTargetEvent.TargetReason.UNKNOWN, true);
++ }
++
++ public void setGoalTarget(EntityLiving entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++ if (getGoalTarget() == entityliving) return;
++ if (fireEvent) {
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN && getGoalTarget() != null && entityliving == null) {
++ reason = getGoalTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED;
++ }
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN) {
++ world.getServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception());
++ }
++ CraftLivingEntity ctarget = null;
++ if (entityliving != null) {
++ ctarget = (CraftLivingEntity) entityliving.getBukkitEntity();
++ }
++ EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason);
++ world.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++
++ if (event.getTarget() != null) {
++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
++ } else {
++ entityliving = null;
++ }
++ }
+ this.goalTarget = entityliving;
++ // CraftBukkit end
+ }
+
+ public boolean a(Class oclass) {
+@@ -235,11 +276,21 @@
+
+ public void a(NBTTagCompound nbttagcompound) {
+ super.a(nbttagcompound);
++
++ // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it
+ if (nbttagcompound.hasKeyOfType("CanPickUpLoot", 1)) {
+- this.j(nbttagcompound.getBoolean("CanPickUpLoot"));
++ boolean data = nbttagcompound.getBoolean("CanPickUpLoot");
++ if (isLevelAtLeast(nbttagcompound, 1) || data) {
++ this.j(data);
++ }
+ }
+
+- this.persistent = nbttagcompound.getBoolean("PersistenceRequired");
++ boolean data = nbttagcompound.getBoolean("PersistenceRequired");
++ if (isLevelAtLeast(nbttagcompound, 1) || data) {
++ this.persistent = data;
++ }
++ // CraftBukkit end
++
+ NBTTagList nbttaglist;
+ int i;
+
+@@ -380,11 +431,11 @@
+ double d2 = entityhuman.locZ - this.locZ;
+ double d3 = d0 * d0 + d1 * d1 + d2 * d2;
+
+- if (this.isTypeNotPersistent() && d3 > 16384.0D) {
++ if (d3 > 16384.0D) { // CraftBukkit - remove isTypeNotPersistent() check
+ this.die();
+ }
+
+- if (this.aO > 600 && this.random.nextInt(800) == 0 && d3 > 1024.0D && this.isTypeNotPersistent()) {
++ if (this.aO > 600 && this.random.nextInt(800) == 0 && d3 > 1024.0D) { // CraftBukkit - remove isTypeNotPersistent() check
+ this.die();
+ } else if (d3 < 1024.0D) {
+ this.aO = 0;
+@@ -707,6 +758,12 @@
+
+ public final boolean e(EntityHuman entityhuman) {
+ if (this.cb() && this.getLeashHolder() == entityhuman) {
++ // CraftBukkit start - fire PlayerUnleashEntityEvent
++ if (CraftEventFactory.callPlayerUnleashEntityEvent(this, entityhuman).isCancelled()) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(1, this, this.getLeashHolder()));
++ return false;
++ }
++ // CraftBukkit end
+ this.unleash(true, !entityhuman.abilities.canInstantlyBuild);
+ return true;
+ } else {
+@@ -714,12 +771,24 @@
+
+ if (itemstack != null && itemstack.getItem() == Items.LEAD && this.ca()) {
+ if (!(this instanceof EntityTameableAnimal) || !((EntityTameableAnimal) this).isTamed()) {
++ // CraftBukkit start - fire PlayerLeashEntityEvent
++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, entityhuman, entityhuman).isCancelled()) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(1, this, this.getLeashHolder()));
++ return false;
++ }
++ // CraftBukkit end
+ this.setLeashHolder(entityhuman, true);
+ --itemstack.count;
+ return true;
+ }
+
+ if (((EntityTameableAnimal) this).e((EntityLiving) entityhuman)) {
++ // CraftBukkit start - fire PlayerLeashEntityEvent
++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, entityhuman, entityhuman).isCancelled()) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(1, this, this.getLeashHolder()));
++ return false;
++ }
++ // CraftBukkit end
+ this.setLeashHolder(entityhuman, true);
+ --itemstack.count;
+ return true;
+@@ -741,10 +810,12 @@
+
+ if (this.bm) {
+ if (!this.isAlive()) {
++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.PLAYER_UNLEASH)); // CraftBukkit
+ this.unleash(true, true);
+ }
+
+ if (this.bn == null || this.bn.dead) {
++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.HOLDER_GONE)); // CraftBukkit
+ this.unleash(true, true);
+ }
+ }
+@@ -811,6 +882,7 @@
+
+ this.bn = entityleash;
+ } else {
++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+ this.unleash(false, true);
+ }
+ }
diff --git a/nms-patches/EntityIronGolem.patch b/nms-patches/EntityIronGolem.patch
new file mode 100644
index 00000000..e71334e1
--- /dev/null
+++ b/nms-patches/EntityIronGolem.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityIronGolem.java 2014-11-27 08:59:46.689421900 +1100
++++ src/main/java/net/minecraft/server/EntityIronGolem.java 2014-11-27 08:42:10.100851012 +1100
+@@ -57,7 +57,7 @@
+
+ protected void s(Entity entity) {
+ if (entity instanceof IMonster && this.bb().nextInt(20) == 0) {
+- this.setGoalTarget((EntityLiving) entity);
++ this.setGoalTarget((EntityLiving) entity, org.bukkit.event.entity.EntityTargetLivingEntityEvent.TargetReason.COLLISION, true); // CraftBukkit - set reason
+ }
+
+ super.s(entity);
diff --git a/nms-patches/EntityItem.patch b/nms-patches/EntityItem.patch
new file mode 100644
index 00000000..0873e363
--- /dev/null
+++ b/nms-patches/EntityItem.patch
@@ -0,0 +1,115 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityItem.java 2014-11-27 08:59:46.693421882 +1100
++++ src/main/java/net/minecraft/server/EntityItem.java 2014-11-27 08:42:10.104851005 +1100
+@@ -4,6 +4,8 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import org.bukkit.event.player.PlayerPickupItemEvent; // CraftBukkit
++
+ public class EntityItem extends Entity {
+
+ private static final Logger b = LogManager.getLogger();
+@@ -13,6 +15,7 @@
+ private String f;
+ private String g;
+ public float a;
++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit
+
+ public EntityItem(World world, double d0, double d1, double d2) {
+ super(world);
+@@ -28,6 +31,11 @@
+
+ public EntityItem(World world, double d0, double d1, double d2, ItemStack itemstack) {
+ this(world, d0, d1, d2);
++ // CraftBukkit start - Can't set null items in the datawatcher
++ if (itemstack == null || itemstack.getItem() == null) {
++ return;
++ }
++ // CraftBukkit end
+ this.setItemStack(itemstack);
+ }
+
+@@ -52,9 +60,12 @@
+ this.die();
+ } else {
+ super.s_();
+- if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
+- --this.pickupDelay;
+- }
++ // CraftBukkit start - Use wall time for pickup and despawn timers
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ this.pickupDelay -= elapsedTicks;
++ this.age += elapsedTicks;
++ this.lastTick = MinecraftServer.currentTick;
++ // CraftBukkit end
+
+ this.lastX = this.locX;
+ this.lastY = this.locY;
+@@ -90,12 +101,20 @@
+ this.motY *= -0.5D;
+ }
+
++ /* Craftbukkit start - moved up
+ if (this.age != -32768) {
+ ++this.age;
+ }
++ // Craftbukkit end */
+
+ this.W();
+ if (!this.world.isStatic && this.age >= 6000) {
++ // CraftBukkit start - fire ItemDespawnEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
++ this.age = 0;
++ return;
++ }
++ // CraftBukkit end
+ this.die();
+ }
+
+@@ -228,7 +247,18 @@
+
+ NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Item");
+
+- this.setItemStack(ItemStack.createStack(nbttagcompound1));
++ // CraftBukkit start - Handle missing "Item" compounds
++ if (nbttagcompound1 != null) {
++ ItemStack itemstack = ItemStack.createStack(nbttagcompound1);
++ if (itemstack != null) {
++ this.setItemStack(itemstack);
++ } else {
++ this.die();
++ }
++ } else {
++ this.die();
++ }
++ // CraftBukkit end
+ if (this.getItemStack() == null) {
+ this.die();
+ }
+@@ -239,6 +269,26 @@
+ if (!this.world.isStatic) {
+ ItemStack itemstack = this.getItemStack();
+ int i = itemstack.count;
++
++ // CraftBukkit start - fire PlayerPickupItemEvent
++ int canHold = entityhuman.inventory.canHold(itemstack);
++ int remaining = itemstack.count - canHold;
++
++ if (this.pickupDelay <= 0 && canHold > 0) {
++ itemstack.count = canHold;
++ PlayerPickupItemEvent event = new PlayerPickupItemEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++ // event.setCancelled(!entityhuman.canPickUpLoot); TODO
++ this.world.getServer().getPluginManager().callEvent(event);
++ itemstack.count = canHold + remaining;
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ // Possibly < 0; fix here so we do not have to modify code below
++ this.pickupDelay = 0;
++ }
++ // CraftBukkit end
+
+ if (this.pickupDelay == 0 && (this.g == null || 6000 - this.age <= 200 || this.g.equals(entityhuman.getName())) && entityhuman.inventory.pickup(itemstack)) {
+ if (itemstack.getItem() == Item.getItemOf(Blocks.LOG)) {
diff --git a/nms-patches/EntityItemFrame.patch b/nms-patches/EntityItemFrame.patch
new file mode 100644
index 00000000..1ae52fd2
--- /dev/null
+++ b/nms-patches/EntityItemFrame.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityItemFrame.java 2014-11-27 08:59:46.693421882 +1100
++++ src/main/java/net/minecraft/server/EntityItemFrame.java 2014-11-27 08:42:10.176850864 +1100
+@@ -27,6 +27,12 @@
+ return false;
+ } else if (!damagesource.isExplosion() && this.getItem() != null) {
+ if (!this.world.isStatic) {
++ // CraftBukkit start - fire EntityDamageEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damagesource, f, false) || this.dead) {
++ return true;
++ }
++ // CraftBukkit end
++
+ this.a(damagesource.getEntity(), false);
+ this.setItem((ItemStack) null);
+ }
diff --git a/nms-patches/EntityLargeFireball.patch b/nms-patches/EntityLargeFireball.patch
new file mode 100644
index 00000000..2e14fc4d
--- /dev/null
+++ b/nms-patches/EntityLargeFireball.patch
@@ -0,0 +1,38 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityLargeFireball.java 2014-11-27 08:59:46.697421864 +1100
++++ src/main/java/net/minecraft/server/EntityLargeFireball.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.ExplosionPrimeEvent; // CraftBukkit
++
+ public class EntityLargeFireball extends EntityFireball {
+
+ public int yield = 1;
+@@ -21,7 +23,16 @@
+
+ boolean flag = this.world.getGameRules().getBoolean("mobGriefing");
+
+- this.world.createExplosion((Entity) null, this.locX, this.locY, this.locZ, (float) this.yield, flag, flag);
++ // CraftBukkit start - fire ExplosionPrimeEvent
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) org.bukkit.craftbukkit.entity.CraftEntity.getEntity(this.world.getServer(), this));
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ // give 'this' instead of (Entity) null so we know what causes the damage
++ this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire(), flag);
++ }
++ // CraftBukkit end
++
+ this.die();
+ }
+
+@@ -35,7 +46,8 @@
+ public void a(NBTTagCompound nbttagcompound) {
+ super.a(nbttagcompound);
+ if (nbttagcompound.hasKeyOfType("ExplosionPower", 99)) {
+- this.yield = nbttagcompound.getInt("ExplosionPower");
++ // CraftBukkit - set bukkitYield when setting explosionpower
++ bukkitYield = this.yield = nbttagcompound.getInt("ExplosionPower");
+ }
+
+ }
diff --git a/nms-patches/EntityLeash.patch b/nms-patches/EntityLeash.patch
new file mode 100644
index 00000000..65d979ac
--- /dev/null
+++ b/nms-patches/EntityLeash.patch
@@ -0,0 +1,61 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityLeash.java 2014-11-27 08:59:46.701421847 +1100
++++ src/main/java/net/minecraft/server/EntityLeash.java 2014-11-27 08:42:10.136850942 +1100
+@@ -3,6 +3,8 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class EntityLeash extends EntityHanging {
+
+ public EntityLeash(World world) {
+@@ -63,6 +65,12 @@
+ while (iterator.hasNext()) {
+ entityinsentient = (EntityInsentient) iterator.next();
+ if (entityinsentient.cb() && entityinsentient.getLeashHolder() == entityhuman) {
++ // CraftBukkit start
++ if (CraftEventFactory.callPlayerLeashEntityEvent(entityinsentient, this, entityhuman).isCancelled()) {
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(1, entityinsentient, entityinsentient.getLeashHolder()));
++ continue;
++ }
++ // CraftBukkit end
+ entityinsentient.setLeashHolder(this, true);
+ flag = true;
+ }
+@@ -70,8 +78,11 @@
+ }
+
+ if (!this.world.isStatic && !flag) {
+- this.die();
+- if (entityhuman.abilities.canInstantlyBuild) {
++ // CraftBukkit start - Move below
++ // this.die();
++ boolean die = true;
++ // CraftBukkit end
++ if (true || entityhuman.abilities.canInstantlyBuild) { // CraftBukkit - Process for non-creative as well
+ d0 = 7.0D;
+ list = this.world.a(EntityInsentient.class, new AxisAlignedBB(this.locX - d0, this.locY - d0, this.locZ - d0, this.locX + d0, this.locY + d0, this.locZ + d0));
+ iterator = list.iterator();
+@@ -79,10 +90,21 @@
+ while (iterator.hasNext()) {
+ entityinsentient = (EntityInsentient) iterator.next();
+ if (entityinsentient.cb() && entityinsentient.getLeashHolder() == this) {
+- entityinsentient.unleash(true, false);
++ // CraftBukkit start
++ if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, entityhuman).isCancelled()) {
++ die = false;
++ continue;
++ }
++ entityinsentient.unleash(true, !entityhuman.abilities.canInstantlyBuild); // false -> survival mode boolean
++ // CraftBukkit end
+ }
+ }
+ }
++ // CraftBukkit start
++ if (die) {
++ this.die();
++ }
++ // CraftBukkit end
+ }
+
+ return true;
diff --git a/nms-patches/EntityLightning.patch b/nms-patches/EntityLightning.patch
new file mode 100644
index 00000000..7e8633d2
--- /dev/null
+++ b/nms-patches/EntityLightning.patch
@@ -0,0 +1,111 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityLightning.java 2014-11-27 08:59:46.701421847 +1100
++++ src/main/java/net/minecraft/server/EntityLightning.java 2014-11-27 08:42:10.116850981 +1100
+@@ -2,30 +2,54 @@
+
+ import java.util.List;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class EntityLightning extends EntityWeather {
+
+ private int lifeTicks;
+ public long a;
+ private int c;
++
++ // CraftBukkit start
++ public boolean isEffect = false;
+
+ public EntityLightning(World world, double d0, double d1, double d2) {
++ this(world, d0, d1, d2, false);
++ }
++
++ public EntityLightning(World world, double d0, double d1, double d2, boolean isEffect) {
++ // CraftBukkit end
++
+ super(world);
++
++ // CraftBukkit - Set isEffect
++ this.isEffect = isEffect;
++
+ this.setPositionRotation(d0, d1, d2, 0.0F, 0.0F);
+ this.lifeTicks = 2;
+ this.a = this.random.nextLong();
+ this.c = this.random.nextInt(3) + 1;
+- if (!world.isStatic && world.getGameRules().getBoolean("doFireTick") && (world.getDifficulty() == EnumDifficulty.NORMAL || world.getDifficulty() == EnumDifficulty.HARD) && world.areChunksLoaded(new BlockPosition(this), 10)) {
++ // CraftBukkit - add "!isEffect"
++ if (!isEffect && !world.isStatic && world.getGameRules().getBoolean("doFireTick") && (world.getDifficulty() == EnumDifficulty.NORMAL || world.getDifficulty() == EnumDifficulty.HARD) && world.areChunksLoaded(new BlockPosition(this), 10)) {
+ BlockPosition blockposition = new BlockPosition(this);
+
+- if (world.getType(blockposition).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(world, blockposition)) {
+- world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ if (world.getType(blockposition).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(world, blockposition)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this).isCancelled()) {
++ world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ BlockPosition blockposition1 = blockposition.a(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
+
+- if (world.getType(blockposition1).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(world, blockposition1)) {
+- world.setTypeUpdate(blockposition1, Blocks.FIRE.getBlockData());
++ if (world.getType(blockposition1).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(world, blockposition1)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callBlockIgniteEvent(world, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ(), this).isCancelled()) {
++ world.setTypeUpdate(blockposition1, Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -35,7 +59,24 @@
+ public void s_() {
+ super.s_();
+ if (this.lifeTicks == 2) {
+- this.world.makeSound(this.locX, this.locY, this.locZ, "ambient.weather.thunder", 10000.0F, 0.8F + this.random.nextFloat() * 0.2F);
++ // CraftBukkit start - Use relative location for far away sounds
++ //this.world.makeSound(this.locX, this.locY, this.locZ, "ambient.weather.thunder", 10000.0F, 0.8F + this.random.nextFloat() * 0.2F);
++ float pitch = 0.8F + this.random.nextFloat() * 0.2F;
++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16;
++ for (EntityPlayer player : (List<EntityPlayer>) this.world.players) {
++ double deltaX = this.locX - player.locX;
++ double deltaZ = this.locZ - player.locZ;
++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++ if (distanceSquared > viewDistance * viewDistance) {
++ double deltaLength = Math.sqrt(distanceSquared);
++ double relativeX = player.locX + (deltaX / deltaLength) * viewDistance;
++ double relativeZ = player.locZ + (deltaZ / deltaLength) * viewDistance;
++ player.playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect("ambient.weather.thunder", relativeX, this.locY, relativeZ, 10000.0F, pitch));
++ } else {
++ player.playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect("ambient.weather.thunder", this.locX, this.locY, this.locZ, 10000.0F, pitch));
++ }
++ }
++ // CraftBukkit end
+ this.world.makeSound(this.locX, this.locY, this.locZ, "random.explode", 2.0F, 0.5F + this.random.nextFloat() * 0.2F);
+ }
+
+@@ -48,14 +89,18 @@
+ this.lifeTicks = 1;
+ this.a = this.random.nextLong();
+ BlockPosition blockposition = new BlockPosition(this);
+-
+- if (!this.world.isStatic && this.world.getGameRules().getBoolean("doFireTick") && this.world.areChunksLoaded(blockposition, 10) && this.world.getType(blockposition).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(this.world, blockposition)) {
+- this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ // CraftBukkit - add "!isEffect"
++ if (!isEffect && !this.world.isStatic && this.world.getGameRules().getBoolean("doFireTick") && this.world.areChunksLoaded(blockposition, 10) && this.world.getType(blockposition).getBlock().getMaterial() == Material.AIR && Blocks.FIRE.canPlace(this.world, blockposition)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this).isCancelled()) {
++ this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+
+- if (this.lifeTicks >= 0) {
++ if (this.lifeTicks >= 0 && !this.isEffect) { // CraftBukkit - add !this.isEffect
+ if (this.world.isStatic) {
+ this.world.c(2);
+ } else {
diff --git a/nms-patches/EntityLiving.patch b/nms-patches/EntityLiving.patch
new file mode 100644
index 00000000..880a5b32
--- /dev/null
+++ b/nms-patches/EntityLiving.patch
@@ -0,0 +1,440 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityLiving.java 2014-11-27 08:59:46.705421829 +1100
++++ src/main/java/net/minecraft/server/EntityLiving.java 2014-11-27 08:42:10.160850895 +1100
+@@ -8,6 +8,15 @@
+ import java.util.Random;
+ import java.util.UUID;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import com.google.common.base.Function;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityDamageEvent;
++import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++// CraftBukkit end
++
+ public abstract class EntityLiving extends Entity {
+
+ private static final UUID a = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D");
+@@ -67,6 +76,11 @@
+ private float bk;
+ private int bl;
+ private float bm;
++ // CraftBukkit start
++ public int expToDrop;
++ public int maxAirTicks = 300;
++ ArrayList<org.bukkit.inventory.ItemStack> drops = null;
++ // CraftBukkit end
+
+ public void G() {
+ this.damageEntity(DamageSource.OUT_OF_WORLD, Float.MAX_VALUE);
+@@ -75,7 +89,8 @@
+ public EntityLiving(World world) {
+ super(world);
+ this.aW();
+- this.setHealth(this.getMaxHealth());
++ // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
++ this.datawatcher.watch(6, (float) this.getAttributeInstance(GenericAttributes.maxHealth).getValue());
+ this.k = true;
+ this.aF = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
+ this.setPosition(this.locX, this.locY, this.locZ);
+@@ -116,8 +131,14 @@
+ }
+
+ int i = (int) (150.0D * d1);
+-
+- ((WorldServer) this.world).a(EnumParticle.BLOCK_DUST, this.locX, this.locY, this.locZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, new int[] { Block.getCombinedId(iblockdata)});
++
++ // CraftBukkit start - visiblity api
++ if (this instanceof EntityPlayer) {
++ ((WorldServer) this.world).sendParticles((EntityPlayer) this, EnumParticle.BLOCK_DUST, false, this.locX, this.locY, this.locZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, new int[] { Block.getCombinedId(iblockdata)});
++ } else {
++ ((WorldServer) this.world).a(EnumParticle.BLOCK_DUST, this.locX, this.locY, this.locZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, new int[] { Block.getCombinedId(iblockdata)});
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -174,7 +195,11 @@
+ this.mount((Entity) null);
+ }
+ } else {
+- this.setAirTicks(300);
++ // CraftBukkit start - Only set if needed to work around a DataWatcher inefficiency
++ if (this.getAirTicks() != 300) {
++ this.setAirTicks(maxAirTicks);
++ }
++ // CraftBukkit end
+ }
+
+ if (this.isAlive() && this.U()) {
+@@ -220,6 +245,18 @@
+ this.lastPitch = this.pitch;
+ this.world.methodProfiler.b();
+ }
++
++ // CraftBukkit start
++ public int getExpReward() {
++ int exp = this.getExpValue(this.killer);
++
++ if (!this.world.isStatic && (this.lastDamageByPlayerTime > 0 || this.alwaysGivesExp()) && this.aZ() && this.world.getGameRules().getBoolean("doMobLoot")) {
++ return exp;
++ } else {
++ return 0;
++ }
++ }
++ // CraftBukkit end
+
+ public boolean isBaby() {
+ return false;
+@@ -227,19 +264,18 @@
+
+ protected void aY() {
+ ++this.deathTicks;
+- if (this.deathTicks == 20) {
++ if (this.deathTicks >= 20 && !this.dead) { // CraftBukkit - (this.deathTicks == 20) -> (this.deathTicks >= 20 && !this.dead)
+ int i;
+
+- if (!this.world.isStatic && (this.lastDamageByPlayerTime > 0 || this.alwaysGivesExp()) && this.aZ() && this.world.getGameRules().getBoolean("doMobLoot")) {
+- i = this.getExpValue(this.killer);
+-
+- while (i > 0) {
+- int j = EntityExperienceOrb.getOrbValue(i);
+-
+- i -= j;
+- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX, this.locY, this.locZ, j));
+- }
++ // CraftBukkit start - Update getExpReward() above if the removed if() changes!
++ i = this.expToDrop;
++ while (i > 0) {
++ int j = EntityExperienceOrb.getOrbValue(i);
++ i -= j;
++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX, this.locY, this.locZ, j));
+ }
++ this.expToDrop = 0;
++ // CraftBukkit end
+
+ this.die();
+
+@@ -375,6 +411,17 @@
+ }
+ }
+ }
++
++ // CraftBukkit start
++ if (nbttagcompound.hasKey("Bukkit.MaxHealth")) {
++ NBTBase nbtbase = nbttagcompound.get("Bukkit.MaxHealth");
++ if (nbtbase.getTypeId() == 5) {
++ this.getAttributeInstance(GenericAttributes.maxHealth).setValue((double) ((NBTTagFloat) nbtbase).c());
++ } else if (nbtbase.getTypeId() == 3) {
++ this.getAttributeInstance(GenericAttributes.maxHealth).setValue((double) ((NBTTagInt) nbtbase).d());
++ }
++ }
++ // CraftBukkit end
+
+ if (nbttagcompound.hasKeyOfType("HealF", 99)) {
+ this.setHealth(nbttagcompound.getFloat("HealF"));
+@@ -486,7 +533,8 @@
+ }
+
+ public boolean hasEffect(int i) {
+- return this.effects.containsKey(Integer.valueOf(i));
++ // CraftBukkit - Add size check for efficiency
++ return this.effects.size() != 0 && this.effects.containsKey(Integer.valueOf(i));
+ }
+
+ public boolean hasEffect(MobEffectList mobeffectlist) {
+@@ -560,20 +608,52 @@
+
+ }
+
++ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
+ public void heal(float f) {
++ heal(f, EntityRegainHealthEvent.RegainReason.CUSTOM);
++ }
++
++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) {
+ float f1 = this.getHealth();
+
+ if (f1 > 0.0F) {
+- this.setHealth(f1 + f);
++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setHealth((float) (this.getHealth() + event.getAmount()));
++ }
++ // CraftBukkit end
+ }
+
+ }
+
+ public final float getHealth() {
++ // CraftBukkit start - Use unscaled health
++ if (this instanceof EntityPlayer) {
++ return (float) ((EntityPlayer) this).getBukkitEntity().getHealth();
++ }
++ // CraftBukkit end
+ return this.datawatcher.getFloat(6);
+ }
+
+ public void setHealth(float f) {
++ // CraftBukkit start - Handle scaled health
++ if (this instanceof EntityPlayer) {
++ org.bukkit.craftbukkit.entity.CraftPlayer player = ((EntityPlayer) this).getBukkitEntity();
++ // Squeeze
++ if (f < 0.0F) {
++ player.setRealHealth(0.0D);
++ } else if (f > player.getMaxHealth()) {
++ player.setRealHealth(player.getMaxHealth());
++ } else {
++ player.setRealHealth(f);
++ }
++
++ this.datawatcher.watch(6, Float.valueOf(player.getScaledHealth()));
++ return;
++ }
++ // CraftBukkit end
+ this.datawatcher.watch(6, Float.valueOf(MathHelper.a(f, 0.0F, this.getMaxHealth())));
+ }
+
+@@ -589,7 +669,8 @@
+ } else if (damagesource.o() && this.hasEffect(MobEffectList.FIRE_RESISTANCE)) {
+ return false;
+ } else {
+- if ((damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && this.getEquipment(4) != null) {
++ // CraftBukkit - Moved into d(DamageSource, float)
++ if (false && (damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && this.getEquipment(4) != null) {
+ this.getEquipment(4).damage((int) (f * 4.0F + this.random.nextFloat() * f * 2.0F), this);
+ f *= 0.75F;
+ }
+@@ -602,13 +683,22 @@
+ return false;
+ }
+
+- this.d(damagesource, f - this.lastDamage);
++ // CraftBukkit start
++ if (!this.d(damagesource, f - this.lastDamage)) {
++ return false;
++ }
++ // CraftBukkit end
+ this.lastDamage = f;
+ flag = false;
+ } else {
++ // CraftBukkit start
++ float previousHealth = this.getHealth();
++ if (!this.d(damagesource, f)) {
++ return false;
++ }
+ this.lastDamage = f;
+ this.noDamageTicks = this.maxNoDamageTicks;
+- this.d(damagesource, f);
++ // CraftBukkit end
+ this.hurtTicks = this.at = 10;
+ }
+
+@@ -717,11 +807,19 @@
+ }
+
+ if (this.aZ() && this.world.getGameRules().getBoolean("doMobLoot")) {
++ this.drops = new ArrayList<org.bukkit.inventory.ItemStack>(); // CraftBukkit - Setup drop capture
++
+ this.dropDeathLoot(this.lastDamageByPlayerTime > 0, i);
+ this.dropEquipment(this.lastDamageByPlayerTime > 0, i);
+ if (this.lastDamageByPlayerTime > 0 && this.random.nextFloat() < 0.025F + (float) i * 0.01F) {
+ this.getRareDrop();
+- }
++ }
++ // CraftBukkit start - Call death event
++ CraftEventFactory.callEntityDeathEvent(this, this.drops);
++ this.drops = null;
++ } else {
++ CraftEventFactory.callEntityDeathEvent(this);
++ // CraftBukkit end
+ }
+ }
+
+@@ -781,8 +879,13 @@
+ int i = MathHelper.f((f - 3.0F - f2) * f1);
+
+ if (i > 0) {
++ // CraftBukkit start
++ if (!this.damageEntity(DamageSource.FALL, (float) i)) {
++ return;
++ }
++ // CraftBukkit end
+ this.makeSound(this.n(i), 1.0F, 1.0F);
+- this.damageEntity(DamageSource.FALL, (float) i);
++ // this.damageEntity(DamageSource.FALL, (float) i); // CraftBukkit - moved up
+ int j = MathHelper.floor(this.locX);
+ int k = MathHelper.floor(this.locY - 0.20000000298023224D);
+ int l = MathHelper.floor(this.locZ);
+@@ -826,7 +929,7 @@
+ int i = 25 - this.bq();
+ float f1 = f * (float) i;
+
+- this.damageArmor(f);
++ // this.damageArmor(f); // CraftBukkit - Moved into d(DamageSource, float)
+ f = f1 / 25.0F;
+ }
+
+@@ -840,8 +943,9 @@
+ int i;
+ int j;
+ float f1;
+-
+- if (this.hasEffect(MobEffectList.RESISTANCE) && damagesource != DamageSource.OUT_OF_WORLD) {
++
++ // CraftBukkit - Moved to d(DamageSource, float)
++ if (false && this.hasEffect(MobEffectList.RESISTANCE) && damagesource != DamageSource.OUT_OF_WORLD) {
+ i = (this.getEffect(MobEffectList.RESISTANCE).getAmplifier() + 1) * 5;
+ j = 25 - i;
+ f1 = f * (float) j;
+@@ -867,22 +971,117 @@
+ }
+ }
+
+- protected void d(DamageSource damagesource, float f) {
++ // CraftBukkit start
++ protected boolean d(final DamageSource damagesource, float f) { // void -> boolean, add final
+ if (!this.isInvulnerable(damagesource)) {
+- f = this.applyArmorModifier(damagesource, f);
+- f = this.applyMagicModifier(damagesource, f);
+- float f1 = f;
++ final boolean human = this instanceof EntityHuman;
++ float originalDamage = f;
++ Function<Double, Double> hardHat = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ if ((damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && EntityLiving.this.getEquipment(4) != null) {
++ return -(f - (f * 0.75F));
++ }
++ return -0.0;
++ }
++ };
++ float hardHatModifier = hardHat.apply((double) f).floatValue();
++ f += hardHatModifier;
++
++ Function<Double, Double> blocking = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ if (human) {
++ if (!damagesource.ignoresArmor() && ((EntityHuman) EntityLiving.this).isBlocking() && f > 0.0F) {
++ return -(f - ((1.0F + f) * 0.5F));
++ }
++ }
++ return -0.0;
++ }
++ };
++ float blockingModifier = blocking.apply((double) f).floatValue();
++ f += blockingModifier;
++
++ Function<Double, Double> armor = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(f - EntityLiving.this.applyArmorModifier(damagesource, f.floatValue()));
++ }
++ };
++ float armorModifier = armor.apply((double) f).floatValue();
++ f += armorModifier;
++
++ Function<Double, Double> resistance = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ if (!damagesource.isStarvation() && EntityLiving.this.hasEffect(MobEffectList.RESISTANCE) && damagesource != DamageSource.OUT_OF_WORLD) {
++ int i = (EntityLiving.this.getEffect(MobEffectList.RESISTANCE).getAmplifier() + 1) * 5;
++ int j = 25 - i;
++ float f1 = f.floatValue() * (float) j;
++ return -(f - (f1 / 25.0F));
++ }
++ return -0.0;
++ }
++ };
++ float resistanceModifier = resistance.apply((double) f).floatValue();
++ f += resistanceModifier;
++
++ Function<Double, Double> magic = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(f - EntityLiving.this.applyMagicModifier(damagesource, f.floatValue()));
++ }
++ };
++ float magicModifier = magic.apply((double) f).floatValue();
++ f += magicModifier;
++
++ Function<Double, Double> absorption = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(Math.max(f - Math.max(f - EntityLiving.this.getAbsorptionHearts(), 0.0F), 0.0F));
++ }
++ };
++ float absorptionModifier = absorption.apply((double) f).floatValue();
++
++ EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption);
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ f = (float) event.getFinalDamage();
+
+- f = Math.max(f - this.getAbsorptionHearts(), 0.0F);
+- this.setAbsorptionHearts(this.getAbsorptionHearts() - (f1 - f));
++ // Apply damage to helmet
++ if ((damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && this.getEquipment(4) != null) {
++ this.getEquipment(4).damage((int) (event.getDamage() * 4.0F + this.random.nextFloat() * event.getDamage() * 2.0F), this);
++ }
++
++ // Apply damage to armor
++ if (!damagesource.ignoresArmor()) {
++ float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
++ this.damageArmor(armorDamage);
++ }
++
++ absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION);
++ this.setAbsorptionHearts(Math.max(this.getAbsorptionHearts() - absorptionModifier, 0.0F));
+ if (f != 0.0F) {
++ if (human) {
++ ((EntityHuman) this).applyExhaustion(damagesource.getExhaustionCost());
++ }
++ // CraftBukkit end
+ float f2 = this.getHealth();
+
+ this.setHealth(f2 - f);
+ this.br().a(damagesource, f2, f);
++ // CraftBukkit start
++ if (human) {
++ return true;
++ }
++ // CraftBukkit end
+ this.setAbsorptionHearts(this.getAbsorptionHearts() - f);
+ }
++ return true; // CraftBukkit
+ }
++ return false; // CraftBukkit
+ }
+
+ public CombatTracker br() {
+@@ -1236,7 +1435,8 @@
+ if (f > 0.0025000002F) {
+ f3 = 1.0F;
+ f2 = (float) Math.sqrt((double) f) * 3.0F;
+- f1 = (float) Math.atan2(d1, d0) * 180.0F / 3.1415927F - 90.0F;
++ // CraftBukkit - Math -> TrigMath
++ f1 = (float) org.bukkit.craftbukkit.TrigMath.atan2(d1, d0) * 180.0F / 3.1415927F - 90.0F;
+ }
+
+ if (this.ax > 0.0F) {
+@@ -1400,6 +1600,13 @@
+ if (list != null && !list.isEmpty()) {
+ for (int i = 0; i < list.size(); ++i) {
+ Entity entity = (Entity) list.get(i);
++
++ // TODO better check now?
++ // CraftBukkit start - Only handle mob (non-player) collisions every other tick
++ if (entity instanceof EntityLiving && !(this instanceof EntityPlayer) && this.ticksLived % 2 == 0) {
++ continue;
++ }
++ // CraftBukkit end
+
+ if (entity.ae()) {
+ this.s(entity);
diff --git a/nms-patches/EntityMinecartAbstract.patch b/nms-patches/EntityMinecartAbstract.patch
new file mode 100644
index 00000000..c8ed3d56
--- /dev/null
+++ b/nms-patches/EntityMinecartAbstract.patch
@@ -0,0 +1,231 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityMinecartAbstract.java 2014-11-27 08:59:46.705421829 +1100
++++ src/main/java/net/minecraft/server/EntityMinecartAbstract.java 2014-11-27 08:42:10.136850942 +1100
+@@ -2,6 +2,15 @@
+
+ import java.util.Iterator;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.vehicle.VehicleDamageEvent;
++import org.bukkit.event.vehicle.VehicleDestroyEvent;
++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
++import org.bukkit.util.Vector;
++// CraftBukkit end
++
+ public abstract class EntityMinecartAbstract extends Entity implements INamableTileEntity {
+
+ private boolean a;
+@@ -13,6 +22,17 @@
+ private double g;
+ private double h;
+ private double i;
++
++ // CraftBukkit start
++ public boolean slowWhenEmpty = true;
++ private double derailedX = 0.5;
++ private double derailedY = 0.5;
++ private double derailedZ = 0.5;
++ private double flyingX = 0.95;
++ private double flyingY = 0.95;
++ private double flyingZ = 0.95;
++ public double maxSpeed = 0.4D;
++ // CraftBukkit end
+
+ public EntityMinecartAbstract(World world) {
+ super(world);
+@@ -79,6 +99,8 @@
+ this.lastX = d0;
+ this.lastY = d1;
+ this.lastZ = d2;
++
++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleCreateEvent((Vehicle) this.getBukkitEntity())); // CraftBukkit
+ }
+
+ public double an() {
+@@ -90,16 +112,39 @@
+ if (this.isInvulnerable(damagesource)) {
+ return false;
+ } else {
++ // CraftBukkit start - fire VehicleDamageEvent
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.entity.Entity passenger = (damagesource.getEntity() == null) ? null : damagesource.getEntity().getBukkitEntity();
++
++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, passenger, f);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return true;
++ }
++
++ f = (float) event.getDamage();
++ // CraftBukkit end
++
+ this.k(-this.r());
+ this.j(10);
+ this.ac();
+ this.setDamage(this.getDamage() + f * 10.0F);
+ boolean flag = damagesource.getEntity() instanceof EntityHuman && ((EntityHuman) damagesource.getEntity()).abilities.canInstantlyBuild;
+
+- if (flag || this.getDamage() > 40.0F) {
++ if (flag || this.getDamage() > 40.0F) { // CraftBukkit - multi-world should still allow teleport even if default vanilla nether disabled
+ if (this.passenger != null) {
+ this.passenger.mount((Entity) null);
+ }
++ // CraftBukkit start
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, passenger);
++ this.world.getServer().getPluginManager().callEvent(destroyEvent);
++
++ if (destroyEvent.isCancelled()) {
++ this.setDamage(40); // Maximize damage so this doesn't get triggered again right away
++ return true;
++ }
++ // CraftBukkit end
+
+ if (flag && !this.hasCustomName()) {
+ this.die();
+@@ -135,6 +180,14 @@
+ }
+
+ public void s_() {
++ // CraftBukkit start
++ double prevX = this.locX;
++ double prevY = this.locY;
++ double prevZ = this.locZ;
++ float prevYaw = this.yaw;
++ float prevPitch = this.pitch;
++ // CraftBukkit end
++
+ if (this.getType() > 0) {
+ this.j(this.getType() - 1);
+ }
+@@ -155,7 +208,7 @@
+
+ i = this.L();
+ if (this.ak) {
+- if (minecraftserver.getAllowNether()) {
++ if (true || minecraftserver.getAllowNether()) {
+ if (this.vehicle == null && this.al++ >= i) {
+ this.al = i;
+ this.portalCooldown = this.ar();
+@@ -252,6 +305,20 @@
+ }
+
+ this.setYawPitch(this.yaw, this.pitch);
++
++ // CraftBukkit start
++ org.bukkit.World bworld = this.world.getWorld();
++ Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
++ Location to = new Location(bworld, this.locX, this.locY, this.locZ, this.yaw, this.pitch);
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++
++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++
++ if (!from.equals(to)) {
++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to));
++ }
++ // CraftBukkit end
++
+ Iterator iterator = this.world.getEntities(this, this.getBoundingBox().grow(0.20000000298023224D, 0.0D, 0.20000000298023224D)).iterator();
+
+ while (iterator.hasNext()) {
+@@ -275,7 +342,7 @@
+ }
+
+ protected double m() {
+- return 0.4D;
++ return this.maxSpeed; // CraftBukkit
+ }
+
+ public void a(int i, int j, int k, boolean flag) {}
+@@ -286,16 +353,20 @@
+ this.motX = MathHelper.a(this.motX, -d0, d0);
+ this.motZ = MathHelper.a(this.motZ, -d0, d0);
+ if (this.onGround) {
+- this.motX *= 0.5D;
+- this.motY *= 0.5D;
+- this.motZ *= 0.5D;
++ // CraftBukkit start - replace magic numbers with our variables
++ this.motX *= this.derailedX;
++ this.motY *= this.derailedY;
++ this.motZ *= this.derailedZ;
++ // CraftBukkit end
+ }
+
+ this.move(this.motX, this.motY, this.motZ);
+ if (!this.onGround) {
+- this.motX *= 0.949999988079071D;
+- this.motY *= 0.949999988079071D;
+- this.motZ *= 0.949999988079071D;
++ // CraftBukkit start - replace magic numbers with our variables
++ this.motX *= this.flyingX;
++ this.motY *= this.flyingY;
++ this.motZ *= this.flyingZ;
++ // CraftBukkit end
+ }
+
+ }
+@@ -483,7 +554,7 @@
+ }
+
+ protected void o() {
+- if (this.passenger != null) {
++ if (this.passenger != null || !this.slowWhenEmpty) { // CraftBukkit - add !this.slowWhenEmpty
+ this.motX *= 0.996999979019165D;
+ this.motY *= 0.0D;
+ this.motZ *= 0.996999979019165D;
+@@ -611,6 +682,17 @@
+ if (!this.world.isStatic) {
+ if (!entity.T && !this.T) {
+ if (entity != this.passenger) {
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.entity.Entity hitEntity = (entity == null) ? null : entity.getBukkitEntity();
++
++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, hitEntity);
++ this.world.getServer().getPluginManager().callEvent(collisionEvent);
++
++ if (collisionEvent.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ if (entity instanceof EntityLiving && !(entity instanceof EntityHuman) && !(entity instanceof EntityIronGolem) && this.s() == EnumMinecartType.RIDEABLE && this.motX * this.motX + this.motZ * this.motZ > 0.01D && this.passenger == null && entity.vehicle == null) {
+ entity.mount(this);
+ }
+@@ -619,7 +701,8 @@
+ double d1 = entity.locZ - this.locZ;
+ double d2 = d0 * d0 + d1 * d1;
+
+- if (d2 >= 9.999999747378752E-5D) {
++ // CraftBukkit - collision
++ if (d2 >= 9.999999747378752E-5D && !collisionEvent.isCollisionCancelled()) {
+ d2 = (double) MathHelper.sqrt(d2);
+ d0 /= d2;
+ d1 /= d2;
+@@ -775,4 +858,26 @@
+ return chatmessage;
+ }
+ }
++
++ // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers
++ public Vector getFlyingVelocityMod() {
++ return new Vector(flyingX, flyingY, flyingZ);
++ }
++
++ public void setFlyingVelocityMod(Vector flying) {
++ flyingX = flying.getX();
++ flyingY = flying.getY();
++ flyingZ = flying.getZ();
++ }
++
++ public Vector getDerailedVelocityMod() {
++ return new Vector(derailedX, derailedY, derailedZ);
++ }
++
++ public void setDerailedVelocityMod(Vector derailed) {
++ derailedX = derailed.getX();
++ derailedY = derailed.getY();
++ derailedZ = derailed.getZ();
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/EntityMinecartCommandBlockListener.patch b/nms-patches/EntityMinecartCommandBlockListener.patch
new file mode 100644
index 00000000..ccfa4973
--- /dev/null
+++ b/nms-patches/EntityMinecartCommandBlockListener.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityMinecartCommandBlockListener.java 2014-11-27 08:59:46.709421812 +1100
++++ src/main/java/net/minecraft/server/EntityMinecartCommandBlockListener.java 2014-11-27 08:42:10.088851036 +1100
+@@ -6,6 +6,7 @@
+
+ EntityMinecartCommandBlockListener(EntityMinecartCommandBlock entityminecartcommandblock) {
+ this.a = entityminecartcommandblock;
++ this.sender = (org.bukkit.craftbukkit.entity.CraftMinecartCommand) entityminecartcommandblock.getBukkitEntity(); // CraftBukkit - Set the sender
+ }
+
+ public void h() {
diff --git a/nms-patches/EntityMinecartContainer.patch b/nms-patches/EntityMinecartContainer.patch
new file mode 100644
index 00000000..3a7c0c58
--- /dev/null
+++ b/nms-patches/EntityMinecartContainer.patch
@@ -0,0 +1,61 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityMinecartContainer.java 2014-11-27 08:59:46.709421812 +1100
++++ src/main/java/net/minecraft/server/EntityMinecartContainer.java 2014-11-27 08:42:10.120850973 +1100
+@@ -1,9 +1,48 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
++
+ public abstract class EntityMinecartContainer extends EntityMinecartAbstract implements ITileInventory {
+
+- private ItemStack[] items = new ItemStack[36];
++ private ItemStack[] items = new ItemStack[27]; // CraftBukkit - 36 -> 27
+ private boolean b = true;
++
++ // CraftBukkit start
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public InventoryHolder getOwner() {
++ org.bukkit.entity.Entity cart = getBukkitEntity();
++ if(cart instanceof InventoryHolder) return (InventoryHolder) cart;
++ return null;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public EntityMinecartContainer(World world) {
+ super(world);
+@@ -81,7 +120,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public void c(int i) {
diff --git a/nms-patches/EntityMonster.patch b/nms-patches/EntityMonster.patch
new file mode 100644
index 00000000..f5500fac
--- /dev/null
+++ b/nms-patches/EntityMonster.patch
@@ -0,0 +1,26 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityMonster.java 2014-11-27 08:59:46.709421812 +1100
++++ src/main/java/net/minecraft/server/EntityMonster.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.EntityCombustByEntityEvent; // CraftBukkit
++
+ public abstract class EntityMonster extends EntityCreature implements IMonster {
+
+ protected final PathfinderGoal a = new PathfinderGoalAvoidTarget(this, new EntitySelectorExplodingCreeper(this), 4.0F, 1.0D, 2.0D);
+@@ -81,7 +83,14 @@
+ int j = EnchantmentManager.getFireAspectEnchantmentLevel(this);
+
+ if (j > 0) {
+- entity.setOnFire(j * 4);
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), j * 4);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ entity.setOnFire(combustEvent.getDuration());
++ }
++ // CraftBukkit end
+ }
+
+ this.a((EntityLiving) this, entity);
diff --git a/nms-patches/EntityMushroomCow.patch b/nms-patches/EntityMushroomCow.patch
new file mode 100644
index 00000000..2d2507ed
--- /dev/null
+++ b/nms-patches/EntityMushroomCow.patch
@@ -0,0 +1,26 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityMushroomCow.java 2014-11-27 08:59:46.713421793 +1100
++++ src/main/java/net/minecraft/server/EntityMushroomCow.java 2014-11-27 08:42:10.084851043 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.player.PlayerShearEntityEvent; // CraftBukkit
++
+ public class EntityMushroomCow extends EntityCow {
+
+ public EntityMushroomCow(World world) {
+@@ -24,6 +26,15 @@
+ }
+
+ if (itemstack != null && itemstack.getItem() == Items.SHEARS && this.getAge() >= 0) {
++ // CraftBukkit start
++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), this.getBukkitEntity());
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++
+ this.die();
+ this.world.addParticle(EnumParticle.EXPLOSION_LARGE, this.locX, this.locY + (double) (this.length / 2.0F), this.locZ, 0.0D, 0.0D, 0.0D, new int[0]);
+ if (!this.world.isStatic) {
diff --git a/nms-patches/EntityOcelot.patch b/nms-patches/EntityOcelot.patch
new file mode 100644
index 00000000..1abf343c
--- /dev/null
+++ b/nms-patches/EntityOcelot.patch
@@ -0,0 +1,30 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityOcelot.java 2014-11-27 08:59:46.713421793 +1100
++++ src/main/java/net/minecraft/server/EntityOcelot.java 2014-11-27 08:42:10.160850895 +1100
+@@ -51,7 +51,7 @@
+ }
+
+ protected boolean isTypeNotPersistent() {
+- return !this.isTamed() && this.ticksLived > 2400;
++ return !this.isTamed() /*&& this.ticksLived > 2400*/; // CraftBukkit
+ }
+
+ protected void aW() {
+@@ -124,7 +124,8 @@
+ }
+
+ if (!this.world.isStatic) {
+- if (this.random.nextInt(3) == 0) {
++ // CraftBukkit - added event call and isCancelled check
++ if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, entityhuman).isCancelled()) {
+ this.setTamed(true);
+ this.setCatType(1 + this.world.random.nextInt(3));
+ this.setOwnerUUID(entityhuman.getUniqueID().toString());
+@@ -231,7 +232,7 @@
+
+ entityocelot.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
+ entityocelot.setAgeRaw(-24000);
+- this.world.addEntity(entityocelot);
++ this.world.addEntity(entityocelot, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OCELOT_BABY); // CraftBukkit - add SpawnReason
+ }
+ }
+
diff --git a/nms-patches/EntityPainting.patch b/nms-patches/EntityPainting.patch
new file mode 100644
index 00000000..7c698b4f
--- /dev/null
+++ b/nms-patches/EntityPainting.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityPainting.java 2014-11-27 08:59:46.717421776 +1100
++++ src/main/java/net/minecraft/server/EntityPainting.java 2014-11-27 08:42:10.132850949 +1100
+@@ -9,6 +9,7 @@
+
+ public EntityPainting(World world) {
+ super(world);
++ this.art = EnumArt.values()[this.random.nextInt(EnumArt.values().length)]; // CraftBukkit - generate a non-null painting
+ }
+
+ public EntityPainting(World world, BlockPosition blockposition, EnumDirection enumdirection) {
diff --git a/nms-patches/EntityPig.patch b/nms-patches/EntityPig.patch
new file mode 100644
index 00000000..064655a7
--- /dev/null
+++ b/nms-patches/EntityPig.patch
@@ -0,0 +1,29 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityPig.java 2014-11-27 08:59:46.717421776 +1100
++++ src/main/java/net/minecraft/server/EntityPig.java 2014-11-27 08:42:10.140850934 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class EntityPig extends EntityAnimal {
+
+ private final PathfinderGoalPassengerCarrotStick bk;
+@@ -111,10 +113,17 @@
+ public void onLightningStrike(EntityLightning entitylightning) {
+ if (!this.world.isStatic) {
+ EntityPigZombie entitypigzombie = new EntityPigZombie(this.world);
++
++ // CraftBukkit start
++ if (CraftEventFactory.callPigZapEvent(this, entitylightning, entitypigzombie).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+
+ entitypigzombie.setEquipment(0, new ItemStack(Items.GOLDEN_SWORD));
+ entitypigzombie.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, this.pitch);
+- this.world.addEntity(entitypigzombie);
++ // CraftBukkit - added a reason for spawning this creature
++ this.world.addEntity(entitypigzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING);
+ this.die();
+ }
+ }
diff --git a/nms-patches/EntityPlayer.patch b/nms-patches/EntityPlayer.patch
new file mode 100644
index 00000000..e63348a0
--- /dev/null
+++ b/nms-patches/EntityPlayer.patch
@@ -0,0 +1,542 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityPlayer.java 2014-11-27 08:59:46.721421758 +1100
++++ src/main/java/net/minecraft/server/EntityPlayer.java 2014-11-27 08:42:10.164850887 +1100
+@@ -13,6 +13,17 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.WeatherType;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.inventory.InventoryType;
++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
++// CraftBukkit end
++
+ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ private static final Logger bF = LogManager.getLogger();
+@@ -39,6 +50,18 @@
+ public boolean g;
+ public int ping;
+ public boolean viewingCredits;
++
++ // CraftBukkit start
++ public String displayName;
++ public IChatBaseComponent listName;
++ public org.bukkit.Location compassTarget;
++ public int newExp = 0;
++ public int newLevel = 0;
++ public int newTotalExp = 0;
++ public boolean keepLevel = false;
++ public double maxHealthCache;
++ public boolean joining = true;
++ // CraftBukkit end
+
+ public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
+ super(worldserver, gameprofile);
+@@ -69,7 +92,11 @@
+ while (!worldserver.getCubes(this, this.getBoundingBox()).isEmpty() && this.locY < 255.0D) {
+ this.setPosition(this.locX, this.locY + 1.0D, this.locZ);
+ }
+-
++ // CraftBukkit start
++ this.displayName = this.getName();
++ // this.canPickUpLoot = true; TODO
++ this.maxHealthCache = this.getMaxHealth();
++ // CraftBukkit end
+ }
+
+ public void a(NBTTagCompound nbttagcompound) {
+@@ -81,13 +108,39 @@
+ this.playerInteractManager.setGameMode(EnumGamemode.getById(nbttagcompound.getInt("playerGameType")));
+ }
+ }
+-
++ this.getBukkitEntity().readExtraData(nbttagcompound); // CraftBukkit
+ }
+
+ public void b(NBTTagCompound nbttagcompound) {
+ super.b(nbttagcompound);
+ nbttagcompound.setInt("playerGameType", this.playerInteractManager.getGameMode().getId());
++ this.getBukkitEntity().setExtraData(nbttagcompound); // CraftBukkit
++ }
++
++ // CraftBukkit start - World fallback code, either respawn location or global spawn
++ public void spawnIn(World world) {
++ super.spawnIn(world);
++ if (world == null) {
++ this.dead = false;
++ BlockPosition position = null;
++ if (this.spawnWorld != null && !this.spawnWorld.equals("")) {
++ CraftWorld cworld = (CraftWorld) Bukkit.getServer().getWorld(this.spawnWorld);
++ if (cworld != null && this.getBed() != null) {
++ world = cworld.getHandle();
++ position = EntityHuman.getBed(cworld.getHandle(), this.getBed(), false);
++ }
++ }
++ if (world == null || position == null) {
++ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
++ position = world.getSpawn();
++ }
++ this.world = world;
++ this.setPosition(position.getX() + 0.5, position.getY(), position.getZ() + 0.5);
++ }
++ this.dimension = ((WorldServer) this.world).dimension;
++ this.playerInteractManager.a((WorldServer) world);
+ }
++ // CraftBukkit end
+
+ public void levelDown(int i) {
+ super.levelDown(i);
+@@ -114,6 +167,11 @@
+ }
+
+ public void s_() {
++ // CraftBukkit start
++ if (this.joining) {
++ this.joining = false;
++ }
++ // CraftBukkit end
+ this.playerInteractManager.a();
+ --this.invulnerableTicks;
+ if (this.noDamageTicks > 0) {
+@@ -155,7 +213,7 @@
+ chunk = this.world.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z);
+ if (chunk.isReady()) {
+ arraylist.add(chunk);
+- arraylist1.addAll(((WorldServer) this.world).getTileEntities(chunkcoordintpair.x * 16, 0, chunkcoordintpair.z * 16, chunkcoordintpair.x * 16 + 16, 256, chunkcoordintpair.z * 16 + 16));
++ arraylist1.addAll(chunk.tileEntities.values()); // CraftBukkit - Get tile entities directly from the chunk instead of the world
+ iterator1.remove();
+ }
+ }
+@@ -220,8 +278,9 @@
+ }
+ }
+
++ // CraftBukkit - Optionally scale health
+ if (this.getHealth() != this.bK || this.bL != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.bM) {
+- this.playerConnection.sendPacket(new PacketPlayOutUpdateHealth(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
++ this.playerConnection.sendPacket(new PacketPlayOutUpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
+ this.bK = this.getHealth();
+ this.bL = this.foodData.getFoodLevel();
+ this.bM = this.foodData.getSaturationLevel() == 0.0F;
+@@ -229,15 +288,14 @@
+
+ if (this.getHealth() + this.getAbsorptionHearts() != this.bJ) {
+ this.bJ = this.getHealth() + this.getAbsorptionHearts();
+- Collection collection = this.getScoreboard().getObjectivesForCriteria(IScoreboardCriteria.g);
+- Iterator iterator = collection.iterator();
+-
+- while (iterator.hasNext()) {
+- ScoreboardObjective scoreboardobjective = (ScoreboardObjective) iterator.next();
+-
+- this.getScoreboard().getPlayerScoreForObjective(this.getName(), scoreboardobjective).updateForList(Arrays.asList(new EntityHuman[] { this}));
+- }
++ // CraftBukkit - Update ALL the scores!
++ this.world.getServer().getScoreboardManager().updateAllScoresForList(IScoreboardCriteria.f, this.getName(), com.google.common.collect.ImmutableList.of(this));
++ }
++ // CraftBukkit start - Force max health updates
++ if (this.maxHealthCache != this.getMaxHealth()) {
++ this.getBukkitEntity().updateScaledHealth();
+ }
++ // CraftBukkit end
+
+ if (this.expTotal != this.lastSentExp) {
+ this.lastSentExp = this.expTotal;
+@@ -247,7 +305,17 @@
+ if (this.ticksLived % 20 * 5 == 0 && !this.getStatisticManager().hasAchievement(AchievementList.L)) {
+ this.h_();
+ }
++
++ // CraftBukkit start - initialize oldLevel and fire PlayerLevelChangeEvent
++ if (this.oldLevel == -1) {
++ this.oldLevel = this.expLevel;
++ }
+
++ if (this.oldLevel != this.expLevel) {
++ CraftEventFactory.callPlayerLevelChangeEvent(this.world.getServer().getPlayer((EntityPlayer) this), this.oldLevel, this.expLevel);
++ this.oldLevel = this.expLevel;
++ }
++ // CraftBukkit end
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.a(throwable, "Ticking player");
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Player being ticked");
+@@ -296,30 +364,64 @@
+ }
+
+ public void die(DamageSource damagesource) {
+- if (this.world.getGameRules().getBoolean("showDeathMessages")) {
+- ScoreboardTeamBase scoreboardteambase = this.getScoreboardTeam();
++ // CraftBukkit start - fire PlayerDeathEvent
++ if (this.dead) {
++ return;
++ }
++
++ java.util.List<org.bukkit.inventory.ItemStack> loot = new java.util.ArrayList<org.bukkit.inventory.ItemStack>();
++ boolean keepInventory = this.world.getGameRules().getBoolean("keepInventory");
++
++ if (!keepInventory) {
++ for (int i = 0; i < this.inventory.items.length; ++i) {
++ if (this.inventory.items[i] != null) {
++ loot.add(CraftItemStack.asCraftMirror(this.inventory.items[i]));
++ }
++ }
+
+- if (scoreboardteambase != null && scoreboardteambase.j() != EnumNameTagVisibility.ALWAYS) {
+- if (scoreboardteambase.j() == EnumNameTagVisibility.HIDE_FOR_OTHER_TEAMS) {
+- this.server.getPlayerList().a((EntityHuman) this, this.br().b());
+- } else if (scoreboardteambase.j() == EnumNameTagVisibility.HIDE_FOR_OWN_TEAM) {
+- this.server.getPlayerList().b((EntityHuman) this, this.br().b());
++ for (int i = 0; i < this.inventory.armor.length; ++i) {
++ if (this.inventory.armor[i] != null) {
++ loot.add(CraftItemStack.asCraftMirror(this.inventory.armor[i]));
+ }
++ }
++ }
++
++ IChatBaseComponent chatmessage = this.br().b();
++
++ String deathmessage = chatmessage.c();
++ org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory);
++
++ String deathMessage = event.getDeathMessage();
++
++ if (deathMessage != null && deathMessage.length() > 0 && this.world.getGameRules().getBoolean("showDeathMessages")) { // TODO: allow plugins to override?
++ if (deathMessage.equals(deathmessage)) {
++ this.server.getPlayerList().sendMessage(chatmessage);
+ } else {
+- this.server.getPlayerList().sendMessage(this.br().b());
++ this.server.getPlayerList().sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(deathMessage));
+ }
+ }
++
++ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory.
++ if (!event.getKeepInventory()) {
++ for (int i = 0; i < this.inventory.items.length; ++i) {
++ this.inventory.items[i] = null;
++ }
+
+- if (!this.world.getGameRules().getBoolean("keepInventory")) {
+- this.inventory.n();
++ for (int i = 0; i < this.inventory.armor.length; ++i) {
++ this.inventory.armor[i] = null;
++ }
+ }
+
+- Collection collection = this.world.getScoreboard().getObjectivesForCriteria(IScoreboardCriteria.d);
++ this.closeInventory();
++ this.e((Entity) this); // Remove spectated target
++ // CraftBukkit end
++
++ // CraftBukkit - Get our scores instead
++ Collection collection = this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreboardCriteria.c, this.getName(), new java.util.ArrayList<ScoreboardScore>());
+ Iterator iterator = collection.iterator();
+
+ while (iterator.hasNext()) {
+- ScoreboardObjective scoreboardobjective = (ScoreboardObjective) iterator.next();
+- ScoreboardScore scoreboardscore = this.getScoreboard().getPlayerScoreForObjective(this.getName(), scoreboardobjective);
++ ScoreboardScore scoreboardscore = (ScoreboardScore) iterator.next(); // CraftBukkit - Use our scores instead
+
+ scoreboardscore.incrementScore();
+ }
+@@ -376,7 +478,8 @@
+ }
+
+ private boolean cq() {
+- return this.server.getPVP();
++ // CraftBukkit - this.server.getPvP() -> this.world.pvpMode
++ return this.world.pvpMode;
+ }
+
+ public void c(int i) {
+@@ -388,6 +491,8 @@
+ } else {
+ if (this.dimension == 0 && i == 1) {
+ this.b((Statistic) AchievementList.C);
++ // CraftBukkit start - Rely on custom portal management
++ /*
+ BlockPosition blockposition = this.server.getWorldServer(i).getDimensionSpawn();
+
+ if (blockposition != null) {
+@@ -395,11 +500,16 @@
+ }
+
+ i = 1;
++ */
++ // CraftBukkit end
+ } else {
+ this.b((Statistic) AchievementList.y);
+ }
+
+- this.server.getPlayerList().changeDimension(this, i);
++ // CraftBukkit start
++ TeleportCause cause = (this.dimension == 1 || i == 1) ? TeleportCause.END_PORTAL : TeleportCause.NETHER_PORTAL;
++ this.server.getPlayerList().changeDimension(this, i, cause);
++ // CraftBukkit end
+ this.lastSentExp = -1;
+ this.bK = -1.0F;
+ this.bL = -1;
+@@ -442,6 +552,8 @@
+ }
+
+ public void a(boolean flag, boolean flag1, boolean flag2) {
++ if (!this.sleeping) return; // CraftBukkit - Can't leave bed if not in one!
++
+ if (this.isSleeping()) {
+ this.u().getTracker().sendPacketToEntity(this, new PacketPlayOutAnimation(this, 2));
+ }
+@@ -454,14 +566,23 @@
+ }
+
+ public void mount(Entity entity) {
+- Entity entity1 = this.vehicle;
++ // CraftBukkit start
++ this.setPassengerOf(entity);
++ }
++
++ public void setPassengerOf(Entity entity) {
++ // mount(null) doesn't really fly for overloaded methods,
++ // so this method is needed
++ Entity currentVehicle = this.vehicle;
++
++ super.setPassengerOf(entity);
+
+- super.mount(entity);
+- if (entity != entity1) {
++ // Check if the vehicle actually changed.
++ if (currentVehicle != this.vehicle) {
+ this.playerConnection.sendPacket(new PacketPlayOutAttachEntity(0, this, this.vehicle));
+ this.playerConnection.a(this.locX, this.locY, this.locZ, this.yaw, this.pitch);
+ }
+-
++ // CraftBukkit end
+ }
+
+ protected void a(double d0, boolean flag, Block block, BlockPosition blockposition) {}
+@@ -490,19 +611,38 @@
+ this.playerConnection.sendPacket(new PacketPlayOutOpenSignEditor(tileentitysign.getPosition()));
+ }
+
+- public void nextContainerCounter() {
++ public int nextContainerCounter() { // CraftBukkit - private void -> public int
+ this.containerCounter = this.containerCounter % 100 + 1;
++ return containerCounter; // CraftBukkit
+ }
+
+ public void openTileEntity(ITileEntityContainer itileentitycontainer) {
++ // CraftBukkit start - Inventory open hook
++ Container container = CraftEventFactory.callInventoryOpenEvent(this, itileentitycontainer.createContainer(this.inventory, this));
++ if (container == null) {
++ return;
++ }
++ // CraftBukkit end
+ this.nextContainerCounter();
+ this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.containerCounter, itileentitycontainer.getContainerName(), itileentitycontainer.getScoreboardDisplayName()));
+- this.activeContainer = itileentitycontainer.createContainer(this.inventory, this);
++ this.activeContainer = container; // CraftBukkit
+ this.activeContainer.windowId = this.containerCounter;
+ this.activeContainer.addSlotListener(this);
+ }
+
+ public void openContainer(IInventory iinventory) {
++ // CraftBukkit start - Inventory open hook
++ Container container;
++ if (iinventory instanceof ITileEntityContainer) {
++ container = ((ITileEntityContainer)iinventory).createContainer(this.inventory, this);
++ } else {
++ container = new ContainerChest(this.inventory, iinventory, this);
++ }
++ container = CraftEventFactory.callInventoryOpenEvent(this, container);
++ if (container == null) {
++ return;
++ }
++ // CraftBukkit end
+ if (this.activeContainer != this.defaultContainer) {
+ this.closeInventory();
+ }
+@@ -520,10 +660,10 @@
+ this.nextContainerCounter();
+ if (iinventory instanceof ITileEntityContainer) {
+ this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.containerCounter, ((ITileEntityContainer) iinventory).getContainerName(), iinventory.getScoreboardDisplayName(), iinventory.getSize()));
+- this.activeContainer = ((ITileEntityContainer) iinventory).createContainer(this.inventory, this);
++ this.activeContainer = container; // CraftBukkit
+ } else {
+ this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.containerCounter, "minecraft:container", iinventory.getScoreboardDisplayName(), iinventory.getSize()));
+- this.activeContainer = new ContainerChest(this.inventory, iinventory, this);
++ this.activeContainer = container; // CraftBukkit
+ }
+
+ this.activeContainer.windowId = this.containerCounter;
+@@ -531,8 +671,14 @@
+ }
+
+ public void openTrade(IMerchant imerchant) {
++ // CraftBukkit start - Inventory open hook
++ Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerMerchant(this.inventory, imerchant, this.world));
++ if (container == null) {
++ return;
++ }
++ // CraftBukkit end
+ this.nextContainerCounter();
+- this.activeContainer = new ContainerMerchant(this.inventory, imerchant, this.world);
++ this.activeContainer = container; // CraftBukkit
+ this.activeContainer.windowId = this.containerCounter;
+ this.activeContainer.addSlotListener(this);
+ InventoryMerchant inventorymerchant = ((ContainerMerchant) this.activeContainer).e();
+@@ -552,13 +698,20 @@
+ }
+
+ public void openHorseInventory(EntityHorse entityhorse, IInventory iinventory) {
++ // CraftBukkit start - Inventory open hook
++ Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerHorse(this.inventory, iinventory, entityhorse, this));
++ if (container == null) {
++ iinventory.closeContainer(this);
++ return;
++ }
++ // CraftBukkit end
+ if (this.activeContainer != this.defaultContainer) {
+ this.closeInventory();
+ }
+
+ this.nextContainerCounter();
+ this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.containerCounter, "EntityHorse", iinventory.getScoreboardDisplayName(), iinventory.getSize(), entityhorse.getId()));
+- this.activeContainer = new ContainerHorse(this.inventory, iinventory, entityhorse, this);
++ this.activeContainer = container;
+ this.activeContainer.windowId = this.containerCounter;
+ this.activeContainer.addSlotListener(this);
+ }
+@@ -587,6 +740,11 @@
+ public void a(Container container, List list) {
+ this.playerConnection.sendPacket(new PacketPlayOutWindowItems(container.windowId, list));
+ this.playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, this.inventory.getCarried()));
++ // CraftBukkit start - Send a Set Slot to update the crafting result slot
++ if (java.util.EnumSet.of(InventoryType.CRAFTING,InventoryType.WORKBENCH).contains(container.getBukkitView().getType())) {
++ this.playerConnection.sendPacket(new PacketPlayOutSetSlot(container.windowId, 0, container.getSlot(0).getItem()));
++ }
++ // CraftBukkit end
+ }
+
+ public void setContainerData(Container container, int i, int j) {
+@@ -601,6 +759,7 @@
+ }
+
+ public void closeInventory() {
++ CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit
+ this.playerConnection.sendPacket(new PacketPlayOutCloseWindow(this.activeContainer.windowId));
+ this.p();
+ }
+@@ -681,7 +840,16 @@
+
+ public void triggerHealthUpdate() {
+ this.bK = -1.0E8F;
++ this.lastSentExp = -1; // CraftBukkit - Added to reset
++ }
++
++ // CraftBukkit start - Support multi-line messages
++ public void sendMessage(IChatBaseComponent[] ichatbasecomponent) {
++ for (IChatBaseComponent component : ichatbasecomponent) {
++ this.sendMessage(component);
++ }
+ }
++ // CraftBukkit end
+
+ public void b(IChatBaseComponent ichatbasecomponent) {
+ this.playerConnection.sendPacket(new PacketPlayOutChat(ichatbasecomponent));
+@@ -867,6 +1035,93 @@
+ }
+
+ public IChatBaseComponent getPlayerListName() {
+- return null;
++ return listName; // CraftBukkit
++ }
++
++ // CraftBukkit start - Add per-player time and weather.
++ public long timeOffset = 0;
++ public boolean relativeTime = true;
++
++ public long getPlayerTime() {
++ if (this.relativeTime) {
++ // Adds timeOffset to the current server time.
++ return this.world.getDayTime() + this.timeOffset;
++ } else {
++ // Adds timeOffset to the beginning of this day.
++ return this.world.getDayTime() - (this.world.getDayTime() % 24000) + this.timeOffset;
++ }
++ }
++
++ public WeatherType weather = null;
++
++ public WeatherType getPlayerWeather() {
++ return this.weather;
++ }
++
++ public void setPlayerWeather(WeatherType type, boolean plugin) {
++ if (!plugin && this.weather != null) {
++ return;
++ }
++
++ if (plugin) {
++ this.weather = type;
++ }
++
++ if (type == WeatherType.DOWNFALL) {
++ this.playerConnection.sendPacket(new PacketPlayOutGameStateChange(2, 0));
++ // this.playerConnection.sendPacket(new PacketPlayOutGameStateChange(7, this.world.j(1.0F)));
++ // this.playerConnection.sendPacket(new PacketPlayOutGameStateChange(8, this.world.h(1.0F)));
++ } else {
++ this.playerConnection.sendPacket(new PacketPlayOutGameStateChange(1, 0));
++ }
++ }
++
++ public void resetPlayerWeather() {
++ this.weather = null;
++ this.setPlayerWeather(this.world.getWorldData().hasStorm() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false);
++ }
++
++ @Override
++ public String toString() {
++ return super.toString() + "(" + this.getName() + " at " + this.locX + "," + this.locY + "," + this.locZ + ")";
++ }
++
++ public void reset() {
++ float exp = 0;
++ boolean keepInventory = this.world.getGameRules().getBoolean("keepInventory");
++
++ if (this.keepLevel || keepInventory) {
++ exp = this.exp;
++ this.newTotalExp = this.expTotal;
++ this.newLevel = this.expLevel;
++ }
++
++ this.setHealth(this.getMaxHealth());
++ this.fireTicks = 0;
++ this.fallDistance = 0;
++ this.foodData = new FoodMetaData(this);
++ this.expLevel = this.newLevel;
++ this.expTotal = this.newTotalExp;
++ this.exp = 0;
++ this.deathTicks = 0;
++ this.removeAllEffects();
++ this.updateEffects = true;
++ this.activeContainer = this.defaultContainer;
++ this.killer = null;
++ this.lastDamager = null;
++ this.combatTracker = new CombatTracker(this);
++ this.lastSentExp = -1;
++ if (this.keepLevel || keepInventory) {
++ this.exp = exp;
++ } else {
++ this.giveExp(this.newExp);
++ }
++ this.keepLevel = false;
++ }
++
++ @Override
++ public CraftPlayer getBukkitEntity() {
++ return (CraftPlayer) super.getBukkitEntity();
+ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/EntityPotion.patch b/nms-patches/EntityPotion.patch
new file mode 100644
index 00000000..fd9e7d33
--- /dev/null
+++ b/nms-patches/EntityPotion.patch
@@ -0,0 +1,70 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityPotion.java 2014-11-27 08:59:46.721421758 +1100
++++ src/main/java/net/minecraft/server/EntityPotion.java 2014-11-27 08:42:10.112850989 +1100
+@@ -3,6 +3,13 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import java.util.HashMap;
++
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.entity.LivingEntity;
++// CraftBukkit end
++
+ public class EntityPotion extends EntityProjectile {
+
+ public ItemStack item;
+@@ -57,12 +64,15 @@
+ if (!this.world.isStatic) {
+ List list = Items.POTION.h(this.item);
+
+- if (list != null && !list.isEmpty()) {
++ if (true || list != null && !list.isEmpty()) { // CraftBukkit - Call event even if no effects to apply
+ AxisAlignedBB axisalignedbb = this.getBoundingBox().grow(4.0D, 2.0D, 4.0D);
+ List list1 = this.world.a(EntityLiving.class, axisalignedbb);
+
+- if (!list1.isEmpty()) {
++ if (true || !list1.isEmpty()) { // CraftBukkit - Run code even if there are no entities around
+ Iterator iterator = list1.iterator();
++
++ // CraftBukkit
++ HashMap<LivingEntity, Double> affected = new HashMap<LivingEntity, Double>();
+
+ while (iterator.hasNext()) {
+ EntityLiving entityliving = (EntityLiving) iterator.next();
+@@ -74,12 +84,35 @@
+ if (entityliving == movingobjectposition.entity) {
+ d1 = 1.0D;
+ }
++
++ // CraftBukkit start
++ affected.put((LivingEntity) entityliving.getBukkitEntity(), d1);
++ }
++ }
++
++ org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, affected);
++ if (!event.isCancelled() && list != null && !list.isEmpty()) { // do not process effects if there are no effects to process
++ for (LivingEntity victim : event.getAffectedEntities()) {
++ if (!(victim instanceof CraftLivingEntity)) {
++ continue;
++ }
++
++ EntityLiving entityliving = ((CraftLivingEntity) victim).getHandle();
++ double d1 = event.getIntensity(victim);
++ // CraftBukkit end
+
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+ MobEffect mobeffect = (MobEffect) iterator1.next();
+ int i = mobeffect.getEffectId();
++
++ // CraftBukkit start - Abide by PVP settings - for players only!
++ if (!this.world.pvpMode && this.getShooter() instanceof EntityPlayer && entityliving instanceof EntityPlayer && entityliving != this.getShooter()) {
++ // Block SLOWER_MOVEMENT, SLOWER_DIG, HARM, BLINDNESS, HUNGER, WEAKNESS and POISON potions
++ if (i == 2 || i == 4 || i == 7 || i == 15 || i == 17 || i == 18 || i == 19) continue;
++ }
++ // CraftBukkit end
+
+ if (MobEffectList.byId[i].isInstant()) {
+ MobEffectList.byId[i].applyInstantEffect(this, this.getShooter(), entityliving, mobeffect.getAmplifier(), d1);
diff --git a/nms-patches/EntityProjectile.patch b/nms-patches/EntityProjectile.patch
new file mode 100644
index 00000000..bd4789e6
--- /dev/null
+++ b/nms-patches/EntityProjectile.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityProjectile.java 2014-11-27 08:59:46.725421741 +1100
++++ src/main/java/net/minecraft/server/EntityProjectile.java 2014-11-27 08:42:10.140850934 +1100
+@@ -25,6 +25,7 @@
+ public EntityProjectile(World world, EntityLiving entityliving) {
+ super(world);
+ this.shooter = entityliving;
++ this.projectileSource = (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity(); // CraftBukkit
+ this.a(0.25F, 0.25F);
+ this.setPositionRotation(entityliving.locX, entityliving.locY + (double) entityliving.getHeadHeight(), entityliving.locZ, entityliving.yaw, entityliving.pitch);
+ this.locX -= (double) (MathHelper.cos(this.yaw / 180.0F * 3.1415927F) * 0.16F);
+@@ -130,7 +131,7 @@
+ MovingObjectPosition movingobjectposition1 = axisalignedbb.a(vec3d, vec3d1);
+
+ if (movingobjectposition1 != null) {
+- double d1 = vec3d.f(movingobjectposition1.pos);
++ double d1 = vec3d.distanceSquared(movingobjectposition1.pos); // CraftBukkit - distance efficiency
+
+ if (d1 < d0 || d0 == 0.0D) {
+ entity = entity1;
+@@ -150,6 +151,11 @@
+ this.aq();
+ } else {
+ this.a(movingobjectposition);
++ // CraftBukkit start
++ if (this.dead) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this);
++ }
++ // CraftBukkit end
+ }
+ }
+
diff --git a/nms-patches/EntitySheep.patch b/nms-patches/EntitySheep.patch
new file mode 100644
index 00000000..b1737592
--- /dev/null
+++ b/nms-patches/EntitySheep.patch
@@ -0,0 +1,54 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySheep.java 2014-11-27 08:59:46.725421741 +1100
++++ src/main/java/net/minecraft/server/EntitySheep.java 2014-11-27 08:42:10.124850965 +1100
+@@ -4,6 +4,11 @@
+ import java.util.Map;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.event.entity.SheepRegrowWoolEvent;
++import org.bukkit.event.player.PlayerShearEntityEvent;
++// CraftBukkit end
++
+ public class EntitySheep extends EntityAnimal {
+
+ private final InventoryCrafting bk = new InventoryCrafting(new ContainerSheepBreed(this), 2, 1);
+@@ -30,6 +35,7 @@
+ this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+ this.bk.setItem(0, new ItemStack(Items.DYE, 1, 0));
+ this.bk.setItem(1, new ItemStack(Items.DYE, 1, 0));
++ this.bk.resultInventory = new InventoryCraftResult(); // CraftBukkit - add result slot for event
+ }
+
+ protected void E() {
+@@ -82,6 +88,15 @@
+
+ if (itemstack != null && itemstack.getItem() == Items.SHEARS && !this.isSheared() && !this.isBaby()) {
+ if (!this.world.isStatic) {
++ // CraftBukkit start
++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), this.getBukkitEntity());
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++
+ this.setSheared(true);
+ int i = 1 + this.random.nextInt(3);
+
+@@ -169,7 +184,14 @@
+ }
+
+ public void v() {
+- this.setSheared(false);
++ // CraftBukkit start
++ SheepRegrowWoolEvent event = new SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity());
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setSheared(false);
++ }
++ // CraftBukkit end
+ if (this.isBaby()) {
+ this.setAge(60);
+ }
diff --git a/nms-patches/EntitySkeleton.patch b/nms-patches/EntitySkeleton.patch
new file mode 100644
index 00000000..fe603518
--- /dev/null
+++ b/nms-patches/EntitySkeleton.patch
@@ -0,0 +1,60 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySkeleton.java 2014-11-27 08:59:46.725421741 +1100
++++ src/main/java/net/minecraft/server/EntitySkeleton.java 2014-11-27 08:42:10.136850942 +1100
+@@ -2,6 +2,8 @@
+
+ import java.util.Calendar;
+
++import org.bukkit.event.entity.EntityCombustEvent; // CraftBukkit
++
+ public class EntitySkeleton extends EntityMonster implements IRangedEntity {
+
+ private PathfinderGoalArrowAttack b = new PathfinderGoalArrowAttack(this, 1.0D, 20, 60, 15.0F);
+@@ -90,7 +92,14 @@
+ }
+
+ if (flag) {
+- this.setOnFire(8);
++ // CraftBukkit start
++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), 8);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -225,11 +234,30 @@
+ }
+
+ if (EnchantmentManager.getEnchantmentLevel(Enchantment.ARROW_FIRE.id, this.bz()) > 0 || this.getSkeletonType() == 1) {
+- entityarrow.setOnFire(100);
++ // CraftBukkit start - call EntityCombustEvent
++ EntityCombustEvent event = new EntityCombustEvent(entityarrow.getBukkitEntity(), 100);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entityarrow.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
++ }
++
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.bz(), entityarrow, 0.8F);
++ if (event.isCancelled()) {
++ event.getProjectile().remove();
++ return;
++ }
++
++ if (event.getProjectile() == entityarrow.getBukkitEntity()) {
++ world.addEntity(entityarrow);
+ }
++ // CraftBukkit end
+
+ this.makeSound("random.bow", 1.0F, 1.0F / (this.bb().nextFloat() * 0.4F + 0.8F));
+- this.world.addEntity(entityarrow);
++ // this.world.addEntity(entityarrow); // CraftBukkit - moved up
+ }
+
+ public int getSkeletonType() {
diff --git a/nms-patches/EntitySlime.patch b/nms-patches/EntitySlime.patch
new file mode 100644
index 00000000..02d48970
--- /dev/null
+++ b/nms-patches/EntitySlime.patch
@@ -0,0 +1,40 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySlime.java 2014-11-27 08:59:46.729421723 +1100
++++ src/main/java/net/minecraft/server/EntitySlime.java 2014-11-27 08:42:10.100851012 +1100
+@@ -1,5 +1,9 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.event.entity.SlimeSplitEvent;
++// CraftBukkit end
++
+ public class EntitySlime extends EntityInsentient implements IMonster {
+
+ public float a;
+@@ -132,6 +136,18 @@
+
+ if (!this.world.isStatic && i > 1 && this.getHealth() <= 0.0F) {
+ int j = 2 + this.random.nextInt(3);
++
++ // CraftBukkit start
++ SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), j);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled() && event.getCount() > 0) {
++ j = event.getCount();
++ } else {
++ super.die();
++ return;
++ }
++ // CraftBukkit end
+
+ for (int k = 0; k < j; ++k) {
+ float f = ((float) (k % 2) - 0.5F) * (float) i / 4.0F;
+@@ -148,7 +164,7 @@
+
+ entityslime.setSize(i / 2);
+ entityslime.setPositionRotation(this.locX + (double) f, this.locY + 0.5D, this.locZ + (double) f1, this.random.nextFloat() * 360.0F, 0.0F);
+- this.world.addEntity(entityslime);
++ this.world.addEntity(entityslime, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason
+ }
+ }
+
diff --git a/nms-patches/EntitySmallFireball.patch b/nms-patches/EntitySmallFireball.patch
new file mode 100644
index 00000000..7d2e7589
--- /dev/null
+++ b/nms-patches/EntitySmallFireball.patch
@@ -0,0 +1,39 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySmallFireball.java 2014-11-27 08:59:46.729421723 +1100
++++ src/main/java/net/minecraft/server/EntitySmallFireball.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.EntityCombustByEntityEvent; // CraftBukkit
++
+ public class EntitySmallFireball extends EntityFireball {
+
+ public EntitySmallFireball(World world) {
+@@ -26,7 +28,14 @@
+ if (flag) {
+ this.a(this.shooter, movingobjectposition.entity);
+ if (!movingobjectposition.entity.isFireProof()) {
+- movingobjectposition.entity.setOnFire(5);
++ // CraftBukkit start - Entity damage by entity event + combust event
++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent((org.bukkit.entity.Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity(), 5);
++ movingobjectposition.entity.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ movingobjectposition.entity.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
+ }
+ }
+ } else {
+@@ -39,7 +48,11 @@
+ BlockPosition blockposition = movingobjectposition.a().shift(movingobjectposition.direction);
+
+ if (this.world.isEmpty(blockposition)) {
+- this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this).isCancelled()) {
++ this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/EntitySnowman.patch b/nms-patches/EntitySnowman.patch
new file mode 100644
index 00000000..67e4371f
--- /dev/null
+++ b/nms-patches/EntitySnowman.patch
@@ -0,0 +1,42 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySnowman.java 2014-11-27 08:59:46.733421706 +1100
++++ src/main/java/net/minecraft/server/EntitySnowman.java 2014-11-27 08:42:10.144850927 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.event.block.EntityBlockFormEvent;
++// CraftBukkit end
++
+ public class EntitySnowman extends EntityGolem implements IRangedEntity {
+
+ public EntitySnowman(World world) {
+@@ -31,7 +37,7 @@
+ }
+
+ if (this.world.getBiome(new BlockPosition(i, 0, k)).a(new BlockPosition(i, j, k)) > 1.0F) {
+- this.damageEntity(DamageSource.BURN, 1.0F);
++ this.damageEntity(CraftEventFactory.MELTING, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING
+ }
+
+ for (int l = 0; l < 4; ++l) {
+@@ -39,7 +45,17 @@
+ j = MathHelper.floor(this.locY);
+ k = MathHelper.floor(this.locZ + (double) ((float) (l / 2 % 2 * 2 - 1) * 0.25F));
+ if (this.world.getType(new BlockPosition(i, j, k)).getBlock().getMaterial() == Material.AIR && this.world.getBiome(new BlockPosition(i, 0, k)).a(new BlockPosition(i, j, k)) < 0.8F && Blocks.SNOW_LAYER.canPlace(this.world, new BlockPosition(i, j, k))) {
+- this.world.setTypeUpdate(new BlockPosition(i, j, k), Blocks.SNOW_LAYER.getBlockData());
++ // CraftBukkit start
++ org.bukkit.block.BlockState blockState = this.world.getWorld().getBlockAt(i, j, k).getState();
++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.SNOW_LAYER));
++
++ EntityBlockFormEvent event = new EntityBlockFormEvent(this.getBukkitEntity(), blockState.getBlock(), blockState);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if(!event.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/EntitySpider.patch b/nms-patches/EntitySpider.patch
new file mode 100644
index 00000000..c9c70dc8
--- /dev/null
+++ b/nms-patches/EntitySpider.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySpider.java 2014-11-27 08:59:46.733421706 +1100
++++ src/main/java/net/minecraft/server/EntitySpider.java 2014-11-27 08:42:10.096851020 +1100
+@@ -107,7 +107,7 @@
+
+ entityskeleton.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
+ entityskeleton.prepare(difficultydamagescaler, (GroupDataEntity) null);
+- this.world.addEntity(entityskeleton);
++ this.world.addEntity(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); // CraftBukkit - add SpawnReason
+ entityskeleton.mount(this);
+ }
+
diff --git a/nms-patches/EntitySquid.patch b/nms-patches/EntitySquid.patch
new file mode 100644
index 00000000..83d481ae
--- /dev/null
+++ b/nms-patches/EntitySquid.patch
@@ -0,0 +1,37 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntitySquid.java 2014-11-27 08:59:46.733421706 +1100
++++ src/main/java/net/minecraft/server/EntitySquid.java 2014-11-27 08:42:10.156850903 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.TrigMath; // CraftBukkit
++
+ public class EntitySquid extends EntityWaterAnimal {
+
+ public float a;
+@@ -67,9 +69,11 @@
+
+ }
+
++ /* CraftBukkit start - Delegate to Entity to use existing inWater value
+ public boolean V() {
+ return this.world.a(this.getBoundingBox().grow(0.0D, -0.6000000238418579D, 0.0D), Material.WATER, (Entity) this);
+ }
++ // CraftBukkit end */
+
+ public void m() {
+ super.m();
+@@ -116,10 +120,12 @@
+ }
+
+ f = MathHelper.sqrt(this.motX * this.motX + this.motZ * this.motZ);
+- this.aG += (-((float) Math.atan2(this.motX, this.motZ)) * 180.0F / 3.1415927F - this.aG) * 0.1F;
++ // CraftBukkit - Math -> TrigMath
++ this.aG += (-((float) TrigMath.atan2(this.motX, this.motZ)) * 180.0F / 3.1415927F - this.aG) * 0.1F;
+ this.yaw = this.aG;
+ this.c = (float) ((double) this.c + 3.141592653589793D * (double) this.bp * 1.5D);
+- this.a += (-((float) Math.atan2((double) f, this.motY)) * 180.0F / 3.1415927F - this.a) * 0.1F;
++ // CraftBukkit - Math -> TrigMath
++ this.a += (-((float) TrigMath.atan2((double) f, this.motY)) * 180.0F / 3.1415927F - this.a) * 0.1F;
+ } else {
+ this.bl = MathHelper.e(MathHelper.sin(this.bj)) * 3.1415927F * 0.25F;
+ if (!this.world.isStatic) {
diff --git a/nms-patches/EntityTNTPrimed.patch b/nms-patches/EntityTNTPrimed.patch
new file mode 100644
index 00000000..96c3399a
--- /dev/null
+++ b/nms-patches/EntityTNTPrimed.patch
@@ -0,0 +1,52 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityTNTPrimed.java 2014-11-27 08:59:46.737421688 +1100
++++ src/main/java/net/minecraft/server/EntityTNTPrimed.java 2014-11-27 08:42:10.120850973 +1100
+@@ -1,9 +1,13 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.ExplosionPrimeEvent; // CraftBukkit
++
+ public class EntityTNTPrimed extends Entity {
+
+ public int fuseTicks;
+ private EntityLiving source;
++ public float yield = 4; // CraftBukkit - add field
++ public boolean isIncendiary = false; // CraftBukkit - add field
+
+ public EntityTNTPrimed(World world) {
+ super(world);
+@@ -52,10 +56,13 @@
+ }
+
+ if (this.fuseTicks-- <= 0) {
+- this.die();
++ // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
++ // this.die();
+ if (!this.world.isStatic) {
+ this.explode();
+ }
++ this.die();
++ // CraftBukkit end
+ } else {
+ this.W();
+ this.world.addParticle(EnumParticle.SMOKE_NORMAL, this.locX, this.locY + 0.5D, this.locZ, 0.0D, 0.0D, 0.0D, new int[0]);
+@@ -64,9 +71,18 @@
+ }
+
+ private void explode() {
+- float f = 4.0F;
++ // CraftBukkit start
++ // float f = 4.0F;
+
+- this.world.explode(this, this.locX, this.locY + (double) (this.length / 2.0F), this.locZ, f, true);
++ org.bukkit.craftbukkit.CraftServer server = this.world.getServer();
++
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) org.bukkit.craftbukkit.entity.CraftEntity.getEntity(server, this));
++ server.getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.world.createExplosion(this, this.locX, this.locY + (double) (this.length / 2.0F), this.locZ, event.getRadius(), event.getFire(), true);
++ }
++ // CraftBukkit end
+ }
+
+ protected void b(NBTTagCompound nbttagcompound) {
diff --git a/nms-patches/EntityThrownExpBottle.patch b/nms-patches/EntityThrownExpBottle.patch
new file mode 100644
index 00000000..c6b40039
--- /dev/null
+++ b/nms-patches/EntityThrownExpBottle.patch
@@ -0,0 +1,22 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityThrownExpBottle.java 2014-11-27 08:59:46.737421688 +1100
++++ src/main/java/net/minecraft/server/EntityThrownExpBottle.java 2014-11-27 08:42:10.112850989 +1100
+@@ -28,8 +28,17 @@
+
+ protected void a(MovingObjectPosition movingobjectposition) {
+ if (!this.world.isStatic) {
+- this.world.triggerEffect(2002, new BlockPosition(this), 0);
+- int i = 3 + this.world.random.nextInt(5) + this.world.random.nextInt(5);
++ // CraftBukkit - moved to after event
++ // this.world.triggerEffect(2002, new BlockPosition(this), 0);
++ int i = 3 + this.world.random.nextInt(5) + this.world.random.nextInt(5);
++
++ // CraftBukkit start
++ org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, i);
++ i = event.getExperience();
++ if (event.getShowEffect()) {
++ this.world.triggerEffect(2002, new BlockPosition(this), 0);
++ }
++ // CraftBukkit end
+
+ while (i > 0) {
+ int j = EntityExperienceOrb.getOrbValue(i);
diff --git a/nms-patches/EntityTrackerEntry.patch b/nms-patches/EntityTrackerEntry.patch
new file mode 100644
index 00000000..9d0f38de
--- /dev/null
+++ b/nms-patches/EntityTrackerEntry.patch
@@ -0,0 +1,176 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityTrackerEntry.java 2014-11-27 08:59:46.741421670 +1100
++++ src/main/java/net/minecraft/server/EntityTrackerEntry.java 2014-11-27 08:42:10.136850942 +1100
+@@ -8,6 +8,11 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerVelocityEvent;
++// CraftBukkit end
++
+ public class EntityTrackerEntry {
+
+ private static final Logger p = LogManager.getLogger();
+@@ -74,13 +79,13 @@
+ this.broadcast(new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle));
+ }
+
+- if (this.tracker instanceof EntityItemFrame && this.m % 10 == 0) {
++ if (this.tracker instanceof EntityItemFrame /*&& this.m % 10 == 0*/) { // CraftBukkit - Moved below, should always enter this block
+ EntityItemFrame entityitemframe = (EntityItemFrame) this.tracker;
+ ItemStack itemstack = entityitemframe.getItem();
+
+- if (itemstack != null && itemstack.getItem() instanceof ItemWorldMap) {
++ if (this.m % 10 == 0 && itemstack != null && itemstack.getItem() instanceof ItemWorldMap) { // CraftBukkit - Moved this.m % 10 logic here so item frames do not enter the other blocks
+ WorldMap worldmap = Items.FILLED_MAP.getSavedMap(itemstack, this.tracker.world);
+- Iterator iterator = list.iterator();
++ Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit
+
+ while (iterator.hasNext()) {
+ EntityHuman entityhuman = (EntityHuman) iterator.next();
+@@ -115,6 +120,19 @@
+ Object object = null;
+ boolean flag = Math.abs(j1) >= 4 || Math.abs(k1) >= 4 || Math.abs(l1) >= 4 || this.m % 60 == 0;
+ boolean flag1 = Math.abs(l - this.yRot) >= 4 || Math.abs(i1 - this.xRot) >= 4;
++
++ // CraftBukkit start - Code moved from below
++ if (flag) {
++ this.xLoc = i;
++ this.yLoc = j;
++ this.zLoc = k;
++ }
++
++ if (flag1) {
++ this.yRot = l;
++ this.xRot = i1;
++ }
++ // CraftBukkit end
+
+ if (this.m > 0 || this.tracker instanceof EntityArrow) {
+ if (j1 >= -128 && j1 < 128 && k1 >= -128 && k1 < 128 && l1 >= -128 && l1 < 128 && this.v <= 400 && !this.x && this.y == this.tracker.onGround) {
+@@ -128,6 +146,11 @@
+ } else {
+ this.y = this.tracker.onGround;
+ this.v = 0;
++ // CraftBukkit start - Refresh list of who can see a player before sending teleport packet
++ if (this.tracker instanceof EntityPlayer) {
++ this.scanPlayers(new java.util.ArrayList(this.trackedPlayers));
++ }
++ // CraftBukkit end
+ object = new PacketPlayOutEntityTeleport(this.tracker.getId(), i, j, k, (byte) l, (byte) i1, this.tracker.onGround);
+ }
+ }
+@@ -152,6 +175,7 @@
+ }
+
+ this.b();
++ /* CraftBukkit start - Code moved up
+ if (flag) {
+ this.xLoc = i;
+ this.yLoc = j;
+@@ -162,6 +186,7 @@
+ this.yRot = l;
+ this.xRot = i1;
+ }
++ // CraftBukkit end */
+
+ this.x = false;
+ } else {
+@@ -193,7 +218,27 @@
+
+ ++this.m;
+ if (this.tracker.velocityChanged) {
+- this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker));
++ // CraftBukkit start - Create PlayerVelocity event
++ boolean cancelled = false;
++
++ if (this.tracker instanceof EntityPlayer) {
++ Player player = (Player) this.tracker.getBukkitEntity();
++ org.bukkit.util.Vector velocity = player.getVelocity();
++
++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity);
++ this.tracker.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ cancelled = true;
++ } else if (!velocity.equals(event.getVelocity())) {
++ player.setVelocity(velocity);
++ }
++ }
++
++ if (!cancelled) {
++ this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker));
++ }
++ // CraftBukkit end
+ this.tracker.velocityChanged = false;
+ }
+
+@@ -211,6 +256,11 @@
+ Set set = attributemapserver.getAttributes();
+
+ if (!set.isEmpty()) {
++ // CraftBukkit start - Send scaled max health
++ if (this.tracker instanceof EntityPlayer) {
++ ((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(set, false);
++ }
++ // CraftBukkit end
+ this.broadcastIncludingSelf(new PacketPlayOutUpdateAttributes(this.tracker.getId(), set));
+ }
+
+@@ -260,7 +310,17 @@
+ public void updatePlayer(EntityPlayer entityplayer) {
+ if (entityplayer != this.tracker) {
+ if (this.c(entityplayer)) {
+- if (!this.trackedPlayers.contains(entityplayer) && (this.e(entityplayer) || this.tracker.attachedToPlayer)) {
++ if (!this.trackedPlayers.contains(entityplayer) && (this.e(entityplayer) || this.tracker.attachedToPlayer)) {
++ // CraftBukkit start - respect vanish API
++ if (this.tracker instanceof EntityPlayer) {
++ Player player = ((EntityPlayer) this.tracker).getBukkitEntity();
++ if (!entityplayer.getBukkitEntity().canSee(player)) {
++ return;
++ }
++ }
++
++ entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId()));
++ // CraftBukkit end
+ this.trackedPlayers.add(entityplayer);
+ Packet packet = this.c();
+
+@@ -278,6 +338,12 @@
+ if (this.tracker instanceof EntityLiving) {
+ AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).getAttributeMap();
+ Collection collection = attributemapserver.c();
++
++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
++ if (this.tracker.getId() == entityplayer.getId()) {
++ ((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(collection, false);
++ }
++ // CraftBukkit end
+
+ if (!collection.isEmpty()) {
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutUpdateAttributes(this.tracker.getId(), collection));
+@@ -316,6 +382,11 @@
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutBed(entityhuman, new BlockPosition(this.tracker)));
+ }
+ }
++
++ // CraftBukkit start - Fix for nonsensical head yaw
++ this.i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
++ this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i));
++ // CraftBukkit end
+
+ if (this.tracker instanceof EntityLiving) {
+ EntityLiving entityliving = (EntityLiving) this.tracker;
+@@ -356,7 +427,10 @@
+
+ private Packet c() {
+ if (this.tracker.dead) {
+- EntityTrackerEntry.p.warn("Fetching addPacket for removed entity");
++ // CraftBukkit start - Remove useless error spam, just return
++ // EntityTrackerEntry.p.warn("Fetching addPacket for removed entity");
++ return null;
++ // CraftBukkit end
+ }
+
+ if (this.tracker instanceof EntityItem) {
diff --git a/nms-patches/EntityVillager.patch b/nms-patches/EntityVillager.patch
new file mode 100644
index 00000000..e2b327ae
--- /dev/null
+++ b/nms-patches/EntityVillager.patch
@@ -0,0 +1,19 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityVillager.java 2014-11-27 08:59:46.741421670 +1100
++++ src/main/java/net/minecraft/server/EntityVillager.java 2014-11-27 08:42:10.144850927 +1100
+@@ -1,6 +1,7 @@
+ package net.minecraft.server;
+
+ import java.util.Iterator;
++import org.bukkit.craftbukkit.entity.CraftVillager;
+
+ public class EntityVillager extends EntityAgeable implements NPC, IMerchant {
+
+@@ -28,7 +29,7 @@
+
+ public EntityVillager(World world, int i) {
+ super(world);
+- this.inventory = new InventorySubcontainer("Items", false, 8);
++ this.inventory = new InventorySubcontainer("Items", false, 8, (CraftVillager) this.getBukkitEntity()); // CraftBukkit add argument
+ this.setProfession(i);
+ this.a(0.6F, 1.8F);
+ ((Navigation) this.getNavigation()).b(true);
diff --git a/nms-patches/EntityWither.patch b/nms-patches/EntityWither.patch
new file mode 100644
index 00000000..7caa58e9
--- /dev/null
+++ b/nms-patches/EntityWither.patch
@@ -0,0 +1,78 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityWither.java 2014-11-27 08:59:46.745421653 +1100
++++ src/main/java/net/minecraft/server/EntityWither.java 2014-11-27 08:42:10.156850903 +1100
+@@ -5,6 +5,12 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
++
+ public class EntityWither extends EntityMonster implements IRangedEntity {
+
+ private float[] b = new float[2];
+@@ -160,13 +166,38 @@
+ if (this.cj() > 0) {
+ i = this.cj() - 1;
+ if (i <= 0) {
+- this.world.createExplosion(this, this.locX, this.locY + (double) this.getHeadHeight(), this.locZ, 7.0F, false, this.world.getGameRules().getBoolean("mobGriefing"));
+- this.world.a(1013, new BlockPosition(this), 0);
++ // CraftBukkit start
++ // this.world.createExplosion(this, this.locX, this.locY + (double) this.getHeadHeight(), this.locZ, 7.0F, false, this.world.getGameRules().getBoolean("mobGriefing"));
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.world.createExplosion(this, this.locX, this.locY + (double) this.getHeadHeight(), this.locZ, event.getRadius(), event.getFire(), this.world.getGameRules().getBoolean("mobGriefing"));
++ }
++ // CraftBukkit end
++
++ // CraftBukkit start - Use relative location for far away sounds
++ // this.world.a(1013, new BlockPosition(this), 0);
++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16;
++ for (EntityPlayer player : (List<EntityPlayer>) this.world.players) {
++ double deltaX = this.locX - player.locX;
++ double deltaZ = this.locZ - player.locZ;
++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++ if (distanceSquared > viewDistance * viewDistance) {
++ double deltaLength = Math.sqrt(distanceSquared);
++ double relativeX = player.locX + (deltaX / deltaLength) * viewDistance;
++ double relativeZ = player.locZ + (deltaZ / deltaLength) * viewDistance;
++ player.playerConnection.sendPacket(new PacketPlayOutWorldEvent(1013, new BlockPosition((int) relativeX, (int) this.locY, (int) relativeZ), 0, true));
++ } else {
++ player.playerConnection.sendPacket(new PacketPlayOutWorldEvent(1013, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0, true));
++ }
++ }
++ // CraftBukkit end
+ }
+
+ this.r(i);
+ if (this.ticksLived % 10 == 0) {
+- this.heal(10.0F);
++ this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
+ }
+
+ } else {
+@@ -251,6 +282,11 @@
+ Block block = this.world.getType(new BlockPosition(j2, k2, l2)).getBlock();
+
+ if (block.getMaterial() != Material.AIR && block != Blocks.BEDROCK && block != Blocks.END_PORTAL && block != Blocks.END_PORTAL_FRAME && block != Blocks.COMMAND_BLOCK && block != Blocks.BARRIER) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, j2, k2, l2, Blocks.AIR, 0).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ flag = this.world.setAir(new BlockPosition(j2, k2, l2), true) || flag;
+ }
+ }
+@@ -264,7 +300,7 @@
+ }
+
+ if (this.ticksLived % 20 == 0) {
+- this.heal(1.0F);
++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ }
+
+ }
diff --git a/nms-patches/EntityWitherSkull.patch b/nms-patches/EntityWitherSkull.patch
new file mode 100644
index 00000000..1428bf51
--- /dev/null
+++ b/nms-patches/EntityWitherSkull.patch
@@ -0,0 +1,36 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityWitherSkull.java 2014-11-27 08:59:46.745421653 +1100
++++ src/main/java/net/minecraft/server/EntityWitherSkull.java 2014-11-27 08:42:10.120850973 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.ExplosionPrimeEvent; // CraftBukkit
++
+ public class EntityWitherSkull extends EntityFireball {
+
+ public EntityWitherSkull(World world) {
+@@ -36,7 +38,7 @@
+ if (this.shooter != null) {
+ if (movingobjectposition.entity.damageEntity(DamageSource.mobAttack(this.shooter), 8.0F)) {
+ if (!movingobjectposition.entity.isAlive()) {
+- this.shooter.heal(5.0F);
++ this.shooter.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); // CraftBukkit
+ } else {
+ this.a(this.shooter, movingobjectposition.entity);
+ }
+@@ -60,7 +62,15 @@
+ }
+ }
+
+- this.world.createExplosion(this, this.locX, this.locY, this.locZ, 1.0F, false, this.world.getGameRules().getBoolean("mobGriefing"));
++ // CraftBukkit start
++ // this.world.createExplosion(this, this.locX, this.locY, this.locZ, 1.0F, false, this.world.getGameRules().getBoolean("mobGriefing"));
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire(), this.world.getGameRules().getBoolean("mobGriefing"));
++ }
++ // CraftBukkit end
+ this.die();
+ }
+
diff --git a/nms-patches/EntityWolf.patch b/nms-patches/EntityWolf.patch
new file mode 100644
index 00000000..1621a484
--- /dev/null
+++ b/nms-patches/EntityWolf.patch
@@ -0,0 +1,87 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityWolf.java 2014-11-27 08:59:46.749421635 +1100
++++ src/main/java/net/minecraft/server/EntityWolf.java 2014-11-27 08:42:10.160850895 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent.TargetReason;
++// CraftBukkit end
++
+ public class EntityWolf extends EntityTameableAnimal {
+
+ private float bm;
+@@ -51,8 +56,19 @@
+ } else if (!this.isTamed()) {
+ this.setAngry(true);
+ }
++ }
+
++ // CraftBukkit - add overriden version
++ @Override
++ public void setGoalTarget(EntityLiving entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fire) {
++ super.setGoalTarget(entityliving, reason, fire);
++ if (entityliving == null) {
++ this.setAngry(false);
++ } else if (!this.isTamed()) {
++ this.setAngry(true);
++ }
+ }
++ // CraftBukkit end
+
+ protected void E() {
+ this.datawatcher.watch(18, Float.valueOf(this.getHealth()));
+@@ -85,7 +101,8 @@
+ }
+
+ protected String z() {
+- return this.isAngry() ? "mob.wolf.growl" : (this.random.nextInt(3) == 0 ? (this.isTamed() && this.datawatcher.getFloat(18) < 10.0F ? "mob.wolf.whine" : "mob.wolf.panting") : "mob.wolf.bark");
++ // CraftBukkit - (getFloat(18) < 10) -> (getFloat(18) < this.getMaxHealth() / 2)
++ return this.isAngry() ? "mob.wolf.growl" : (this.random.nextInt(3) == 0 ? (this.isTamed() && this.datawatcher.getFloat(18) < this.getMaxHealth() / 2 ? "mob.wolf.whine" : "mob.wolf.panting") : "mob.wolf.bark");
+ }
+
+ protected String bn() {
+@@ -219,7 +236,7 @@
+ --itemstack.count;
+ }
+
+- this.heal((float) itemfood.getNutrition(itemstack));
++ this.heal((float) itemfood.getNutrition(itemstack), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
+ if (itemstack.count <= 0) {
+ entityhuman.inventory.setItem(entityhuman.inventory.itemInHandIndex, (ItemStack) null);
+ }
+@@ -244,7 +261,7 @@
+ this.bk.setSitting(!this.isSitting());
+ this.aW = false;
+ this.navigation.n();
+- this.setGoalTarget((EntityLiving) null);
++ this.setGoalTarget((EntityLiving) null, TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason
+ }
+ } else if (itemstack != null && itemstack.getItem() == Items.BONE && !this.isAngry()) {
+ if (!entityhuman.abilities.canInstantlyBuild) {
+@@ -256,12 +273,13 @@
+ }
+
+ if (!this.world.isStatic) {
+- if (this.random.nextInt(3) == 0) {
++ // CraftBukkit - added event call and isCancelled check.
++ if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, entityhuman).isCancelled()) {
+ this.setTamed(true);
+ this.navigation.n();
+- this.setGoalTarget((EntityLiving) null);
++ this.setGoalTarget((EntityLiving) null, TargetReason.FORGOT_TARGET, true);
+ this.bk.setSitting(true);
+- this.setHealth(20.0F);
++ this.setHealth(this.getMaxHealth()); // CraftBukkit - 20.0 -> getMaxHealth()
+ this.setOwnerUUID(entityhuman.getUniqueID().toString());
+ this.l(true);
+ this.world.broadcastEntityEffect(this, (byte) 7);
+@@ -348,7 +366,7 @@
+ }
+
+ protected boolean isTypeNotPersistent() {
+- return !this.isTamed() && this.ticksLived > 2400;
++ return !this.isTamed() /*&& this.ticksLived > 2400*/; // CraftBukkit
+ }
+
+ public boolean a(EntityLiving entityliving, EntityLiving entityliving1) {
diff --git a/nms-patches/EntityZombie.patch b/nms-patches/EntityZombie.patch
new file mode 100644
index 00000000..634ca939
--- /dev/null
+++ b/nms-patches/EntityZombie.patch
@@ -0,0 +1,108 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/EntityZombie.java 2014-11-27 08:59:46.749421635 +1100
++++ src/main/java/net/minecraft/server/EntityZombie.java 2014-11-27 08:42:10.144850927 +1100
+@@ -4,6 +4,14 @@
+ import java.util.List;
+ import java.util.UUID;
+
++//CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.entity.EntityCombustEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++//CraftBukkit end
++
+ public class EntityZombie extends EntityMonster {
+
+ protected static final IAttribute b = (new AttributeRanged((IAttribute) null, "zombie.spawnReinforcements", 0.0D, 0.0D, 1.0D)).a("Spawn Reinforcements Chance");
+@@ -14,6 +22,7 @@
+ private boolean bn = false;
+ private float bo = -1.0F;
+ private float bp;
++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
+
+ public EntityZombie(World world) {
+ super(world);
+@@ -136,7 +145,14 @@
+ }
+
+ if (flag) {
+- this.setOnFire(8);
++ // CraftBukkit start
++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), 8);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -170,8 +186,8 @@
+ if (World.a((IBlockAccess) this.world, new BlockPosition(i1, j1 - 1, k1)) && this.world.getLightLevel(new BlockPosition(i1, j1, k1)) < 10) {
+ entityzombie.setPosition((double) i1, (double) j1, (double) k1);
+ if (!this.world.isPlayerNearby((double) i1, (double) j1, (double) k1, 7.0D) && this.world.a(entityzombie.getBoundingBox(), (Entity) entityzombie) && this.world.getCubes(entityzombie, entityzombie.getBoundingBox()).isEmpty() && !this.world.containsLiquid(entityzombie.getBoundingBox())) {
+- this.world.addEntity(entityzombie);
+- entityzombie.setGoalTarget(entityliving);
++ this.world.addEntity(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
++ entityzombie.setGoalTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true);
+ entityzombie.prepare(this.world.E(new BlockPosition(entityzombie)), (GroupDataEntity) null);
+ this.getAttributeInstance(EntityZombie.b).b(new AttributeModifier("Zombie reinforcement caller charge", -0.05000000074505806D, 0));
+ entityzombie.getAttributeInstance(EntityZombie.b).b(new AttributeModifier("Zombie reinforcement callee charge", -0.05000000074505806D, 0));
+@@ -190,6 +206,12 @@
+ public void s_() {
+ if (!this.world.isStatic && this.cn()) {
+ int i = this.cp();
++
++ // CraftBukkit start - Use wall time instead of ticks for villager conversion
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ this.lastTick = MinecraftServer.currentTick;
++ i *= elapsedTicks;
++ // CraftBukkit end
+
+ this.bm -= i;
+ if (this.bm <= 0) {
+@@ -207,7 +229,14 @@
+ int i = this.world.getDifficulty().a();
+
+ if (this.bz() == null && this.isBurning() && this.random.nextFloat() < (float) i * 0.3F) {
+- entity.setOnFire(2 * i);
++ // CraftBukkit start
++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 2 * i);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entity.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -316,7 +345,7 @@
+ entityzombie.setBaby(true);
+ }
+
+- this.world.addEntity(entityzombie);
++ this.world.addEntity(entityzombie, CreatureSpawnEvent.SpawnReason.INFECTION); // CraftBukkit - add SpawnReason
+ this.world.a((EntityHuman) null, 1016, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0);
+ }
+
+@@ -369,7 +398,7 @@
+ entitychicken1.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
+ entitychicken1.prepare(difficultydamagescaler, (GroupDataEntity) null);
+ entitychicken1.l(true);
+- this.world.addEntity(entitychicken1);
++ this.world.addEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT);
+ this.mount(entitychicken1);
+ }
+ }
+@@ -452,7 +481,7 @@
+ }
+
+ this.world.kill(this);
+- this.world.addEntity(entityvillager);
++ this.world.addEntity(entityvillager, CreatureSpawnEvent.SpawnReason.CURED); // CraftBukkit - add SpawnReason
+ entityvillager.addEffect(new MobEffect(MobEffectList.CONFUSION.id, 200, 0));
+ this.world.a((EntityHuman) null, 1017, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0);
+ }
diff --git a/nms-patches/ExpirableListEntry.patch b/nms-patches/ExpirableListEntry.patch
new file mode 100644
index 00000000..6bb3d391
--- /dev/null
+++ b/nms-patches/ExpirableListEntry.patch
@@ -0,0 +1,42 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ExpirableListEntry.java 2014-11-27 08:59:46.749421635 +1100
++++ src/main/java/net/minecraft/server/ExpirableListEntry.java 2014-11-27 08:42:10.096851020 +1100
+@@ -22,7 +22,7 @@
+ }
+
+ protected ExpirableListEntry(Object object, JsonObject jsonobject) {
+- super(object, jsonobject);
++ super(checkExpiry(object, jsonobject), jsonobject);
+
+ Date date;
+
+@@ -65,4 +65,30 @@
+ jsonobject.addProperty("expires", this.d == null ? "forever" : ExpirableListEntry.a.format(this.d));
+ jsonobject.addProperty("reason", this.e);
+ }
++
++ // CraftBukkit start
++ public String getSource() {
++ return this.c;
++ }
++
++ public Date getCreated() {
++ return this.b;
++ }
++
++ private static Object checkExpiry(Object object, JsonObject jsonobject) {
++ Date expires = null;
++
++ try {
++ expires = jsonobject.has("expires") ? a.parse(jsonobject.get("expires").getAsString()) : null;
++ } catch (ParseException ex) {
++ // Guess we don't have a date
++ }
++
++ if (expires == null || expires.after(new Date())) {
++ return object;
++ } else {
++ return null;
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/Explosion.patch b/nms-patches/Explosion.patch
new file mode 100644
index 00000000..3d0b5b84
--- /dev/null
+++ b/nms-patches/Explosion.patch
@@ -0,0 +1,127 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Explosion.java 2014-11-27 08:59:46.753421618 +1100
++++ src/main/java/net/minecraft/server/Explosion.java 2014-11-27 08:42:10.160850895 +1100
+@@ -8,6 +8,12 @@
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Random;
++
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityExplodeEvent;
++import org.bukkit.Location;
++// CraftBukkit end
+
+ public class Explosion {
+
+@@ -22,11 +28,12 @@
+ private final float size;
+ private final List blocks = Lists.newArrayList();
+ private final Map k = Maps.newHashMap();
++ public boolean wasCanceled = false; // CraftBukkit - add field
+
+ public Explosion(World world, Entity entity, double d0, double d1, double d2, float f, boolean flag, boolean flag1) {
+ this.world = world;
+ this.source = entity;
+- this.size = f;
++ this.size = (float) Math.max(f, 0.0); // CraftBukkit - clamp bad values
+ this.posX = d0;
+ this.posY = d1;
+ this.posZ = d2;
+@@ -35,6 +42,12 @@
+ }
+
+ public void a() {
++ // CraftBukkit start
++ if (this.size < 0.1F) {
++ return;
++ }
++ // CraftBukkit end
++
+ HashSet hashset = Sets.newHashSet();
+ boolean flag = true;
+
+@@ -68,7 +81,7 @@
+ f -= (f2 + 0.3F) * 0.3F;
+ }
+
+- if (f > 0.0F && (this.source == null || this.source.a(this, this.world, blockposition, iblockdata, f))) {
++ if (f > 0.0F && (this.source == null || this.source.a(this, this.world, blockposition, iblockdata, f)) && blockposition.getY() < 256 && blockposition.getY() >= 0) { // CraftBukkit - don't wrap explosions
+ hashset.add(blockposition);
+ }
+
+@@ -112,7 +125,14 @@
+ double d12 = (double) this.world.a(vec3d, entity.getBoundingBox());
+ double d13 = (1.0D - d7) * d12;
+
++ // entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D)));
++
++ // CraftBukkit start
++ CraftEventFactory.entityDamage = source;
+ entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D)));
++ CraftEventFactory.entityDamage = null;
++ // CraftBukkit end
++
+ double d14 = EnchantmentProtection.a(entity, d13);
+
+ entity.motX += d8 * d14;
+@@ -140,6 +160,35 @@
+ BlockPosition blockposition;
+
+ if (this.b) {
++ // CraftBukkit start
++ org.bukkit.World bworld = this.world.getWorld();
++ org.bukkit.entity.Entity explode = this.source == null ? null : this.source.getBukkitEntity();
++ Location location = new Location(bworld, this.posX, this.posY, this.posZ);
++
++ List<org.bukkit.block.Block> blockList = Lists.newArrayList();
++ for (int i1 = this.blocks.size() - 1; i1 >= 0; i1--) {
++ BlockPosition cpos = (BlockPosition) this.blocks.get(i1);
++ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ());
++ if (bblock.getType() != org.bukkit.Material.AIR) {
++ blockList.add(bblock);
++ }
++ }
++
++ EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, 0.3F);
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ this.blocks.clear();
++
++ for (org.bukkit.block.Block bblock : event.blockList()) {
++ BlockPosition coords = new BlockPosition(bblock.getX(), bblock.getY(), bblock.getZ());
++ blocks.add(coords);
++ }
++
++ if (event.isCancelled()) {
++ this.wasCanceled = true;
++ return;
++ }
++ // CraftBukkit end
+ iterator = this.blocks.iterator();
+
+ while (iterator.hasNext()) {
+@@ -170,7 +219,8 @@
+
+ if (block.getMaterial() != Material.AIR) {
+ if (block.a(this)) {
+- block.dropNaturally(this.world, blockposition, this.world.getType(blockposition), 1.0F / this.size, 0);
++ // CraftBukkit - add yield
++ block.dropNaturally(this.world, blockposition, this.world.getType(blockposition), event.getYield(), 0);
+ }
+
+ this.world.setTypeAndData(blockposition, Blocks.AIR.getBlockData(), 3);
+@@ -184,8 +234,12 @@
+
+ while (iterator.hasNext()) {
+ blockposition = (BlockPosition) iterator.next();
+- if (this.world.getType(blockposition).getBlock().getMaterial() == Material.AIR && this.world.getType(blockposition.down()).getBlock().m() && this.c.nextInt(3) == 0) {
+- this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ if (this.world.getType(blockposition).getBlock().getMaterial() == Material.AIR && this.world.getType(blockposition.down()).getBlock().m() && this.c.nextInt(3) == 0) {
++ // CraftBukkit start - Ignition by explosion
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this).isCancelled()) {
++ this.world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/FoodMetaData.patch b/nms-patches/FoodMetaData.patch
new file mode 100644
index 00000000..bfd974b3
--- /dev/null
+++ b/nms-patches/FoodMetaData.patch
@@ -0,0 +1,66 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/FoodMetaData.java 2014-11-27 08:59:46.753421618 +1100
++++ src/main/java/net/minecraft/server/FoodMetaData.java 2014-11-27 08:42:10.104851005 +1100
+@@ -6,9 +6,17 @@
+ public float saturationLevel = 5.0F;
+ public float exhaustionLevel;
+ public int foodTickTimer;
++ private EntityHuman entityhuman; // CraftBukkit
+ private int e = 20;
+
+- public FoodMetaData() {}
++ public FoodMetaData() { throw new AssertionError("Whoopsie, we missed the bukkit."); } // CraftBukkit start - throw an error
++
++ // CraftBukkit start - added EntityHuman constructor
++ public FoodMetaData(EntityHuman entityhuman) {
++ org.apache.commons.lang.Validate.notNull(entityhuman);
++ this.entityhuman = entityhuman;
++ }
++ // CraftBukkit end
+
+ public void eat(int i, float f) {
+ this.foodLevel = Math.min(i + this.foodLevel, 20);
+@@ -16,7 +24,17 @@
+ }
+
+ public void a(ItemFood itemfood, ItemStack itemstack) {
+- this.eat(itemfood.getNutrition(itemstack), itemfood.getSaturationModifier(itemstack));
++ // CraftBukkit start
++ int oldFoodLevel = foodLevel;
++
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityhuman, itemfood.getNutrition(itemstack) + oldFoodLevel);
++
++ if (!event.isCancelled()) {
++ this.eat(event.getFoodLevel() - oldFoodLevel, itemfood.getSaturationModifier(itemstack));
++ }
++
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutUpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel));
++ // CraftBukkit end
+ }
+
+ public void a(EntityHuman entityhuman) {
+@@ -28,14 +46,23 @@
+ if (this.saturationLevel > 0.0F) {
+ this.saturationLevel = Math.max(this.saturationLevel - 1.0F, 0.0F);
+ } else if (enumdifficulty != EnumDifficulty.PEACEFUL) {
+- this.foodLevel = Math.max(this.foodLevel - 1, 0);
++ // CraftBukkit start
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityhuman, Math.max(this.foodLevel - 1, 0));
++
++ if (!event.isCancelled()) {
++ this.foodLevel = event.getFoodLevel();
++ }
++
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutUpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel));
++ // CraftBukkit end
+ }
+ }
+
+ if (entityhuman.world.getGameRules().getBoolean("naturalRegeneration") && this.foodLevel >= 18 && entityhuman.cl()) {
+ ++this.foodTickTimer;
+ if (this.foodTickTimer >= 80) {
+- entityhuman.heal(1.0F);
++ // CraftBukkit - added RegainReason
++ entityhuman.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED);
+ this.a(3.0F);
+ this.foodTickTimer = 0;
+ }
diff --git a/nms-patches/HandshakeListener.patch b/nms-patches/HandshakeListener.patch
new file mode 100644
index 00000000..651b7fee
--- /dev/null
+++ b/nms-patches/HandshakeListener.patch
@@ -0,0 +1,69 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/HandshakeListener.java 2014-11-27 08:59:46.757421600 +1100
++++ src/main/java/net/minecraft/server/HandshakeListener.java 2014-11-27 08:42:10.100851012 +1100
+@@ -1,6 +1,16 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.net.InetAddress;
++import java.util.HashMap;
++// CraftBukkit end
++
+ public class HandshakeListener implements PacketHandshakingInListener {
++
++ // CraftBukkit start - add fields
++ private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>();
++ private static int throttleCounter = 0;
++ // CraftBukkit end
+
+ private final MinecraftServer a;
+ private final NetworkManager b;
+@@ -15,6 +25,41 @@
+ case 1:
+ this.b.a(EnumProtocol.LOGIN);
+ ChatComponentText chatcomponenttext;
++
++ // CraftBukkit start - Connection throttle
++ try {
++ long currentTime = System.currentTimeMillis();
++ long connectionThrottle = MinecraftServer.getServer().server.getConnectionThrottle();
++ InetAddress address = ((java.net.InetSocketAddress) this.b.getSocketAddress()).getAddress();
++
++ synchronized (throttleTracker) {
++ if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) {
++ throttleTracker.put(address, currentTime);
++ chatcomponenttext = new ChatComponentText("Connection throttled! Please wait before reconnecting.");
++ this.b.handle(new PacketLoginOutDisconnect(chatcomponenttext));
++ this.b.close(chatcomponenttext);
++ return;
++ }
++
++ throttleTracker.put(address, currentTime);
++ throttleCounter++;
++ if (throttleCounter > 200) {
++ throttleCounter = 0;
++
++ // Cleanup stale entries
++ java.util.Iterator iter = throttleTracker.entrySet().iterator();
++ while (iter.hasNext()) {
++ java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next();
++ if (entry.getValue() > connectionThrottle) {
++ iter.remove();
++ }
++ }
++ }
++ }
++ } catch (Throwable t) {
++ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
++ }
++ // CraftBukkit end
+
+ if (packethandshakinginsetprotocol.b() > 47) {
+ chatcomponenttext = new ChatComponentText("Outdated server! I\'m still on 1.8");
+@@ -26,6 +71,7 @@
+ this.b.close(chatcomponenttext);
+ } else {
+ this.b.a((PacketListener) (new LoginListener(this.a, this.b)));
++ ((LoginListener) this.b.getPacketListener()).hostname = packethandshakinginsetprotocol.b + ":" + packethandshakinginsetprotocol.c; // CraftBukkit - set hostname
+ }
+ break;
+
diff --git a/nms-patches/IDataManager.patch b/nms-patches/IDataManager.patch
new file mode 100644
index 00000000..0c8a3723
--- /dev/null
+++ b/nms-patches/IDataManager.patch
@@ -0,0 +1,18 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/IDataManager.java 2014-11-27 08:59:46.757421600 +1100
++++ src/main/java/net/minecraft/server/IDataManager.java 2014-11-27 08:42:10.104851005 +1100
+@@ -6,7 +6,7 @@
+
+ WorldData getWorldData();
+
+- void checkSession();
++ void checkSession() throws ExceptionWorldConflict; // CraftBukkit - throws ExceptionWorldConflict
+
+ IChunkLoader createChunkLoader(WorldProvider worldprovider);
+
+@@ -23,4 +23,6 @@
+ File getDataFile(String s);
+
+ String g();
++
++ java.util.UUID getUUID(); // CraftBukkit
+ }
diff --git a/nms-patches/IInventory.patch b/nms-patches/IInventory.patch
new file mode 100644
index 00000000..d65dcde8
--- /dev/null
+++ b/nms-patches/IInventory.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/IInventory.java 2014-11-27 08:59:46.757421600 +1100
++++ src/main/java/net/minecraft/server/IInventory.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.craftbukkit.entity.CraftHumanEntity; // CraftBukkit
++
+ public interface IInventory extends INamableTileEntity {
+
+ int getSize();
+@@ -31,4 +33,20 @@
+ int g();
+
+ void l();
++
++ // CraftBukkit start
++ ItemStack[] getContents();
++
++ void onOpen(CraftHumanEntity who);
++
++ void onClose(CraftHumanEntity who);
++
++ java.util.List<org.bukkit.entity.HumanEntity> getViewers();
++
++ org.bukkit.inventory.InventoryHolder getOwner();
++
++ void setMaxStackSize(int size);
++
++ int MAX_STACK = 64;
++ // CraftBukkit end
+ }
diff --git a/nms-patches/IRecipe.patch b/nms-patches/IRecipe.patch
new file mode 100644
index 00000000..cdc66801
--- /dev/null
+++ b/nms-patches/IRecipe.patch
@@ -0,0 +1,9 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/IRecipe.java 2014-11-27 08:59:46.769421547 +1100
++++ src/main/java/net/minecraft/server/IRecipe.java 2014-11-27 08:42:10.168850880 +1100
+@@ -11,4 +11,6 @@
+ ItemStack b();
+
+ ItemStack[] b(InventoryCrafting inventorycrafting);
++
++ org.bukkit.inventory.Recipe toBukkitRecipe(); // CraftBukkit
+ }
diff --git a/nms-patches/InventoryCraftResult.patch b/nms-patches/InventoryCraftResult.patch
new file mode 100644
index 00000000..af51f97f
--- /dev/null
+++ b/nms-patches/InventoryCraftResult.patch
@@ -0,0 +1,48 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryCraftResult.java 2014-11-27 08:59:46.761421583 +1100
++++ src/main/java/net/minecraft/server/InventoryCraftResult.java 2014-11-27 08:42:10.140850934 +1100
+@@ -1,8 +1,36 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventoryCraftResult implements IInventory {
+
+ private ItemStack[] items = new ItemStack[1];
++
++ // CraftBukkit start
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return null; // Result slots don't get an owner
++ }
++
++ // Don't need a transaction; the InventoryCrafting keeps track of it for us
++ public void onOpen(CraftHumanEntity who) {}
++ public void onClose(CraftHumanEntity who) {}
++ public java.util.List<HumanEntity> getViewers() {
++ return new java.util.ArrayList<HumanEntity>();
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public InventoryCraftResult() {}
+
+@@ -53,7 +81,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public void update() {}
diff --git a/nms-patches/InventoryCrafting.patch b/nms-patches/InventoryCrafting.patch
new file mode 100644
index 00000000..84e27a99
--- /dev/null
+++ b/nms-patches/InventoryCrafting.patch
@@ -0,0 +1,64 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryCrafting.java 2014-11-27 08:59:46.761421583 +1100
++++ src/main/java/net/minecraft/server/InventoryCrafting.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,11 +1,61 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.InventoryType;
++// CraftBukkit end
++
+ public class InventoryCrafting implements IInventory {
+
+ private final ItemStack[] items;
+ private final int b;
+ private final int c;
+ private final Container d;
++
++ // CraftBukkit start - add fields
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ public IRecipe currentRecipe;
++ public IInventory resultInventory;
++ private EntityHuman owner;
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public InventoryType getInvType() {
++ return items.length == 4 ? InventoryType.CRAFTING : InventoryType.WORKBENCH;
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return owner.getBukkitEntity();
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ resultInventory.setMaxStackSize(size);
++ }
++
++ public InventoryCrafting(Container container, int i, int j, EntityHuman player) {
++ this(container, i, j);
++ this.owner = player;
++ }
++ // CraftBukkit end
+
+ public InventoryCrafting(Container container, int i, int j) {
+ int k = i * j;
diff --git a/nms-patches/InventoryEnderChest.patch b/nms-patches/InventoryEnderChest.patch
new file mode 100644
index 00000000..ee181fb3
--- /dev/null
+++ b/nms-patches/InventoryEnderChest.patch
@@ -0,0 +1,51 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryEnderChest.java 2014-11-27 08:59:46.761421583 +1100
++++ src/main/java/net/minecraft/server/InventoryEnderChest.java 2014-11-27 08:42:10.096851020 +1100
+@@ -1,8 +1,48 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventoryEnderChest extends InventorySubcontainer {
+
+ private TileEntityEnderChest a;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ public org.bukkit.entity.Player player;
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return this.player;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++ // CraftBukkit end
+
+ public InventoryEnderChest() {
+ super("container.enderchest", false, 27);
diff --git a/nms-patches/InventoryHorseChest.patch b/nms-patches/InventoryHorseChest.patch
new file mode 100644
index 00000000..4146987e
--- /dev/null
+++ b/nms-patches/InventoryHorseChest.patch
@@ -0,0 +1,63 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryHorseChest.java 2014-11-27 08:59:46.765421565 +1100
++++ src/main/java/net/minecraft/server/InventoryHorseChest.java 2014-11-27 08:42:10.172850872 +1100
+@@ -1,8 +1,60 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventoryHorseChest extends InventorySubcontainer {
+
+ public InventoryHorseChest(String s, int i) {
+ super(s, false, i);
+ }
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private EntityHorse horse;
++ private int maxStack = MAX_STACK;
++
++ public InventoryHorseChest(String s, int i, EntityHorse horse) {
++ super(s, false, i, (org.bukkit.craftbukkit.entity.CraftHorse) horse.getBukkitEntity());
++ this.horse = horse;
++ }
++
++ @Override
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return (org.bukkit.entity.Horse) this.horse.getBukkitEntity();
++ }
++
++ @Override
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/InventoryLargeChest.patch b/nms-patches/InventoryLargeChest.patch
new file mode 100644
index 00000000..2e1b2eaa
--- /dev/null
+++ b/nms-patches/InventoryLargeChest.patch
@@ -0,0 +1,66 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryLargeChest.java 2014-11-27 08:59:46.765421565 +1100
++++ src/main/java/net/minecraft/server/InventoryLargeChest.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,10 +1,54 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventoryLargeChest implements ITileInventory {
+
+ private String a;
+ public ITileInventory left;
+ public ITileInventory right;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++
++ public ItemStack[] getContents() {
++ ItemStack[] result = new ItemStack[this.getSize()];
++ for (int i = 0; i < result.length; i++) {
++ result[i] = this.getItem(i);
++ }
++ return result;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ this.left.onOpen(who);
++ this.right.onOpen(who);
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ this.left.onClose(who);
++ this.right.onClose(who);
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return null; // This method won't be called since CraftInventoryDoubleChest doesn't defer to here
++ }
++
++ public void setMaxStackSize(int size) {
++ this.left.setMaxStackSize(size);
++ this.right.setMaxStackSize(size);
++ }
++ // CraftBukkit end
+
+ public InventoryLargeChest(String s, ITileInventory itileinventory, ITileInventory itileinventory1) {
+ this.a = s;
+@@ -68,7 +112,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return this.left.getMaxStackSize();
++ return Math.min(this.left.getMaxStackSize(), this.right.getMaxStackSize()); // CraftBukkit - check both sides
+ }
+
+ public void update() {
diff --git a/nms-patches/InventoryMerchant.patch b/nms-patches/InventoryMerchant.patch
new file mode 100644
index 00000000..bc24b3dc
--- /dev/null
+++ b/nms-patches/InventoryMerchant.patch
@@ -0,0 +1,59 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventoryMerchant.java 2014-11-27 08:59:46.769421547 +1100
++++ src/main/java/net/minecraft/server/InventoryMerchant.java 2014-11-27 08:42:10.136850942 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventoryMerchant implements IInventory {
+
+ private final IMerchant merchant;
+@@ -8,6 +14,35 @@
+ private MerchantRecipe recipe;
+ private int e;
+
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.itemsInSlots;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return player.getBukkitEntity();
++ }
++ // CraftBukkit end
++
+ public InventoryMerchant(EntityHuman entityhuman, IMerchant imerchant) {
+ this.player = entityhuman;
+ this.merchant = imerchant;
+@@ -94,7 +129,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean a(EntityHuman entityhuman) {
diff --git a/nms-patches/InventorySubcontainer.patch b/nms-patches/InventorySubcontainer.patch
new file mode 100644
index 00000000..ddacf613
--- /dev/null
+++ b/nms-patches/InventorySubcontainer.patch
@@ -0,0 +1,59 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/InventorySubcontainer.java 2014-11-27 08:59:46.769421547 +1100
++++ src/main/java/net/minecraft/server/InventorySubcontainer.java 2014-11-27 08:42:10.088851036 +1100
+@@ -3,6 +3,12 @@
+ import com.google.common.collect.Lists;
+ import java.util.List;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class InventorySubcontainer implements IInventory {
+
+ private String a;
+@@ -10,8 +16,43 @@
+ public ItemStack[] items;
+ private List d;
+ private boolean e;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++ protected org.bukkit.inventory.InventoryHolder bukkitOwner;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
+
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return bukkitOwner;
++ }
++
+ public InventorySubcontainer(String s, boolean flag, int i) {
++ this(s, flag, i, null);
++ }
++
++ public InventorySubcontainer(String s, boolean flag, int i, org.bukkit.inventory.InventoryHolder owner) { // Added argument
++ this.bukkitOwner = owner;
++ // CraftBukkit end
+ this.a = s;
+ this.e = flag;
+ this.b = i;
diff --git a/nms-patches/ItemBoat.patch b/nms-patches/ItemBoat.patch
new file mode 100644
index 00000000..29142514
--- /dev/null
+++ b/nms-patches/ItemBoat.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemBoat.java 2014-11-27 08:59:46.773421530 +1100
++++ src/main/java/net/minecraft/server/ItemBoat.java 2014-11-27 08:42:10.156850903 +1100
+@@ -53,6 +53,14 @@
+ } else {
+ if (movingobjectposition.type == EnumMovingObjectType.BLOCK) {
+ BlockPosition blockposition = movingobjectposition.a();
++
++ // CraftBukkit start - Boat placement
++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(entityhuman, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, blockposition, movingobjectposition.direction, itemstack);
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++ // CraftBukkit end
+
+ if (world.getType(blockposition).getBlock() == Blocks.SNOW_LAYER) {
+ blockposition = blockposition.down();
diff --git a/nms-patches/ItemBow.patch b/nms-patches/ItemBow.patch
new file mode 100644
index 00000000..9f8c9f51
--- /dev/null
+++ b/nms-patches/ItemBow.patch
@@ -0,0 +1,49 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemBow.java 2014-11-27 08:59:46.773421530 +1100
++++ src/main/java/net/minecraft/server/ItemBow.java 2014-11-27 08:42:10.140850934 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.EntityCombustEvent; // CraftBukkit
++
+ public class ItemBow extends Item {
+
+ public static final String[] a = new String[] { "pulling_0", "pulling_1", "pulling_2"};
+@@ -45,9 +47,28 @@
+ }
+
+ if (EnchantmentManager.getEnchantmentLevel(Enchantment.ARROW_FIRE.id, itemstack) > 0) {
+- entityarrow.setOnFire(100);
++ // CraftBukkit start - call EntityCombustEvent
++ EntityCombustEvent event = new EntityCombustEvent(entityarrow.getBukkitEntity(), 100);
++ entityarrow.world.getServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entityarrow.setOnFire(event.getDuration());
++ }
++ // CraftBukkit end
++ }
++
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityhuman, itemstack, entityarrow, f);
++ if (event.isCancelled()) {
++ event.getProjectile().remove();
++ return;
+ }
+
++ if (event.getProjectile() == entityarrow.getBukkitEntity()) {
++ world.addEntity(entityarrow);
++ }
++ // CraftBukkit end
++
+ itemstack.damage(1, entityhuman);
+ world.makeSound(entityhuman, "random.bow", 1.0F, 1.0F / (ItemBow.g.nextFloat() * 0.4F + 1.2F) + f * 0.5F);
+ if (flag) {
+@@ -58,7 +79,7 @@
+
+ entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this)]);
+ if (!world.isStatic) {
+- world.addEntity(entityarrow);
++ // world.addEntity(entityarrow); // CraftBukkit - moved up
+ }
+ }
+
diff --git a/nms-patches/ItemBucket.patch b/nms-patches/ItemBucket.patch
new file mode 100644
index 00000000..d037c57b
--- /dev/null
+++ b/nms-patches/ItemBucket.patch
@@ -0,0 +1,102 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemBucket.java 2014-11-27 08:59:46.777421512 +1100
++++ src/main/java/net/minecraft/server/ItemBucket.java 2014-11-27 08:42:10.156850903 +1100
+@@ -1,5 +1,12 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerBucketEmptyEvent;
++import org.bukkit.event.player.PlayerBucketFillEvent;
++// CraftBukkit end
++
+ public class ItemBucket extends Item {
+
+ private Block a;
+@@ -33,19 +40,41 @@
+ Material material = iblockdata.getBlock().getMaterial();
+
+ if (material == Material.WATER && ((Integer) iblockdata.get(BlockFluids.LEVEL)).intValue() == 0) {
++ // CraftBukkit start
++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), null, itemstack, Items.WATER_BUCKET);
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++ // CraftBukkit end
+ world.setAir(blockposition);
+ entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this)]);
+- return this.a(itemstack, entityhuman, Items.WATER_BUCKET);
++ return this.a(itemstack, entityhuman, Items.WATER_BUCKET, event.getItemStack()); // CraftBukkit - added Event stack
+ }
+
+ if (material == Material.LAVA && ((Integer) iblockdata.get(BlockFluids.LEVEL)).intValue() == 0) {
++ // CraftBukkit start
++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), null, itemstack, Items.LAVA_BUCKET);
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++ // CraftBukkit end
+ world.setAir(blockposition);
+ entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this)]);
+- return this.a(itemstack, entityhuman, Items.LAVA_BUCKET);
++ return this.a(itemstack, entityhuman, Items.LAVA_BUCKET, event.getItemStack()); // CraftBukkit - added Event stack
+ }
+ } else {
+ if (this.a == Blocks.AIR) {
+- return new ItemStack(Items.BUCKET);
++ // CraftBukkit start
++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), movingobjectposition.direction, itemstack);
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ return CraftItemStack.asNMSCopy(event.getItemStack());
++ // CraftBukkit end
+ }
+
+ BlockPosition blockposition1 = blockposition.shift(movingobjectposition.direction);
+@@ -53,10 +82,18 @@
+ if (!entityhuman.a(blockposition1, movingobjectposition.direction, itemstack)) {
+ return itemstack;
+ }
++
++ // CraftBukkit start
++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), movingobjectposition.direction, itemstack);
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++ // CraftBukkit end
+
+ if (this.a(world, blockposition1) && !entityhuman.abilities.canInstantlyBuild) {
+ entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this)]);
+- return new ItemStack(Items.BUCKET);
++ return CraftItemStack.asNMSCopy(event.getItemStack()); // CraftBukkit
+ }
+ }
+ }
+@@ -64,15 +101,16 @@
+ return itemstack;
+ }
+ }
+-
+- private ItemStack a(ItemStack itemstack, EntityHuman entityhuman, Item item) {
++
++ // CraftBukkit - added ob.ItemStack result - TODO: Is this... the right way to handle this?
++ private ItemStack a(ItemStack itemstack, EntityHuman entityhuman, Item item, org.bukkit.inventory.ItemStack result) {
+ if (entityhuman.abilities.canInstantlyBuild) {
+ return itemstack;
+ } else if (--itemstack.count <= 0) {
+- return new ItemStack(item);
++ return CraftItemStack.asNMSCopy(result); // CraftBukkit
+ } else {
+- if (!entityhuman.inventory.pickup(new ItemStack(item))) {
+- entityhuman.drop(new ItemStack(item, 1, 0), false);
++ if (!entityhuman.inventory.pickup(CraftItemStack.asNMSCopy(result))) {
++ entityhuman.drop(CraftItemStack.asNMSCopy(result), false);
+ }
+
+ return itemstack;
diff --git a/nms-patches/ItemDye.patch b/nms-patches/ItemDye.patch
new file mode 100644
index 00000000..8586e786
--- /dev/null
+++ b/nms-patches/ItemDye.patch
@@ -0,0 +1,28 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemDye.java 2014-11-27 08:59:46.777421512 +1100
++++ src/main/java/net/minecraft/server/ItemDye.java 2014-11-27 08:42:10.160850895 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.entity.SheepDyeWoolEvent; // CraftBukkit
++
+ public class ItemDye extends Item {
+
+ public static final int[] a = new int[] { 1973019, 11743532, 3887386, 5320730, 2437522, 8073150, 2651799, 11250603, 4408131, 14188952, 4312372, 14602026, 6719955, 12801229, 15435844, 15790320};
+@@ -89,6 +91,17 @@
+ EnumColor enumcolor = EnumColor.fromInvColorIndex(itemstack.getData());
+
+ if (!entitysheep.isSheared() && entitysheep.getColor() != enumcolor) {
++ // CraftBukkit start
++ byte bColor = (byte) enumcolor.getColorIndex();
++ SheepDyeWoolEvent event = new SheepDyeWoolEvent((org.bukkit.entity.Sheep) entitysheep.getBukkitEntity(), org.bukkit.DyeColor.getByData(bColor));
++ entitysheep.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ enumcolor = EnumColor.fromColorIndex((byte) event.getColor().getWoolData());
++ // CraftBukkit end
+ entitysheep.setColor(enumcolor);
+ --itemstack.count;
+ }
diff --git a/nms-patches/ItemFireball.patch b/nms-patches/ItemFireball.patch
new file mode 100644
index 00000000..6a754928
--- /dev/null
+++ b/nms-patches/ItemFireball.patch
@@ -0,0 +1,19 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemFireball.java 2014-11-27 08:59:46.777421512 +1100
++++ src/main/java/net/minecraft/server/ItemFireball.java 2014-11-27 08:42:10.124850965 +1100
+@@ -15,7 +15,15 @@
+ return false;
+ } else {
+ if (world.getType(blockposition).getBlock().getMaterial() == Material.AIR) {
+- world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "item.fireCharge.use", 1.0F, (ItemFireball.g.nextFloat() - ItemFireball.g.nextFloat()) * 0.2F + 1.0F);
++ // CraftBukkit start - fire BlockIgniteEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, entityhuman).isCancelled()) {
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ --itemstack.count;
++ }
++ return false;
++ }
++ // CraftBukkit end
++ world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "item.fireCharge.use", 1.0F, (g.nextFloat() - g.nextFloat()) * 0.2F + 1.0F);
+ world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
+ }
+
diff --git a/nms-patches/ItemFishingRod.patch b/nms-patches/ItemFishingRod.patch
new file mode 100644
index 00000000..564c24b4
--- /dev/null
+++ b/nms-patches/ItemFishingRod.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemFishingRod.java 2014-11-27 08:59:46.781421494 +1100
++++ src/main/java/net/minecraft/server/ItemFishingRod.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.player.PlayerFishEvent; // CraftBukkit
++
+ public class ItemFishingRod extends Item {
+
+ public ItemFishingRod() {
+@@ -15,9 +17,18 @@
+ itemstack.damage(i, entityhuman);
+ entityhuman.bv();
+ } else {
+- world.makeSound(entityhuman, "random.bow", 0.5F, 0.4F / (ItemFishingRod.g.nextFloat() * 0.4F + 0.8F));
++ // CraftBukkit start
++ EntityFishingHook hook = new EntityFishingHook(world, entityhuman);
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), null, (org.bukkit.entity.Fish) hook.getBukkitEntity(), PlayerFishEvent.State.FISHING);
++ world.getServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return itemstack;
++ }
++ // CraftBukkit end
++ world.makeSound(entityhuman, "random.bow", 0.5F, 0.4F / (g.nextFloat() * 0.4F + 0.8F));
+ if (!world.isStatic) {
+- world.addEntity(new EntityFishingHook(world, entityhuman));
++ world.addEntity(hook); // CraftBukkit - moved creation up
+ }
+
+ entityhuman.bv();
diff --git a/nms-patches/ItemFlintAndSteel.patch b/nms-patches/ItemFlintAndSteel.patch
new file mode 100644
index 00000000..a27cc0b6
--- /dev/null
+++ b/nms-patches/ItemFlintAndSteel.patch
@@ -0,0 +1,47 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemFlintAndSteel.java 2014-11-27 08:59:46.781421494 +1100
++++ src/main/java/net/minecraft/server/ItemFlintAndSteel.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class ItemFlintAndSteel extends Item {
+
+ public ItemFlintAndSteel() {
+@@ -9,13 +14,32 @@
+ }
+
+ public boolean interactWith(ItemStack itemstack, EntityHuman entityhuman, World world, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2) {
++ BlockPosition clicked = blockposition; // CraftBukkit
+ blockposition = blockposition.shift(enumdirection);
+ if (!entityhuman.a(blockposition, enumdirection, itemstack)) {
+ return false;
+ } else {
+ if (world.getType(blockposition).getBlock().getMaterial() == Material.AIR) {
+- world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "fire.ignite", 1.0F, ItemFlintAndSteel.g.nextFloat() * 0.4F + 0.8F);
++ // CraftBukkit start - Store the clicked block
++ if (CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) {
++ itemstack.damage(1, entityhuman);
++ return false;
++ }
++
++ CraftBlockState blockState = CraftBlockState.getBlockState(world, blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ // CraftBukkit end
++
++ world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "fire.ignite", 1.0F, g.nextFloat() * 0.4F + 0.8F);
+ world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
++
++ // CraftBukkit start
++ org.bukkit.event.block.BlockPlaceEvent placeEvent = CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockState, clicked.getX(), clicked.getY(), clicked.getZ());
++
++ if (placeEvent.isCancelled() || !placeEvent.canBuild()) {
++ placeEvent.getBlockPlaced().setTypeIdAndData(0, (byte) 0, false);
++ return false;
++ }
++ // CraftBukkit end
+ }
+
+ itemstack.damage(1, entityhuman);
diff --git a/nms-patches/ItemHanging.patch b/nms-patches/ItemHanging.patch
new file mode 100644
index 00000000..0a312a80
--- /dev/null
+++ b/nms-patches/ItemHanging.patch
@@ -0,0 +1,41 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemHanging.java 2014-11-27 08:59:46.785421476 +1100
++++ src/main/java/net/minecraft/server/ItemHanging.java 2014-11-27 08:42:10.144850927 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.event.hanging.HangingPlaceEvent;
++import org.bukkit.event.painting.PaintingPlaceEvent;
++// CraftBukkit end
++
+ public class ItemHanging extends Item {
+
+ private final Class a;
+@@ -24,6 +30,26 @@
+
+ if (entityhanging != null && entityhanging.survives()) {
+ if (!world.isStatic) {
++ // CraftBukkit start - fire HangingPlaceEvent
++ Player who = (entityhuman == null) ? null : (Player) entityhuman.getBukkitEntity();
++ org.bukkit.block.Block blockClicked = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection);
++
++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityhanging.getBukkitEntity(), who, blockClicked, blockFace);
++ world.getServer().getPluginManager().callEvent(event);
++
++ PaintingPlaceEvent paintingEvent = null;
++ if (entityhanging instanceof EntityPainting) {
++ // Fire old painting event until it can be removed
++ paintingEvent = new PaintingPlaceEvent((org.bukkit.entity.Painting) entityhanging.getBukkitEntity(), who, blockClicked, blockFace);
++ paintingEvent.setCancelled(event.isCancelled());
++ world.getServer().getPluginManager().callEvent(paintingEvent);
++ }
++
++ if (event.isCancelled() || (paintingEvent != null && paintingEvent.isCancelled())) {
++ return false;
++ }
++ // CraftBukkit end
+ world.addEntity(entityhanging);
+ }
+
diff --git a/nms-patches/ItemLeash.patch b/nms-patches/ItemLeash.patch
new file mode 100644
index 00000000..2836ac93
--- /dev/null
+++ b/nms-patches/ItemLeash.patch
@@ -0,0 +1,35 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemLeash.java 2014-11-27 08:59:46.785421476 +1100
++++ src/main/java/net/minecraft/server/ItemLeash.java 2014-11-27 08:42:10.084851043 +1100
+@@ -3,6 +3,8 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.event.hanging.HangingPlaceEvent; // CraftBukkit
++
+ public class ItemLeash extends Item {
+
+ public ItemLeash() {
+@@ -40,7 +42,23 @@
+ if (entityinsentient.cb() && entityinsentient.getLeashHolder() == entityhuman) {
+ if (entityleash == null) {
+ entityleash = EntityLeash.a(world, blockposition);
++
++ // CraftBukkit start - fire HangingPlaceEvent
++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityleash.getBukkitEntity(), entityhuman != null ? (org.bukkit.entity.Player) entityhuman.getBukkitEntity() : null, world.getWorld().getBlockAt(i, j, k), org.bukkit.block.BlockFace.SELF);
++ world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ entityleash.die();
++ return false;
++ }
++ // CraftBukkit end
++ }
++
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(entityinsentient, entityleash, entityhuman).isCancelled()) {
++ continue;
+ }
++ // CraftBukkit end
+
+ entityinsentient.setLeashHolder(entityleash, true);
+ flag = true;
diff --git a/nms-patches/ItemMapEmpty.patch b/nms-patches/ItemMapEmpty.patch
new file mode 100644
index 00000000..9159a106
--- /dev/null
+++ b/nms-patches/ItemMapEmpty.patch
@@ -0,0 +1,24 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemMapEmpty.java 2014-11-27 08:59:46.785421476 +1100
++++ src/main/java/net/minecraft/server/ItemMapEmpty.java 2014-11-27 08:42:10.164850887 +1100
+@@ -7,15 +7,19 @@
+ }
+
+ public ItemStack a(ItemStack itemstack, World world, EntityHuman entityhuman) {
+- ItemStack itemstack1 = new ItemStack(Items.FILLED_MAP, 1, world.b("map"));
++ World worldMain = world.getServer().getServer().worlds.get(0); // CraftBukkit - store reference to primary world
++ ItemStack itemstack1 = new ItemStack(Items.FILLED_MAP, 1, worldMain.b("map")); // CraftBukkit - use primary world for maps
+ String s = "map_" + itemstack1.getData();
+ WorldMap worldmap = new WorldMap(s);
+
+ world.a(s, (PersistentBase) worldmap);
+ worldmap.scale = 0;
+ worldmap.a(entityhuman.locX, entityhuman.locZ, worldmap.scale);
+- worldmap.map = (byte) world.worldProvider.getDimension();
++ worldmap.map = (byte) ((WorldServer) world).dimension; // CraftBukkit - use bukkit dimension
+ worldmap.c();
++
++ org.bukkit.craftbukkit.event.CraftEventFactory.callEvent(new org.bukkit.event.server.MapInitializeEvent(worldmap.mapView)); // CraftBukkit
++
+ --itemstack.count;
+ if (itemstack.count <= 0) {
+ return itemstack1;
diff --git a/nms-patches/ItemMinecart.patch b/nms-patches/ItemMinecart.patch
new file mode 100644
index 00000000..5cff838f
--- /dev/null
+++ b/nms-patches/ItemMinecart.patch
@@ -0,0 +1,16 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemMinecart.java 2014-11-27 08:59:46.789421459 +1100
++++ src/main/java/net/minecraft/server/ItemMinecart.java 2014-11-27 08:42:10.168850880 +1100
+@@ -23,6 +23,13 @@
+ if (enumtrackposition.c()) {
+ d0 = 0.5D;
+ }
++ // CraftBukkit start - Minecarts
++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(entityhuman, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, blockposition, enumdirection, itemstack);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
+
+ EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.0625D + d0, (double) blockposition.getZ() + 0.5D, this.b);
+
diff --git a/nms-patches/ItemMonsterEgg.patch b/nms-patches/ItemMonsterEgg.patch
new file mode 100644
index 00000000..3a5fbee6
--- /dev/null
+++ b/nms-patches/ItemMonsterEgg.patch
@@ -0,0 +1,25 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemMonsterEgg.java 2014-11-27 08:59:46.789421459 +1100
++++ src/main/java/net/minecraft/server/ItemMonsterEgg.java 2014-11-27 08:42:10.172850872 +1100
+@@ -19,7 +19,8 @@
+ }
+
+ public boolean interactWith(ItemStack itemstack, EntityHuman entityhuman, World world, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2) {
+- if (world.isStatic) {
++ // CraftBukkit - check ItemStack data
++ if (world.isStatic || itemstack.getData() == 48 || itemstack.getData() == 49 || itemstack.getData() == 63 || itemstack.getData() == 64) {
+ return true;
+ } else if (!entityhuman.a(blockposition.shift(enumdirection), enumdirection, itemstack)) {
+ return false;
+@@ -109,6 +110,12 @@
+ }
+
+ 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.eggInfo.containsKey(Integer.valueOf(i))) {
+ return null;
+ } else {
diff --git a/nms-patches/ItemStack.patch b/nms-patches/ItemStack.patch
new file mode 100644
index 00000000..da50d5cf
--- /dev/null
+++ b/nms-patches/ItemStack.patch
@@ -0,0 +1,219 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemStack.java 2014-11-27 08:59:46.789421459 +1100
++++ src/main/java/net/minecraft/server/ItemStack.java 2014-11-27 08:42:10.156850903 +1100
+@@ -5,6 +5,18 @@
+ import java.text.DecimalFormat;
+ import java.util.Random;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.Location;
++import org.bukkit.TreeType;
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.entity.Player;
++import org.bukkit.event.world.StructureGrowEvent;
++// CraftBukkit end
++
+ public final class ItemStack {
+
+ public static final DecimalFormat a = new DecimalFormat("#.###");
+@@ -46,11 +58,13 @@
+ this.k = false;
+ this.item = item;
+ this.count = i;
+- this.damage = j;
+- if (this.damage < 0) {
+- this.damage = 0;
+- }
+-
++ // CraftBukkit start - Pass to setData to do filtering
++ this.setData(j);
++ //this.damage = j;
++ //if (this.damage < 0) {
++ // this.damage = 0;
++ //}
++ // CraftBukkit end
+ }
+
+ public static ItemStack createStack(NBTTagCompound nbttagcompound) {
+@@ -83,11 +97,96 @@
+ }
+
+ public boolean placeItem(EntityHuman entityhuman, World world, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2) {
++ // CraftBukkit start - handle all block place event logic here
++ int data = this.getData();
++ int count = this.count;
++
++ if (!(this.getItem() instanceof ItemBucket)) { // if not bucket
++ world.captureBlockStates = true;
++ // special case bonemeal
++ if (this.getItem() instanceof ItemDye && this.getData() == 15) {
++ Block block = world.getType(blockposition).getBlock();
++ if (block == Blocks.SAPLING || block instanceof BlockMushroom) {
++ world.captureTreeGeneration = true;
++ }
++ }
++ }
+ boolean flag = this.getItem().interactWith(this, entityhuman, world, blockposition, enumdirection, f, f1, f2);
++ int newData = this.getData();
++ int newCount = this.count;
++ this.count = count;
++ this.setData(data);
++ world.captureBlockStates = false;
++ if (flag && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
++ world.captureTreeGeneration = false;
++ Location location = new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ TreeType treeType = BlockSapling.treeType;
++ BlockSapling.treeType = null;
++ List<BlockState> blocks = (List<BlockState>) world.capturedBlockStates.clone();
++ world.capturedBlockStates.clear();
++ StructureGrowEvent event = null;
++ if (treeType != null) {
++ event = new StructureGrowEvent(location, treeType, false, (Player) entityhuman.getBukkitEntity(), blocks);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ }
++ if (event == null || !event.isCancelled()) {
++ // Change the stack to its new contents if it hasn't been tampered with.
++ if (this.count == count && this.getData() == data) {
++ this.setData(newData);
++ this.count = newCount;
++ }
++ for (BlockState blockstate : blocks) {
++ blockstate.update(true);
++ }
++ }
++
++ return flag;
++ }
++ world.captureTreeGeneration = false;
+
+ if (flag) {
+- entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this.item)]);
++ org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
++ List<BlockState> blocks = (List<BlockState>) world.capturedBlockStates.clone();
++ world.capturedBlockStates.clear();
++ if (blocks.size() > 1) {
++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ } else if (blocks.size() == 1) {
++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ }
++
++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++ flag = false; // cancel placement
++ // revert back all captured blocks
++ for (BlockState blockstate : blocks) {
++ blockstate.update(true, false);
++ }
++ } else {
++ // Change the stack to its new contents if it hasn't been tampered with.
++ if (this.count == count && this.getData() == data) {
++ this.setData(newData);
++ this.count = newCount;
++ }
++ for (BlockState blockstate : blocks) {
++ int x = blockstate.getX();
++ int y = blockstate.getY();
++ int z = blockstate.getZ();
++ int updateFlag = ((CraftBlockState) blockstate).getFlag();
++ org.bukkit.Material mat = blockstate.getType();
++ Block oldBlock = CraftMagicNumbers.getBlock(mat);
++ BlockPosition newblockposition = new BlockPosition(x, y, z);
++ IBlockData block = world.getType(newblockposition);
++
++ if (!(block instanceof BlockContainer)) { // Containers get placed automatically
++ block.getBlock().onPlace(world, newblockposition, block);
++ }
++
++ world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block.getBlock(), updateFlag); // send null chunk as chunk.k() returns false by this point
++ }
++ entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this.item)]);
++ }
+ }
++ world.capturedBlockStates.clear();
++ // CraftBukkit end
+
+ return flag;
+ }
+@@ -111,7 +210,7 @@
+ nbttagcompound.setByte("Count", (byte) this.count);
+ nbttagcompound.setShort("Damage", (short) this.damage);
+ if (this.tag != null) {
+- nbttagcompound.set("tag", this.tag);
++ nbttagcompound.set("tag", this.tag.clone()); // CraftBukkit - make defensive copy, data is going to another thread
+ }
+
+ return nbttagcompound;
+@@ -125,13 +224,18 @@
+ }
+
+ this.count = nbttagcompound.getByte("Count");
++ /* CraftBukkit start - Route through setData for filtering
+ this.damage = nbttagcompound.getShort("Damage");
+ if (this.damage < 0) {
+ this.damage = 0;
+ }
++ */
++ this.setData(nbttagcompound.getShort("Damage"));
++ // CraftBukkit end
+
+ if (nbttagcompound.hasKeyOfType("tag", 10)) {
+- this.tag = nbttagcompound.getCompound("tag");
++ // CraftBukkit - make defensive copy as this data may be coming from the save thread
++ this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone();
+ if (this.item != null) {
+ this.item.a(this.tag);
+ }
+@@ -168,8 +272,29 @@
+ }
+
+ public void setData(int i) {
++ // CraftBukkit start - Filter out data for items that shouldn't have it
++ // The crafting system uses this value for a special purpose so we have to allow it
++ if (i == 32767) {
++ this.damage = i;
++ return;
++ }
++
++ // Is this a block?
++ if (CraftMagicNumbers.getBlock(CraftMagicNumbers.getId(this.getItem())) != Blocks.AIR) {
++ // If vanilla doesn't use data on it don't allow any
++ if (!(this.usesData() || this.getItem().usesDurability())) {
++ i = 0;
++ }
++ }
++
++ // Filter invalid plant data
++ if (CraftMagicNumbers.getBlock(CraftMagicNumbers.getId(this.getItem())) == Blocks.DOUBLE_PLANT && (i > 5 || i < 0)) {
++ i = 0;
++ }
++ // CraftBukkit end
++
+ this.damage = i;
+- if (this.damage < 0) {
++ if (this.damage < -1) { // CraftBukkit
+ this.damage = 0;
+ }
+
+@@ -222,6 +347,12 @@
+ if (this.count < 0) {
+ this.count = 0;
+ }
++
++ // CraftBukkit start - Check for item breaking
++ if (this.count == 0 && entityliving instanceof EntityHuman) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((EntityHuman) entityliving, this);
++ }
++ // CraftBukkit end
+
+ this.damage = 0;
+ }
+@@ -489,6 +620,7 @@
+
+ public void setItem(Item item) {
+ this.item = item;
++ this.setData(this.getData()); // CraftBukkit - Set data again to ensure it is filtered properly
+ }
+
+ public IChatBaseComponent C() {
diff --git a/nms-patches/ItemWaterLily.patch b/nms-patches/ItemWaterLily.patch
new file mode 100644
index 00000000..2158f642
--- /dev/null
+++ b/nms-patches/ItemWaterLily.patch
@@ -0,0 +1,18 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemWaterLily.java 2014-11-27 08:59:46.793421441 +1100
++++ src/main/java/net/minecraft/server/ItemWaterLily.java 2014-11-27 08:42:10.152850911 +1100
+@@ -27,7 +27,15 @@
+ IBlockData iblockdata = world.getType(blockposition);
+
+ if (iblockdata.getBlock().getMaterial() == Material.WATER && ((Integer) iblockdata.get(BlockFluids.LEVEL)).intValue() == 0 && world.isEmpty(blockposition1)) {
++ // CraftBukkit start - special case for handling block placement with water lilies
++ org.bukkit.block.BlockState blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(world, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
+ world.setTypeUpdate(blockposition1, Blocks.WATERLILY.getBlockData());
++ org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockstate, blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++ blockstate.update(true, false);
++ return itemstack;
++ }
++ // CraftBukkit end
+ if (!entityhuman.abilities.canInstantlyBuild) {
+ --itemstack.count;
+ }
diff --git a/nms-patches/ItemWorldMap.patch b/nms-patches/ItemWorldMap.patch
new file mode 100644
index 00000000..f85596fd
--- /dev/null
+++ b/nms-patches/ItemWorldMap.patch
@@ -0,0 +1,75 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ItemWorldMap.java 2014-11-27 08:59:46.793421441 +1100
++++ src/main/java/net/minecraft/server/ItemWorldMap.java 2014-11-27 08:42:10.144850927 +1100
+@@ -4,6 +4,11 @@
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Multisets;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.event.server.MapInitializeEvent;
++// CraftBukkit end
++
+ public class ItemWorldMap extends ItemWorldMapBase {
+
+ protected ItemWorldMap() {
+@@ -11,25 +16,32 @@
+ }
+
+ public WorldMap getSavedMap(ItemStack itemstack, World world) {
++ World worldMain = world.getServer().getServer().worlds.get(0); // CraftBukkit - store reference to primary world
+ String s = "map_" + itemstack.getData();
+- WorldMap worldmap = (WorldMap) world.a(WorldMap.class, s);
++ WorldMap worldmap = (WorldMap) worldMain.a(WorldMap.class, s); // CraftBukkit - use primary world for maps
+
+ if (worldmap == null && !world.isStatic) {
+- itemstack.setData(world.b("map"));
++ itemstack.setData(worldMain.b("map")); // CraftBukkit - use primary world for maps
+ s = "map_" + itemstack.getData();
+ worldmap = new WorldMap(s);
+ worldmap.scale = 3;
+ worldmap.a((double) world.getWorldData().c(), (double) world.getWorldData().e(), worldmap.scale);
+- worldmap.map = (byte) world.worldProvider.getDimension();
++ worldmap.map = (byte) ((WorldServer) world).dimension; // CraftBukkit - fixes Bukkit multiworld maps
+ worldmap.c();
+- world.a(s, (PersistentBase) worldmap);
++ worldMain.a(s, (PersistentBase) worldmap); // CraftBukkit - use primary world for maps
++
++ // CraftBukkit start
++ MapInitializeEvent event = new MapInitializeEvent(worldmap.mapView);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
+ }
+
+ return worldmap;
+ }
+
+ public void a(World world, Entity entity, WorldMap worldmap) {
+- if (world.worldProvider.getDimension() == worldmap.map && entity instanceof EntityHuman) {
++ // CraftBukkit - world.worldProvider -> ((WorldServer) world)
++ if (((WorldServer) world).dimension == worldmap.map && entity instanceof EntityHuman) {
+ int i = 1 << worldmap.scale;
+ int j = worldmap.centerX;
+ int k = worldmap.centerZ;
+@@ -179,6 +191,8 @@
+ if (itemstack.hasTag() && itemstack.getTag().getBoolean("map_is_scaling")) {
+ WorldMap worldmap = Items.FILLED_MAP.getSavedMap(itemstack, world);
+
++ world = world.getServer().getServer().worlds.get(0); // CraftBukkit - use primary world for maps
++
+ itemstack.setData(world.b("map"));
+ WorldMap worldmap1 = new WorldMap("map_" + itemstack.getData());
+
+@@ -190,7 +204,12 @@
+ worldmap1.a((double) worldmap.centerX, (double) worldmap.centerZ, worldmap1.scale);
+ worldmap1.map = worldmap.map;
+ worldmap1.c();
+- world.a("map_" + itemstack.getData(), (PersistentBase) worldmap1);
++ world.a("map_" + itemstack.getData(), (PersistentBase) worldmap1);
++
++ // CraftBukkit start
++ MapInitializeEvent event = new MapInitializeEvent(worldmap1.mapView);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/JsonList.patch b/nms-patches/JsonList.patch
new file mode 100644
index 00000000..0dcf0352
--- /dev/null
+++ b/nms-patches/JsonList.patch
@@ -0,0 +1,33 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/JsonList.java 2014-11-27 08:59:46.797421424 +1100
++++ src/main/java/net/minecraft/server/JsonList.java 2014-11-27 08:42:10.168850880 +1100
+@@ -79,6 +79,12 @@
+ public String[] getEntries() {
+ return (String[]) this.d.keySet().toArray(new String[this.d.size()]);
+ }
++
++ // CraftBukkit start
++ public Collection<JsonListEntry> getValues() {
++ return this.d.values();
++ }
++ // CraftBukkit end
+
+ public boolean isEmpty() {
+ return this.d.size() < 1;
+@@ -122,7 +128,7 @@
+ return this.d;
+ }
+
+- public void save() {
++ public void save() throws IOException { // CraftBukkit - Added throws
+ Collection collection = this.d.values();
+ String s = this.b.toJson(collection);
+ BufferedWriter bufferedwriter = null;
+@@ -136,7 +142,7 @@
+
+ }
+
+- public void load() {
++ public void load() throws IOException { // CraftBukkit - Added throws
+ Collection collection = null;
+ BufferedReader bufferedreader = null;
+
diff --git a/nms-patches/LoginListener.patch b/nms-patches/LoginListener.patch
new file mode 100644
index 00000000..b80cdad0
--- /dev/null
+++ b/nms-patches/LoginListener.patch
@@ -0,0 +1,35 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/LoginListener.java 2014-11-27 08:59:46.797421424 +1100
++++ src/main/java/net/minecraft/server/LoginListener.java 2014-11-27 08:42:10.172850872 +1100
+@@ -26,6 +26,7 @@
+ private GameProfile i;
+ private String j;
+ private SecretKey loginKey;
++ public String hostname = ""; // CraftBukkit - add field
+
+ public LoginListener(MinecraftServer minecraftserver, NetworkManager networkmanager) {
+ this.g = EnumProtocolState.HELLO;
+@@ -64,10 +65,12 @@
+ this.i = this.a(this.i);
+ }
+
+- String s = this.server.getPlayerList().attemptLogin(this.networkManager.getSocketAddress(), this.i);
++ // CraftBukkit start - fire PlayerLoginEvent
++ EntityPlayer s = this.server.getPlayerList().attemptLogin(this, this.i, hostname);
+
+- if (s != null) {
+- this.disconnect(s);
++ if (s == null) {
++ // this.disconnect(s);
++ // CraftBukkit end
+ } else {
+ this.g = EnumProtocolState.ACCEPTED;
+ if (this.server.aI() >= 0 && !this.networkManager.c()) {
+@@ -75,7 +78,7 @@
+ }
+
+ this.networkManager.handle(new PacketLoginOutSuccess(this.i));
+- this.server.getPlayerList().a(this.networkManager, this.server.getPlayerList().processLogin(this.i));
++ this.server.getPlayerList().a(this.networkManager, this.server.getPlayerList().processLogin(this.i, s)); // CraftBukkit - add player reference
+ }
+
+ }
diff --git a/nms-patches/MethodProfiler.patch b/nms-patches/MethodProfiler.patch
new file mode 100644
index 00000000..62424983
--- /dev/null
+++ b/nms-patches/MethodProfiler.patch
@@ -0,0 +1,135 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/MethodProfiler.java 2014-11-27 08:59:46.797421424 +1100
++++ src/main/java/net/minecraft/server/MethodProfiler.java 2014-11-27 08:42:10.132850949 +1100
+@@ -10,129 +10,29 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start - Strip down to empty methods, performance cost
+ public class MethodProfiler {
+-
+- private static final Logger b = LogManager.getLogger();
+- private final List c = Lists.newArrayList();
+- private final List d = Lists.newArrayList();
+ public boolean a;
+- private String e = "";
+- private final Map f = Maps.newHashMap();
+
+ public MethodProfiler() {}
+
+ public void a() {
+- this.f.clear();
+- this.e = "";
+- this.c.clear();
+ }
+
+ public void a(String s) {
+- if (this.a) {
+- if (this.e.length() > 0) {
+- this.e = this.e + ".";
+- }
+-
+- this.e = this.e + s;
+- this.c.add(this.e);
+- this.d.add(Long.valueOf(System.nanoTime()));
+- }
+ }
+
+ public void b() {
+- if (this.a) {
+- long i = System.nanoTime();
+- long j = ((Long) this.d.remove(this.d.size() - 1)).longValue();
+-
+- this.c.remove(this.c.size() - 1);
+- long k = i - j;
+-
+- if (this.f.containsKey(this.e)) {
+- this.f.put(this.e, Long.valueOf(((Long) this.f.get(this.e)).longValue() + k));
+- } else {
+- this.f.put(this.e, Long.valueOf(k));
+- }
+-
+- if (k > 100000000L) {
+- MethodProfiler.b.warn("Something\'s taking too long! \'" + this.e + "\' took aprox " + (double) k / 1000000.0D + " ms");
+- }
+-
+- this.e = !this.c.isEmpty() ? (String) this.c.get(this.c.size() - 1) : "";
+- }
+ }
+
+ public List b(String s) {
+- if (!this.a) {
+- return null;
+- } else {
+- long i = this.f.containsKey("root") ? ((Long) this.f.get("root")).longValue() : 0L;
+- long j = this.f.containsKey(s) ? ((Long) this.f.get(s)).longValue() : -1L;
+- ArrayList arraylist = Lists.newArrayList();
+-
+- if (s.length() > 0) {
+- s = s + ".";
+- }
+-
+- long k = 0L;
+- Iterator iterator = this.f.keySet().iterator();
+-
+- while (iterator.hasNext()) {
+- String s1 = (String) iterator.next();
+-
+- if (s1.length() > s.length() && s1.startsWith(s) && s1.indexOf(".", s.length() + 1) < 0) {
+- k += ((Long) this.f.get(s1)).longValue();
+- }
+- }
+-
+- float f = (float) k;
+-
+- if (k < j) {
+- k = j;
+- }
+-
+- if (i < k) {
+- i = k;
+- }
+-
+- Iterator iterator1 = this.f.keySet().iterator();
+-
+- String s2;
+-
+- while (iterator1.hasNext()) {
+- s2 = (String) iterator1.next();
+- if (s2.length() > s.length() && s2.startsWith(s) && s2.indexOf(".", s.length() + 1) < 0) {
+- long l = ((Long) this.f.get(s2)).longValue();
+- double d0 = (double) l * 100.0D / (double) k;
+- double d1 = (double) l * 100.0D / (double) i;
+- String s3 = s2.substring(s.length());
+-
+- arraylist.add(new ProfilerInfo(s3, d0, d1));
+- }
+- }
+-
+- iterator1 = this.f.keySet().iterator();
+-
+- while (iterator1.hasNext()) {
+- s2 = (String) iterator1.next();
+- this.f.put(s2, Long.valueOf(((Long) this.f.get(s2)).longValue() * 999L / 1000L));
+- }
+-
+- if ((float) k > f) {
+- arraylist.add(new ProfilerInfo("unspecified", (double) ((float) k - f) * 100.0D / (double) k, (double) ((float) k - f) * 100.0D / (double) i));
+- }
+-
+- Collections.sort(arraylist);
+- arraylist.add(0, new ProfilerInfo(s, 100.0D, (double) k * 100.0D / (double) i));
+- return arraylist;
+- }
++ return null;
+ }
+
+ public void c(String s) {
+- this.b();
+- this.a(s);
+ }
+
+ public String c() {
+- return this.c.size() == 0 ? "[UNKNOWN]" : (String) this.c.get(this.c.size() - 1);
++ return null;
+ }
+ }
diff --git a/nms-patches/MinecraftServer.patch b/nms-patches/MinecraftServer.patch
new file mode 100644
index 00000000..22d7748f
--- /dev/null
+++ b/nms-patches/MinecraftServer.patch
@@ -0,0 +1,726 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/MinecraftServer.java 2014-11-27 08:59:46.801421406 +1100
++++ src/main/java/net/minecraft/server/MinecraftServer.java 2014-11-27 08:42:10.092851027 +1100
+@@ -37,6 +37,18 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.io.IOException;
++
++import jline.console.ConsoleReader;
++import joptsimple.OptionSet;
++
++import org.bukkit.craftbukkit.Main;
++import org.bukkit.World.Environment;
++import org.bukkit.craftbukkit.util.Waitable;
++import org.bukkit.event.server.RemoteServerCommandEvent;
++import org.bukkit.event.world.WorldSaveEvent;
++// CraftBukkit end
+ public abstract class MinecraftServer implements ICommandListener, Runnable, IAsyncTaskHandler, IMojangStatistics {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+@@ -93,24 +105,66 @@
+ private Thread serverThread;
+ private long ab = ax();
+
+- public MinecraftServer(File file, Proxy proxy, File file1) {
++ // CraftBukkit start
++ public List<WorldServer> worlds = new ArrayList<WorldServer>();
++ public org.bukkit.craftbukkit.CraftServer server;
++ public OptionSet options;
++ public org.bukkit.command.ConsoleCommandSender console;
++ public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
++ public ConsoleReader reader;
++ public static int currentTick = (int) (System.currentTimeMillis() / 50);
++ public final Thread primaryThread;
++ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
++ public int autosavePeriod;
++ // CraftBukkit end
++
++ public MinecraftServer(OptionSet options, Proxy proxy, File file1) {
+ this.d = proxy;
+ MinecraftServer.k = this;
+- this.universe = file;
++ // this.universe = file; // CraftBukkit
+ this.q = new ServerConnection(this);
+ this.Z = new UserCache(this, file1);
+ this.p = this.h();
+- this.convertable = new WorldLoaderServer(file);
++ // this.convertable = new WorldLoaderServer(file); // CraftBukkit - moved to DedicatedServer.init
+ this.V = new YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString());
+ this.W = this.V.createMinecraftSessionService();
+ this.Y = this.V.createProfileRepository();
++ // CraftBukkit start
++ this.options = options;
++ // Try to see if we're actually running in a terminal, disable jline if not
++ if (System.console() == null) {
++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
++ Main.useJline = false;
++ }
++
++ try {
++ reader = new ConsoleReader(System.in, System.out);
++ reader.setExpandEvents(false); // Avoid parsing exceptions for uncommonly used event designators
++ } catch (Throwable e) {
++ try {
++ // Try again with jline disabled for Windows users without C++ 2008 Redistributable
++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
++ System.setProperty("user.language", "en");
++ Main.useJline = false;
++ reader = new ConsoleReader(System.in, System.out);
++ reader.setExpandEvents(false);
++ } catch (IOException ex) {
++ LOGGER.warn((String) null, ex);
++ }
++ }
++ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
++
++ this.serverThread = primaryThread = new Thread(this, "Server thread"); // Moved from main
+ }
+
++ public abstract PropertyManager getPropertyManager();
++ // CraftBukkit end
++
+ protected CommandDispatcher h() {
+ return new CommandDispatcher();
+ }
+
+- protected abstract boolean init();
++ protected abstract boolean init() throws java.net.UnknownHostException; // CraftBukkit - throws UnknownHostException
+
+ protected void a(String s) {
+ if (this.getConvertable().isConvertable(s)) {
+@@ -129,6 +183,7 @@
+ this.a(s);
+ this.b("menu.loadingLevel");
+ this.worldServer = new WorldServer[3];
++ /* CraftBukkit start - Remove ticktime arrays and worldsettings
+ this.h = new long[this.worldServer.length][100];
+ IDataManager idatamanager = this.convertable.a(s, true);
+
+@@ -152,37 +207,110 @@
+ worlddata.a(s1);
+ worldsettings = new WorldSettings(worlddata);
+ }
++ */
++ int worldCount = 3;
+
+- for (int j = 0; j < this.worldServer.length; ++j) {
+- byte b0 = 0;
++ for (int j = 0; j < worldCount; ++j) {
++ WorldServer world;
++ byte dimension = 0;
+
+ if (j == 1) {
+- b0 = -1;
++ if (getAllowNether()) {
++ dimension = -1;
++ } else {
++ continue;
++ }
+ }
+
+ if (j == 2) {
+- b0 = 1;
++ if (server.getAllowEnd()) {
++ dimension = 1;
++ } else {
++ continue;
++ }
+ }
+
++ String worldType = org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase();
++ String name = (dimension == 0) ? s : s + "_" + worldType;
++
++ org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
++ WorldSettings worldsettings = new WorldSettings(i, this.getGamemode(), this.getGenerateStructures(), this.isHardcore(), worldtype);
++ worldsettings.setGeneratorSettings(s2);
++
+ if (j == 0) {
++ IDataManager idatamanager = new ServerNBTManager(server.getWorldContainer(), s1, true);
++ WorldData worlddata = idatamanager.getWorldData();
++ if (worlddata == null) {
++ worlddata = new WorldData(worldsettings, s1);
++ }
+ if (this.W()) {
+- this.worldServer[j] = (WorldServer) (new DemoWorldServer(this, idatamanager, worlddata, b0, this.methodProfiler)).b();
++ world = (WorldServer) (new DemoWorldServer(this, idatamanager, worlddata, dimension, this.methodProfiler)).b();
+ } else {
+- this.worldServer[j] = (WorldServer) (new WorldServer(this, idatamanager, worlddata, b0, this.methodProfiler)).b();
++ world = (WorldServer) (new WorldServer(this, idatamanager, worlddata, dimension, this.methodProfiler, org.bukkit.World.Environment.getEnvironment(dimension), gen)).b();
+ }
+
+- this.worldServer[j].a(worldsettings);
++ world.a(worldsettings);
++ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
+ } else {
+- this.worldServer[j] = (WorldServer) (new SecondaryWorldServer(this, idatamanager, b0, this.worldServer[0], this.methodProfiler)).b();
++ String dim = "DIM" + dimension;
++
++ File newWorld = new File(new File(name), dim);
++ File oldWorld = new File(new File(s), dim);
++
++ if ((!newWorld.isDirectory()) && (oldWorld.isDirectory())) {
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
++ MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
++ MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
++ MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
++
++ if (newWorld.exists()) {
++ MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ } else if (newWorld.getParentFile().mkdirs()) {
++ if (oldWorld.renameTo(newWorld)) {
++ MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
++ // Migrate world data too.
++ try {
++ com.google.common.io.Files.copy(new File(new File(s), "level.dat"), new File(new File(name), "level.dat"));
++ } catch (IOException exception) {
++ MinecraftServer.LOGGER.warn("Unable to migrate world data.");
++ }
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----");
++ } else {
++ MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ }
++ } else {
++ MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ }
++ }
++
++ IDataManager idatamanager = new ServerNBTManager(server.getWorldContainer(), name, true);
++ // world =, b0 to dimension, s1 to name, added Environment and gen
++ WorldData worlddata = idatamanager.getWorldData();
++ if (worlddata == null) {
++ worlddata = new WorldData(worldsettings, name);
++ }
++ world = (WorldServer) new SecondaryWorldServer(this, idatamanager, dimension, this.worlds.get(0), this.methodProfiler, worlddata, org.bukkit.World.Environment.getEnvironment(dimension), gen).b();
+ }
+
+- this.worldServer[j].addIWorldAccess(new WorldManager(this, this.worldServer[j]));
++ if (gen != null) {
++ world.getWorld().getPopulators().addAll(gen.getDefaultPopulators(world.getWorld()));
++ }
++
++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(world.getWorld()));
++
++ world.addIWorldAccess(new WorldManager(this, world));
+ if (!this.S()) {
+- this.worldServer[j].getWorldData().setGameType(this.getGamemode());
++ world.getWorldData().setGameType(this.getGamemode());
+ }
++
++ worlds.add(world);
++ getPlayerList().setPlayerFileData(worlds.toArray(new WorldServer[worlds.size()]));
+ }
+
+- this.v.setPlayerFileData(this.worldServer);
++ // CraftBukkit end
+ this.a(this.getDifficulty());
+ this.k();
+ }
+@@ -197,25 +325,38 @@
+ this.b("menu.generatingTerrain");
+ byte b0 = 0;
+
+- MinecraftServer.LOGGER.info("Preparing start region for level " + b0);
+- WorldServer worldserver = this.worldServer[b0];
+- BlockPosition blockposition = worldserver.getSpawn();
+- long j = ax();
+-
+- for (int k = -192; k <= 192 && this.isRunning(); k += 16) {
+- for (int l = -192; l <= 192 && this.isRunning(); l += 16) {
+- long i1 = ax();
+-
+- if (i1 - j > 1000L) {
+- this.a_("Preparing spawn area", i * 100 / 625);
+- j = i1;
+- }
++ // CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory
++ for (int m = 0; m < worlds.size(); m++) {
++ WorldServer worldserver = this.worlds.get(m);
++ LOGGER.info("Preparing start region for level " + m + " (Seed: " + worldserver.getSeed() + ")");
++
++ if (!worldserver.getWorld().getKeepSpawnInMemory()) {
++ continue;
++ }
++
++ BlockPosition blockposition = worldserver.getSpawn();
++ long j = ax();
++ i = 0;
++
++ for (int k = -192; k <= 192 && this.isRunning(); k += 16) {
++ for (int l = -192; l <= 192 && this.isRunning(); l += 16) {
++ long i1 = ax();
++
++ if (i1 - j > 1000L) {
++ this.a_("Preparing spawn area", i * 100 / 625);
++ j = i1;
++ }
+
+- ++i;
+- worldserver.chunkProviderServer.getChunkAt(blockposition.getX() + k >> 4, blockposition.getZ() + l >> 4);
++ ++i;
++ worldserver.chunkProviderServer.getChunkAt(blockposition.getX() + k >> 4, blockposition.getZ() + l >> 4);
++ }
+ }
+ }
+
++ for (WorldServer world : this.worlds) {
++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(world.getWorld()));
++ }
++ // CraftBukkit end
+ this.q();
+ }
+
+@@ -247,35 +388,42 @@
+ protected void q() {
+ this.e = null;
+ this.f = 0;
++
++ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); // CraftBukkit
+ }
+
+- protected void saveChunks(boolean flag) {
++ protected void saveChunks(boolean flag) throws ExceptionWorldConflict { // CraftBukkit - added throws
+ if (!this.N) {
+- WorldServer[] aworldserver = this.worldServer;
+- int i = aworldserver.length;
+-
+- for (int j = 0; j < i; ++j) {
+- WorldServer worldserver = aworldserver[j];
+
++ // CraftBukkit start
++ for (int j = 0; j < worlds.size(); ++j) {
++ WorldServer worldserver = worlds.get(j);
++ // CraftBukkit end
+ if (worldserver != null) {
+ if (!flag) {
+ MinecraftServer.LOGGER.info("Saving chunks for level \'" + worldserver.getWorldData().getName() + "\'/" + worldserver.worldProvider.getName());
+ }
+
+- try {
+- worldserver.save(true, (IProgressUpdate) null);
+- } catch (ExceptionWorldConflict exceptionworldconflict) {
+- MinecraftServer.LOGGER.warn(exceptionworldconflict.getMessage());
+- }
++ worldserver.save(true, (IProgressUpdate) null);
++ worldserver.saveLevel();
++
++ WorldSaveEvent event = new WorldSaveEvent(worldserver.getWorld());
++ this.server.getPluginManager().callEvent(event);
++ // CraftBukkit end
+ }
+ }
+
+ }
+ }
+
+- public void stop() {
++ public void stop() throws ExceptionWorldConflict { // CraftBukkit - added throws
+ if (!this.N) {
+ MinecraftServer.LOGGER.info("Stopping server");
++ // CraftBukkit start
++ if (this.server != null) {
++ this.server.disablePlugins();
++ }
++ // CraftBukkit end
+ if (this.ao() != null) {
+ this.ao().b();
+ }
+@@ -290,11 +438,13 @@
+ MinecraftServer.LOGGER.info("Saving worlds");
+ this.saveChunks(false);
+
++ /* CraftBukkit start - Handled in saveChunks
+ for (int i = 0; i < this.worldServer.length; ++i) {
+ WorldServer worldserver = this.worldServer[i];
+
+ worldserver.saveLevel();
+ }
++ // CraftBukkit end */
+ }
+
+ if (this.m.d()) {
+@@ -335,6 +485,7 @@
+ long k = j - this.ab;
+
+ if (k > 2000L && this.ab - this.R >= 15000L) {
++ if (server.getWarnOnOverload()) // CraftBukkit
+ MinecraftServer.LOGGER.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(k), Long.valueOf(k / 50L)});
+ k = 2000L;
+ this.R = this.ab;
+@@ -347,11 +498,12 @@
+
+ i += k;
+ this.ab = j;
+- if (this.worldServer[0].everyoneDeeplySleeping()) {
++ if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit
+ this.y();
+ i = 0L;
+ } else {
+ while (i > 50L) {
++ MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
+ i -= 50L;
+ this.y();
+ }
+@@ -389,6 +541,12 @@
+ } catch (Throwable throwable1) {
+ MinecraftServer.LOGGER.error("Exception stopping the server", throwable1);
+ } finally {
++ // CraftBukkit start - Restore terminal to original settings
++ try {
++ reader.getTerminal().restore();
++ } catch (Exception ignored) {
++ }
++ // CraftBukkit end
+ this.x();
+ }
+
+@@ -428,7 +586,7 @@
+
+ protected void x() {}
+
+- protected void y() {
++ protected void y() throws ExceptionWorldConflict { // CraftBukkit - added throws
+ long i = System.nanoTime();
+
+ ++this.ticks;
+@@ -454,7 +612,7 @@
+ this.r.b().a(agameprofile);
+ }
+
+- if (this.ticks % 900 == 0) {
++ if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
+ this.methodProfiler.a("save");
+ this.v.savePlayers();
+ this.saveChunks(true);
+@@ -493,20 +651,40 @@
+
+ this.methodProfiler.c("levels");
+
++ // CraftBukkit start
++ this.server.getScheduler().mainThreadHeartbeat(this.ticks);
++
++ // Run tasks that are waiting on processing
++ while (!processQueue.isEmpty()) {
++ processQueue.remove().run();
++ }
++
++ org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.tick();
++
++ // Send time updates to everyone, it will get the right time from the world the player is in.
++ if (this.ticks % 20 == 0) {
++ for (int i = 0; i < this.getPlayerList().players.size(); ++i) {
++ EntityPlayer entityplayer = (EntityPlayer) this.getPlayerList().players.get(i);
++ entityplayer.playerConnection.sendPacket(new PacketPlayOutUpdateTime(entityplayer.world.getTime(), entityplayer.getPlayerTime(), entityplayer.world.getGameRules().getBoolean("doDaylightCycle"))); // Add support for per player time
++ }
++ }
++
+ int i;
+
+- for (i = 0; i < this.worldServer.length; ++i) {
++ for (i = 0; i < this.worlds.size(); ++i) {
+ long j = System.nanoTime();
+
+- if (i == 0 || this.getAllowNether()) {
+- WorldServer worldserver = this.worldServer[i];
++ // if (i == 0 || this.getAllowNether()) {
++ WorldServer worldserver = this.worlds.get(i);
+
+ this.methodProfiler.a(worldserver.getWorldData().getName());
++ /* Drop global time updates
+ if (this.ticks % 20 == 0) {
+ this.methodProfiler.a("timeSync");
+ this.v.a(new PacketPlayOutUpdateTime(worldserver.getTime(), worldserver.getDayTime(), worldserver.getGameRules().getBoolean("doDaylightCycle")), worldserver.worldProvider.getDimension());
+ this.methodProfiler.b();
+ }
++ // CraftBukkit end */
+
+ this.methodProfiler.a("tick");
+
+@@ -533,9 +711,9 @@
+ worldserver.getTracker().updatePlayers();
+ this.methodProfiler.b();
+ this.methodProfiler.b();
+- }
++ // } // CraftBukkit
+
+- this.h[i][this.ticks % 100] = System.nanoTime() - j;
++ // this.h[i][this.ticks % 100] = System.nanoTime() - j; // CraftBukkit
+ }
+
+ this.methodProfiler.c("connection");
+@@ -559,10 +737,11 @@
+ this.o.add(iupdateplayerlistbox);
+ }
+
+- public static void main(String[] astring) {
++ public static void main(final OptionSet options) { // CraftBukkit - replaces main(String[] astring)
+ DispenserRegistry.c();
+
+ try {
++ /* CraftBukkit start - Replace everything
+ boolean flag = true;
+ String s = null;
+ String s1 = ".";
+@@ -636,6 +815,29 @@
+
+ dedicatedserver.B();
+ Runtime.getRuntime().addShutdownHook(new ThreadShutdown("Server Shutdown Thread", dedicatedserver));
++ */
++
++ DedicatedServer dedicatedserver = new DedicatedServer(options);
++
++ if (options.has("port")) {
++ int port = (Integer) options.valueOf("port");
++ if (port > 0) {
++ dedicatedserver.setPort(port);
++ }
++ }
++
++ if (options.has("universe")) {
++ dedicatedserver.universe = (File) options.valueOf("universe");
++ } else {
++ dedicatedserver.universe = new File(".");
++ }
++
++ if (options.has("world")) {
++ dedicatedserver.setWorld((String) options.valueOf("world"));
++ }
++
++ dedicatedserver.primaryThread.start();
++ // CraftBukkit end
+ } catch (Exception exception) {
+ MinecraftServer.LOGGER.fatal("Failed to start the minecraft server", exception);
+ }
+@@ -643,8 +845,10 @@
+ }
+
+ public void B() {
++ /* CraftBukkit start - prevent abuse
+ this.serverThread = new Thread(this, "Server thread");
+ this.serverThread.start();
++ // CraftBukkit end */
+ }
+
+ public File d(String s) {
+@@ -660,7 +864,14 @@
+ }
+
+ public WorldServer getWorldServer(int i) {
+- return i == -1 ? this.worldServer[1] : (i == 1 ? this.worldServer[2] : this.worldServer[0]);
++ // CraftBukkit start
++ for (WorldServer world : worlds) {
++ if (world.dimension == i) {
++ return world;
++ }
++ }
++ return worlds.get(0);
++ // CraftBukkit end
+ }
+
+ public String C() {
+@@ -696,17 +907,62 @@
+ }
+
+ public String getPlugins() {
+- return "";
+- }
++ // CraftBukkit start - Whole method
++ StringBuilder result = new StringBuilder();
++ org.bukkit.plugin.Plugin[] plugins = server.getPluginManager().getPlugins();
++
++ result.append(server.getName());
++ result.append(" on Bukkit ");
++ result.append(server.getBukkitVersion());
++
++ if (plugins.length > 0 && server.getQueryPlugins()) {
++ result.append(": ");
++
++ for (int i = 0; i < plugins.length; i++) {
++ if (i > 0) {
++ result.append("; ");
++ }
+
+- public String executeRemoteCommand(String s) {
+- RemoteControlCommandListener.getInstance().i();
+- this.p.a(RemoteControlCommandListener.getInstance(), s);
+- return RemoteControlCommandListener.getInstance().j();
++ result.append(plugins[i].getDescription().getName());
++ result.append(" ");
++ result.append(plugins[i].getDescription().getVersion().replaceAll(";", ","));
++ }
++ }
++
++ return result.toString();
++ // CraftBukkit end
++ }
++
++ // CraftBukkit start - fire RemoteServerCommandEvent
++ public String executeRemoteCommand(final String s) {
++ Waitable<String> waitable = new Waitable<String>() {
++ @Override
++ protected String evaluate() {
++ RemoteControlCommandListener.getInstance().i();
++ // Event changes start
++ RemoteServerCommandEvent event = new RemoteServerCommandEvent(remoteConsole, s);
++ server.getPluginManager().callEvent(event);
++ // Event change end
++ ServerCommand serverCommand = new ServerCommand(event.getCommand(), RemoteControlCommandListener.getInstance());
++ server.dispatchServerCommand(remoteConsole, serverCommand);
++ // this.p.a(RemoteControlCommandListener.getInstance(), s);
++ return RemoteControlCommandListener.getInstance().j();
++ }
++ };
++ processQueue.add(waitable);
++ try {
++ return waitable.get();
++ } catch (java.util.concurrent.ExecutionException e) {
++ throw new RuntimeException("Exception processing rcon command " + s, e.getCause());
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt(); // Maintain interrupted state
++ throw new RuntimeException("Interrupted processing rcon command " + s, e);
++ }
++ // CraftBukkit end
+ }
+
+ public boolean isDebugging() {
+- return false;
++ return this.getPropertyManager().getBoolean("debug", false); // CraftBukkit - don't hardcode
+ }
+
+ public void h(String s) {
+@@ -721,7 +977,7 @@
+ }
+
+ public String getServerModName() {
+- return "vanilla";
++ return server.getName(); // CraftBukkit - cb > vanilla!
+ }
+
+ public CrashReport b(CrashReport crashreport) {
+@@ -734,6 +990,7 @@
+ }
+
+ public List tabCompleteCommand(ICommandListener icommandlistener, String s, BlockPosition blockposition) {
++ /* CraftBukkit start - Allow tab-completion of Bukkit commands
+ ArrayList arraylist = Lists.newArrayList();
+
+ if (s.startsWith("/")) {
+@@ -772,6 +1029,9 @@
+
+ return arraylist;
+ }
++ */
++ return server.tabComplete(icommandlistener, s);
++ // CraftBukkit end
+ }
+
+ public static MinecraftServer getServer() {
+@@ -835,8 +1095,10 @@
+ }
+
+ public void a(EnumDifficulty enumdifficulty) {
+- for (int i = 0; i < this.worldServer.length; ++i) {
+- WorldServer worldserver = this.worldServer[i];
++ // CraftBukkit start
++ for (int i = 0; i < this.worlds.size(); ++i) {
++ WorldServer worldserver = this.worlds.get(i);
++ // CraftBukkit end
+
+ if (worldserver != null) {
+ if (worldserver.getWorldData().isHardcore()) {
+@@ -878,15 +1140,17 @@
+ this.N = true;
+ this.getConvertable().d();
+
+- for (int i = 0; i < this.worldServer.length; ++i) {
+- WorldServer worldserver = this.worldServer[i];
++ // CraftBukkit start
++ for (int i = 0; i < this.worlds.size(); ++i) {
++ WorldServer worldserver = this.worlds.get(i);
++ // CraftBukkit end
+
+ if (worldserver != null) {
+ worldserver.saveLevel();
+ }
+ }
+
+- this.getConvertable().e(this.worldServer[0].getDataManager().g());
++ this.getConvertable().e(this.worlds.get(0).getDataManager().g()); // CraftBukkit
+ this.safeShutdown();
+ }
+
+@@ -919,9 +1183,11 @@
+ int i = 0;
+
+ if (this.worldServer != null) {
+- for (int j = 0; j < this.worldServer.length; ++j) {
+- if (this.worldServer[j] != null) {
+- WorldServer worldserver = this.worldServer[j];
++ // CraftBukkit start
++ for (int j = 0; j < this.worlds.size(); ++j) {
++ WorldServer worldserver = this.worlds.get(j);
++ if (worldserver != null) {
++ // CraftBukkit end
+ WorldData worlddata = worldserver.getWorldData();
+
+ mojangstatisticsgenerator.a("world[" + i + "][dimension]", Integer.valueOf(worldserver.worldProvider.getDimension()));
+@@ -954,7 +1220,7 @@
+ public abstract boolean ad();
+
+ public boolean getOnlineMode() {
+- return this.onlineMode;
++ return server.getOnlineMode(); // CraftBukkit
+ }
+
+ public void setOnlineMode(boolean flag) {
+@@ -1024,8 +1290,10 @@
+ }
+
+ public void setGamemode(EnumGamemode enumgamemode) {
+- for (int i = 0; i < this.worldServer.length; ++i) {
+- getServer().worldServer[i].getWorldData().setGameType(enumgamemode);
++ // CraftBukkit start
++ for (int i = 0; i < this.worlds.size(); ++i) {
++ getServer().worlds.get(i).getWorldData().setGameType(enumgamemode);
++ // CraftBukkit end
+ }
+
+ }
+@@ -1057,7 +1325,7 @@
+ }
+
+ public World getWorld() {
+- return this.worldServer[0];
++ return this.worlds.get(0); // CraftBukkit
+ }
+
+ public Entity f() {
+@@ -1125,11 +1393,10 @@
+ }
+
+ public Entity a(UUID uuid) {
+- WorldServer[] aworldserver = this.worldServer;
+- int i = aworldserver.length;
+-
+- for (int j = 0; j < i; ++j) {
+- WorldServer worldserver = aworldserver[j];
++ // CraftBukkit start
++ for (int j = 0; j < worlds.size(); ++j) {
++ WorldServer worldserver = worlds.get(j);
++ // CraftBukkit end
+
+ if (worldserver != null) {
+ Entity entity = worldserver.getEntity(uuid);
+@@ -1144,7 +1411,7 @@
+ }
+
+ public boolean getSendCommandFeedback() {
+- return getServer().worldServer[0].getGameRules().getBoolean("sendCommandFeedback");
++ return getServer().worlds.get(0).getGameRules().getBoolean("sendCommandFeedback"); // CraftBukkit
+ }
+
+ public void a(EnumCommandResult enumcommandresult, int i) {}
diff --git a/nms-patches/MobEffectList.patch b/nms-patches/MobEffectList.patch
new file mode 100644
index 00000000..33ebcae2
--- /dev/null
+++ b/nms-patches/MobEffectList.patch
@@ -0,0 +1,74 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/MobEffectList.java 2014-11-27 08:59:46.801421406 +1100
++++ src/main/java/net/minecraft/server/MobEffectList.java 2014-11-27 08:42:10.120850973 +1100
+@@ -6,6 +6,11 @@
+ import java.util.UUID;
+ import java.util.Map.Entry;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason;
++// CraftBukkit end
++
+ public class MobEffectList {
+
+ public static final MobEffectList[] byId = new MobEffectList[32];
+@@ -63,6 +68,8 @@
+ }
+
+ this.L = j;
++ org.bukkit.potion.PotionEffectType.registerPotionEffectType(new org.bukkit.craftbukkit.potion.CraftPotionEffectType(this)); // CraftBukkit
++
+ }
+
+ public static MobEffectList b(String s) {
+@@ -94,11 +101,11 @@
+ public void tick(EntityLiving entityliving, int i) {
+ if (this.id == MobEffectList.REGENERATION.id) {
+ if (entityliving.getHealth() < entityliving.getMaxHealth()) {
+- entityliving.heal(1.0F);
++ entityliving.heal(1.0F, RegainReason.MAGIC_REGEN); // CraftBukkit
+ }
+ } else if (this.id == MobEffectList.POISON.id) {
+ if (entityliving.getHealth() > 1.0F) {
+- entityliving.damageEntity(DamageSource.MAGIC, 1.0F);
++ entityliving.damageEntity(CraftEventFactory.POISON, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
+ }
+ } else if (this.id == MobEffectList.WITHER.id) {
+ entityliving.damageEntity(DamageSource.WITHER, 1.0F);
+@@ -106,14 +113,25 @@
+ ((EntityHuman) entityliving).applyExhaustion(0.025F * (float) (i + 1));
+ } else if (this.id == MobEffectList.SATURATION.id && entityliving instanceof EntityHuman) {
+ if (!entityliving.world.isStatic) {
+- ((EntityHuman) entityliving).getFoodData().eat(i + 1, 1.0F);
++ // CraftBukkit start
++ EntityHuman entityhuman = (EntityHuman) entityliving;
++ int oldFoodLevel = entityhuman.getFoodData().foodLevel;
++
++ org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, i + 1 + oldFoodLevel);
++
++ if (!event.isCancelled()) {
++ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
++ }
++
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutUpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel));
++ // CraftBukkit end
+ }
+ } else if ((this.id != MobEffectList.HEAL.id || entityliving.bl()) && (this.id != MobEffectList.HARM.id || !entityliving.bl())) {
+ if (this.id == MobEffectList.HARM.id && !entityliving.bl() || this.id == MobEffectList.HEAL.id && entityliving.bl()) {
+ entityliving.damageEntity(DamageSource.MAGIC, (float) (6 << i));
+ }
+ } else {
+- entityliving.heal((float) Math.max(4 << i, 0));
++ entityliving.heal((float) Math.max(4 << i, 0), RegainReason.MAGIC); // CraftBukkit
+ }
+
+ }
+@@ -132,7 +150,7 @@
+ }
+ } else {
+ j = (int) (d0 * (double) (4 << i) + 0.5D);
+- entityliving.heal((float) j);
++ entityliving.heal((float) j, RegainReason.MAGIC); // CraftBukkit
+ }
+
+ }
diff --git a/nms-patches/MobSpawnerAbstract.patch b/nms-patches/MobSpawnerAbstract.patch
new file mode 100644
index 00000000..1a936183
--- /dev/null
+++ b/nms-patches/MobSpawnerAbstract.patch
@@ -0,0 +1,38 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/MobSpawnerAbstract.java 2014-11-27 08:59:46.805421389 +1100
++++ src/main/java/net/minecraft/server/MobSpawnerAbstract.java 2014-11-27 08:42:10.108850996 +1100
+@@ -4,6 +4,8 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++import org.bukkit.event.entity.CreatureSpawnEvent; // CraftBukkit
++
+ public abstract class MobSpawnerAbstract {
+
+ public int spawnDelay = 20;
+@@ -129,7 +131,7 @@
+
+ entity.f(nbttagcompound);
+ if (entity.world != null && flag) {
+- entity.world.addEntity(entity);
++ entity.world.addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit
+ }
+
+ NBTTagCompound nbttagcompound1;
+@@ -154,7 +156,7 @@
+ entity2.f(nbttagcompound2);
+ entity2.setPositionRotation(entity1.locX, entity1.locY, entity1.locZ, entity1.yaw, entity1.pitch);
+ if (entity.world != null && flag) {
+- entity.world.addEntity(entity2);
++ entity.world.addEntity(entity2, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit
+ }
+
+ entity1.mount(entity2);
+@@ -164,7 +166,7 @@
+ }
+ } else if (entity instanceof EntityLiving && entity.world != null && flag) {
+ ((EntityInsentient) entity).prepare(entity.world.E(new BlockPosition(entity)), (GroupDataEntity) null);
+- entity.world.addEntity(entity);
++ entity.world.addEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER); // CraftBukkit
+ }
+
+ return entity;
diff --git a/nms-patches/NameReferencingFileConverter.patch b/nms-patches/NameReferencingFileConverter.patch
new file mode 100644
index 00000000..621762db
--- /dev/null
+++ b/nms-patches/NameReferencingFileConverter.patch
@@ -0,0 +1,76 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/NameReferencingFileConverter.java 2014-11-27 08:59:46.805421389 +1100
++++ src/main/java/net/minecraft/server/NameReferencingFileConverter.java 2014-11-27 08:42:10.168850880 +1100
+@@ -32,7 +32,7 @@
+ public static final File c = new File("ops.txt");
+ public static final File d = new File("white-list.txt");
+
+- static List a(File file, Map map) {
++ static List a(File file, Map map) throws IOException { // CraftBukkit - Added throws
+ List list = Files.readLines(file, Charsets.UTF_8);
+ Iterator iterator = list.iterator();
+
+@@ -77,9 +77,11 @@
+ if (gameprofilebanlist.c().exists()) {
+ try {
+ gameprofilebanlist.load();
+- } catch (FileNotFoundException filenotfoundexception) {
+- NameReferencingFileConverter.e.warn("Could not load existing file " + gameprofilebanlist.c().getName(), filenotfoundexception);
++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacetrace
++ } catch (IOException filenotfoundexception) {
++ e.warn("Could not load existing file " + gameprofilebanlist.c().getName() + ", " + filenotfoundexception.getMessage());
+ }
++ // CraftBukkit end
+ }
+
+ try {
+@@ -111,9 +113,11 @@
+ if (ipbanlist.c().exists()) {
+ try {
+ ipbanlist.load();
+- } catch (FileNotFoundException filenotfoundexception) {
+- NameReferencingFileConverter.e.warn("Could not load existing file " + ipbanlist.c().getName(), filenotfoundexception);
++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacetrace
++ } catch (IOException filenotfoundexception) {
++ e.warn("Could not load existing file " + ipbanlist.c().getName() + ", " + filenotfoundexception.getMessage());
+ }
++ // CraftBukkit end
+ }
+
+ try {
+@@ -152,9 +156,11 @@
+ if (oplist.c().exists()) {
+ try {
+ oplist.load();
+- } catch (FileNotFoundException filenotfoundexception) {
+- NameReferencingFileConverter.e.warn("Could not load existing file " + oplist.c().getName(), filenotfoundexception);
++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacetrace
++ } catch (IOException filenotfoundexception) {
++ e.warn("Could not load existing file " + oplist.c().getName() + ", " + filenotfoundexception.getMessage());
+ }
++ // CraftBukkit end
+ }
+
+ try {
+@@ -184,9 +190,11 @@
+ if (whitelist.c().exists()) {
+ try {
+ whitelist.load();
+- } catch (FileNotFoundException filenotfoundexception) {
+- NameReferencingFileConverter.e.warn("Could not load existing file " + whitelist.c().getName(), filenotfoundexception);
++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacetrace
++ } catch (IOException filenotfoundexception) {
++ e.warn("Could not load existing file " + whitelist.c().getName() + ", " + filenotfoundexception.getMessage());
+ }
++ // CraftBukkit end
+ }
+
+ try {
+@@ -351,7 +359,7 @@
+
+ private static File d(PropertyManager propertymanager) {
+ String s = propertymanager.getString("level-name", "world");
+- File file = new File(s);
++ File file = new File(MinecraftServer.getServer().server.getWorldContainer(), s); // CraftBukkit - Respect container setting
+
+ return new File(file, "players");
+ }
diff --git a/nms-patches/NetworkManager.patch b/nms-patches/NetworkManager.patch
new file mode 100644
index 00000000..99a6dc91
--- /dev/null
+++ b/nms-patches/NetworkManager.patch
@@ -0,0 +1,20 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/NetworkManager.java 2014-11-27 08:59:46.809421371 +1100
++++ src/main/java/net/minecraft/server/NetworkManager.java 2014-11-27 08:42:10.120850973 +1100
+@@ -41,7 +41,7 @@
+ this.g = enumprotocoldirection;
+ }
+
+- public void channelActive(ChannelHandlerContext channelhandlercontext) {
++ public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception { // CraftBukkit - added throws
+ super.channelActive(channelhandlercontext);
+ this.i = channelhandlercontext.channel();
+ this.j = this.i.remoteAddress();
+@@ -159,7 +159,7 @@
+
+ public void close(IChatBaseComponent ichatbasecomponent) {
+ if (this.i.isOpen()) {
+- this.i.close().awaitUninterruptibly();
++ this.i.close(); // We can't wait as this may be called from an event loop.
+ this.l = ichatbasecomponent;
+ }
+
diff --git a/nms-patches/Packet.patch b/nms-patches/Packet.patch
new file mode 100644
index 00000000..29864db4
--- /dev/null
+++ b/nms-patches/Packet.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Packet.java 2014-11-27 08:59:46.813421353 +1100
++++ src/main/java/net/minecraft/server/Packet.java 2014-11-27 08:42:10.152850911 +1100
+@@ -2,9 +2,9 @@
+
+ public interface Packet {
+
+- void a(PacketDataSerializer packetdataserializer);
++ void a(PacketDataSerializer packetdataserializer) throws java.io.IOException; // CraftBukkit - added throws
+
+- void b(PacketDataSerializer packetdataserializer);
++ void b(PacketDataSerializer packetdataserializer) throws java.io.IOException; // CraftBukkit - added throws
+
+ void a(PacketListener packetlistener);
+ }
diff --git a/nms-patches/PacketDataSerializer.patch b/nms-patches/PacketDataSerializer.patch
new file mode 100644
index 00000000..20a1268c
--- /dev/null
+++ b/nms-patches/PacketDataSerializer.patch
@@ -0,0 +1,122 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PacketDataSerializer.java 2014-11-27 08:59:46.809421371 +1100
++++ src/main/java/net/minecraft/server/PacketDataSerializer.java 2014-11-27 08:42:10.108850996 +1100
+@@ -8,7 +8,6 @@
+ import io.netty.buffer.ByteBufProcessor;
+ import io.netty.handler.codec.DecoderException;
+ import io.netty.handler.codec.EncoderException;
+-import io.netty.util.ReferenceCounted;
+ import java.io.DataInput;
+ import java.io.DataOutput;
+ import java.io.IOException;
+@@ -21,6 +20,8 @@
+ import java.nio.charset.Charset;
+ import java.util.UUID;
+
++import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit
++
+ public class PacketDataSerializer extends ByteBuf {
+
+ private final ByteBuf a;
+@@ -142,7 +143,7 @@
+ } else {
+ try {
+ NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) (new ByteBufOutputStream(this)));
+- } catch (IOException ioexception) {
++ } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception
+ throw new EncoderException(ioexception);
+ }
+ }
+@@ -162,7 +163,7 @@
+ }
+
+ public void a(ItemStack itemstack) {
+- if (itemstack == null) {
++ if (itemstack == null || itemstack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
+ this.writeShort(-1);
+ } else {
+ this.writeShort(Item.getId(itemstack.getItem()));
+@@ -189,6 +190,11 @@
+
+ itemstack = new ItemStack(Item.getById(short0), b0, short1);
+ itemstack.setTag(this.h());
++ // CraftBukkit start
++ if (itemstack.getTag() != null) {
++ CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack));
++ }
++ // CraftBukkit end
+ }
+
+ return itemstack;
+@@ -416,11 +422,11 @@
+ return this.a.getBytes(i, bytebuffer);
+ }
+
+- public ByteBuf getBytes(int i, OutputStream outputstream, int j) {
++ public ByteBuf getBytes(int i, OutputStream outputstream, int j) throws IOException { // CraftBukkit - throws IOException
+ return this.a.getBytes(i, outputstream, j);
+ }
+
+- public int getBytes(int i, GatheringByteChannel gatheringbytechannel, int j) {
++ public int getBytes(int i, GatheringByteChannel gatheringbytechannel, int j) throws IOException { // CraftBukkit - throws IOException
+ return this.a.getBytes(i, gatheringbytechannel, j);
+ }
+
+@@ -484,11 +490,11 @@
+ return this.a.setBytes(i, bytebuffer);
+ }
+
+- public int setBytes(int i, InputStream inputstream, int j) {
++ public int setBytes(int i, InputStream inputstream, int j) throws IOException { // CraftBukkit - throws IOException
+ return this.a.setBytes(i, inputstream, j);
+ }
+
+- public int setBytes(int i, ScatteringByteChannel scatteringbytechannel, int j) {
++ public int setBytes(int i, ScatteringByteChannel scatteringbytechannel, int j) throws IOException { // CraftBukkit - throws IOException
+ return this.a.setBytes(i, scatteringbytechannel, j);
+ }
+
+@@ -580,11 +586,11 @@
+ return this.a.readBytes(bytebuffer);
+ }
+
+- public ByteBuf readBytes(OutputStream outputstream, int i) {
++ public ByteBuf readBytes(OutputStream outputstream, int i) throws IOException { // CraftBukkit - throws IOException
+ return this.a.readBytes(outputstream, i);
+ }
+
+- public int readBytes(GatheringByteChannel gatheringbytechannel, int i) {
++ public int readBytes(GatheringByteChannel gatheringbytechannel, int i) throws IOException { // CraftBukkit - throws IOException
+ return this.a.readBytes(gatheringbytechannel, i);
+ }
+
+@@ -652,11 +658,11 @@
+ return this.a.writeBytes(bytebuffer);
+ }
+
+- public int writeBytes(InputStream inputstream, int i) {
++ public int writeBytes(InputStream inputstream, int i) throws IOException { // CraftBukkit - throws IOException
+ return this.a.writeBytes(inputstream, i);
+ }
+
+- public int writeBytes(ScatteringByteChannel scatteringbytechannel, int i) {
++ public int writeBytes(ScatteringByteChannel scatteringbytechannel, int i) throws IOException { // CraftBukkit - throws IOException
+ return this.a.writeBytes(scatteringbytechannel, i);
+ }
+
+@@ -803,16 +809,4 @@
+ public boolean release(int i) {
+ return this.a.release(i);
+ }
+-
+- public ReferenceCounted retain(int i) {
+- return this.retain(i);
+- }
+-
+- public ReferenceCounted retain() {
+- return this.retain();
+- }
+-
+- public int compareTo(Object object) {
+- return this.compareTo((ByteBuf) object);
+- }
+ }
diff --git a/nms-patches/PacketPlayInBlockPlace.patch b/nms-patches/PacketPlayInBlockPlace.patch
new file mode 100644
index 00000000..d986bc09
--- /dev/null
+++ b/nms-patches/PacketPlayInBlockPlace.patch
@@ -0,0 +1,32 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PacketPlayInBlockPlace.java 2014-11-27 08:59:46.813421353 +1100
++++ src/main/java/net/minecraft/server/PacketPlayInBlockPlace.java 2014-11-27 08:42:10.152850911 +1100
+@@ -9,6 +9,8 @@
+ private float e;
+ private float f;
+ private float g;
++
++ public long timestamp; // CraftBukkit
+
+ public PacketPlayInBlockPlace() {}
+
+@@ -26,6 +28,7 @@
+ }
+
+ public void a(PacketDataSerializer packetdataserializer) {
++ timestamp = System.currentTimeMillis(); // CraftBukkit
+ this.b = packetdataserializer.c();
+ this.c = packetdataserializer.readUnsignedByte();
+ this.d = packetdataserializer.i();
+@@ -71,7 +74,10 @@
+ return this.g;
+ }
+
+- public void a(PacketListener packetlistener) {
+- this.a((PacketListenerPlayIn) packetlistener);
++ // CraftBukkit start - fix decompile error
++ @Override
++ public void a(PacketListener pl) {
++ a((PacketListenerPlayIn)pl);
+ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/PacketPlayInCloseWindow.patch b/nms-patches/PacketPlayInCloseWindow.patch
new file mode 100644
index 00000000..14efaf9c
--- /dev/null
+++ b/nms-patches/PacketPlayInCloseWindow.patch
@@ -0,0 +1,29 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PacketPlayInCloseWindow.java 2014-11-27 08:59:46.817421336 +1100
++++ src/main/java/net/minecraft/server/PacketPlayInCloseWindow.java 2014-11-27 08:42:10.168850880 +1100
+@@ -6,6 +6,17 @@
+
+ public PacketPlayInCloseWindow() {}
+
++ // CraftBukkit start
++ @Override
++ public void a(PacketListener pl) {
++ a((PacketListenerPlayIn) pl);
++ }
++
++ public PacketPlayInCloseWindow(int id) {
++ this.id = id;
++ }
++ // CraftBukkit end
++
+ public void a(PacketListenerPlayIn packetlistenerplayin) {
+ packetlistenerplayin.a(this);
+ }
+@@ -17,8 +28,4 @@
+ public void b(PacketDataSerializer packetdataserializer) {
+ packetdataserializer.writeByte(this.id);
+ }
+-
+- public void a(PacketListener packetlistener) {
+- this.a((PacketListenerPlayIn) packetlistener);
+- }
+ }
diff --git a/nms-patches/PacketStatusListener.patch b/nms-patches/PacketStatusListener.patch
new file mode 100644
index 00000000..b7aad224
--- /dev/null
+++ b/nms-patches/PacketStatusListener.patch
@@ -0,0 +1,113 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PacketStatusListener.java 2014-11-27 08:59:46.817421336 +1100
++++ src/main/java/net/minecraft/server/PacketStatusListener.java 2014-11-27 08:42:10.168850880 +1100
+@@ -1,5 +1,15 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import com.mojang.authlib.GameProfile;
++import java.net.InetSocketAddress;
++import java.util.Iterator;
++
++import org.bukkit.craftbukkit.util.CraftIconCache;
++import org.bukkit.entity.Player;
++
++// CraftBukkit end
++
+ public class PacketStatusListener implements PacketStatusInListener {
+
+ private final MinecraftServer minecraftServer;
+@@ -13,7 +23,93 @@
+ public void a(IChatBaseComponent ichatbasecomponent) {}
+
+ public void a(PacketStatusInStart packetstatusinstart) {
+- this.networkManager.handle(new PacketStatusOutServerInfo(this.minecraftServer.aE()));
++ // this.networkManager.handle(new PacketStatusOutServerInfo(this.minecraftServer.aE()));
++ // CraftBukkit start - fire ping event
++ final Object[] players = minecraftServer.getPlayerList().players.toArray();
++ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent {
++ CraftIconCache icon = minecraftServer.server.getServerIcon();
++
++ ServerListPingEvent() {
++ super(((InetSocketAddress) networkManager.getSocketAddress()).getAddress(), minecraftServer.getMotd(), minecraftServer.getPlayerList().getMaxPlayers());
++ }
++
++ @Override
++ public void setServerIcon(org.bukkit.util.CachedServerIcon icon) {
++ if (!(icon instanceof CraftIconCache)) {
++ throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class);
++ }
++ this.icon = (CraftIconCache) icon;
++ }
++
++ @Override
++ public Iterator<Player> iterator() throws UnsupportedOperationException {
++ return new Iterator<Player>() {
++ int i;
++ int ret = Integer.MIN_VALUE;
++ EntityPlayer player;
++
++ @Override
++ public boolean hasNext() {
++ if (player != null) {
++ return true;
++ }
++ final Object[] currentPlayers = players;
++ for (int length = currentPlayers.length, i = this.i; i < length; i++) {
++ final EntityPlayer player = (EntityPlayer) currentPlayers[i];
++ if (player != null) {
++ this.i = i + 1;
++ this.player = player;
++ return true;
++ }
++ }
++ return false;
++ }
++
++ @Override
++ public Player next() {
++ if (!hasNext()) {
++ throw new java.util.NoSuchElementException();
++ }
++ final EntityPlayer player = this.player;
++ this.player = null;
++ this.ret = this.i - 1;
++ return player.getBukkitEntity();
++ }
++
++ @Override
++ public void remove() {
++ final Object[] currentPlayers = players;
++ final int i = this.ret;
++ if (i < 0 || currentPlayers[i] == null) {
++ throw new IllegalStateException();
++ }
++ currentPlayers[i] = null;
++ }
++ };
++ }
++ }
++
++ ServerListPingEvent event = new ServerListPingEvent();
++ this.minecraftServer.server.getPluginManager().callEvent(event);
++
++ java.util.List<GameProfile> profiles = new java.util.ArrayList<GameProfile>(players.length);
++ for (Object player : players) {
++ if (player != null) {
++ profiles.add(((EntityPlayer) player).getProfile());
++ }
++ }
++
++ ServerPingPlayerSample playerSample = new ServerPingPlayerSample(event.getMaxPlayers(), profiles.size());
++ playerSample.a(profiles.toArray(new GameProfile[profiles.size()]));
++
++ ServerPing ping = new ServerPing();
++ ping.setFavicon(event.icon.value);
++ ping.setMOTD(new ChatComponentText(event.getMotd()));
++ ping.setPlayerSample(playerSample);
++ ping.setServerInfo(new ServerPingServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), 47)); // TODO: Update when protocol changes
++
++ this.networkManager.handle(new PacketStatusOutServerInfo(ping));
++ // CraftBukkit end
+ }
+
+ public void a(PacketStatusInPing packetstatusinping) {
diff --git a/nms-patches/Path.patch b/nms-patches/Path.patch
new file mode 100644
index 00000000..39db7a87
--- /dev/null
+++ b/nms-patches/Path.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Path.java 2014-11-27 08:59:46.849421195 +1100
++++ src/main/java/net/minecraft/server/Path.java 2014-11-27 08:42:10.088851036 +1100
+@@ -2,7 +2,7 @@
+
+ public class Path {
+
+- private PathPoint[] a = new PathPoint[1024];
++ private PathPoint[] a = new PathPoint[128]; // CraftBukkit - reduce default size
+ private int b;
+
+ public Path() {}
diff --git a/nms-patches/PathfinderGoalBreakDoor.patch b/nms-patches/PathfinderGoalBreakDoor.patch
new file mode 100644
index 00000000..c690fcf5
--- /dev/null
+++ b/nms-patches/PathfinderGoalBreakDoor.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalBreakDoor.java 2014-11-27 08:59:46.817421336 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java 2014-11-27 08:42:10.152850911 +1100
+@@ -63,6 +63,12 @@
+ }
+
+ if (this.g == 240 && this.a.world.getDifficulty() == EnumDifficulty.HARD) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.a, this.b.getX(), this.b.getY(), this.b.getZ()).isCancelled()) {
++ this.c();
++ return;
++ }
++ // CraftBukkit end
+ this.a.world.setAir(this.b);
+ this.a.world.triggerEffect(1012, this.b, 0);
+ this.a.world.triggerEffect(2001, this.b, Block.getId(this.c));
diff --git a/nms-patches/PathfinderGoalBreed.patch b/nms-patches/PathfinderGoalBreed.patch
new file mode 100644
index 00000000..50c07973
--- /dev/null
+++ b/nms-patches/PathfinderGoalBreed.patch
@@ -0,0 +1,23 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalBreed.java 2014-11-27 08:59:46.821421318 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalBreed.java 2014-11-27 08:42:10.164850887 +1100
+@@ -70,6 +70,11 @@
+ EntityAgeable entityageable = this.d.createChild(this.e);
+
+ if (entityageable != null) {
++ // CraftBukkit start - set persistence for tame animals
++ if (entityageable instanceof EntityTameableAnimal && ((EntityTameableAnimal) entityageable).isTamed()) {
++ entityageable.persistent = true;
++ }
++ // CraftBukkit end
+ EntityHuman entityhuman = this.d.co();
+
+ if (entityhuman == null && this.e.co() != null) {
+@@ -89,7 +94,7 @@
+ this.e.cq();
+ entityageable.setAgeRaw(-24000);
+ entityageable.setPositionRotation(this.d.locX, this.d.locY, this.d.locZ, 0.0F, 0.0F);
+- this.a.addEntity(entityageable);
++ this.a.addEntity(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
+ Random random = this.d.bb();
+
+ for (int i = 0; i < 7; ++i) {
diff --git a/nms-patches/PathfinderGoalDefendVillage.patch b/nms-patches/PathfinderGoalDefendVillage.patch
new file mode 100644
index 00000000..2f2260c3
--- /dev/null
+++ b/nms-patches/PathfinderGoalDefendVillage.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalDefendVillage.java 2014-11-27 08:59:46.821421318 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalDefendVillage.java 2014-11-27 08:42:10.172850872 +1100
+@@ -32,7 +32,7 @@
+ }
+
+ public void c() {
+- this.a.setGoalTarget(this.b);
++ this.a.setGoalTarget(this.b, org.bukkit.event.entity.EntityTargetEvent.TargetReason.DEFEND_VILLAGE, true); // CraftBukkit - reason
+ super.c();
+ }
+ }
diff --git a/nms-patches/PathfinderGoalEatTile.patch b/nms-patches/PathfinderGoalEatTile.patch
new file mode 100644
index 00000000..88f1cd79
--- /dev/null
+++ b/nms-patches/PathfinderGoalEatTile.patch
@@ -0,0 +1,34 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalEatTile.java 2014-11-27 08:59:46.821421318 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalEatTile.java 2014-11-27 08:42:10.108850996 +1100
+@@ -3,6 +3,11 @@
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.Material;
++// CraftBukkit end
++
+ public class PathfinderGoalEatTile extends PathfinderGoal {
+
+ private static final Predicate b = BlockStatePredicate.a((Block) Blocks.TALLGRASS).a(BlockLongGrass.TYPE, Predicates.equalTo(EnumTallGrassType.GRASS));
+@@ -50,7 +55,8 @@
+ BlockPosition blockposition = new BlockPosition(this.c.locX, this.c.locY, this.c.locZ);
+
+ if (PathfinderGoalEatTile.b.apply(this.d.getType(blockposition))) {
+- if (this.d.getGameRules().getBoolean("mobGriefing")) {
++ // CraftBukkit
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.c, this.c.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), Material.AIR, !this.d.getGameRules().getBoolean("mobGriefing")).isCancelled()) {
+ this.d.setAir(blockposition, false);
+ }
+
+@@ -59,7 +65,8 @@
+ BlockPosition blockposition1 = blockposition.down();
+
+ if (this.d.getType(blockposition1).getBlock() == Blocks.GRASS) {
+- if (this.d.getGameRules().getBoolean("mobGriefing")) {
++ // CraftBukkit
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.c, this.c.world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()), Material.DIRT, !this.d.getGameRules().getBoolean("mobGriefing")).isCancelled()) {
+ this.d.triggerEffect(2001, blockposition1, Block.getId(Blocks.GRASS));
+ this.d.setTypeAndData(blockposition1, Blocks.DIRT.getBlockData(), 2);
+ }
diff --git a/nms-patches/PathfinderGoalEndermanPickupBlock.patch b/nms-patches/PathfinderGoalEndermanPickupBlock.patch
new file mode 100644
index 00000000..31b450c1
--- /dev/null
+++ b/nms-patches/PathfinderGoalEndermanPickupBlock.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalEndermanPickupBlock.java 2014-11-27 08:59:46.825421301 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalEndermanPickupBlock.java 2014-11-27 08:42:10.164850887 +1100
+@@ -25,8 +25,12 @@
+ Block block = iblockdata.getBlock();
+
+ if (EntityEnderman.co().contains(block)) {
+- this.enderman.setCarried(iblockdata);
+- world.setTypeUpdate(blockposition, Blocks.AIR.getBlockData());
++ // CraftBukkit start - Pickup event
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, this.enderman.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), org.bukkit.Material.AIR).isCancelled()) {
++ this.enderman.setCarried(iblockdata);
++ world.setTypeUpdate(blockposition, Blocks.AIR.getBlockData());
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/PathfinderGoalEndermanPlaceBlock.patch b/nms-patches/PathfinderGoalEndermanPlaceBlock.patch
new file mode 100644
index 00000000..e858f052
--- /dev/null
+++ b/nms-patches/PathfinderGoalEndermanPlaceBlock.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalEndermanPlaceBlock.java 2014-11-27 08:59:46.825421301 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalEndermanPlaceBlock.java 2014-11-27 08:42:10.172850872 +1100
+@@ -25,8 +25,12 @@
+ Block block1 = world.getType(blockposition.down()).getBlock();
+
+ if (this.a(world, blockposition, this.a.getCarried().getBlock(), block, block1)) {
++ // CraftBukkit start - Place event
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.a, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.a.getCarried().getBlock(), this.a.getCarried().getBlock().toLegacyData(this.a.getCarried())).isCancelled()) {
+ world.setTypeAndData(blockposition, this.a.getCarried(), 3);
+ this.a.setCarried(Blocks.AIR.getBlockData());
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/PathfinderGoalGhastAttackTarget.patch b/nms-patches/PathfinderGoalGhastAttackTarget.patch
new file mode 100644
index 00000000..adc48e2b
--- /dev/null
+++ b/nms-patches/PathfinderGoalGhastAttackTarget.patch
@@ -0,0 +1,12 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalGhastAttackTarget.java 2014-11-27 08:59:46.829421283 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalGhastAttackTarget.java 2014-11-27 08:42:10.152850911 +1100
+@@ -43,7 +43,8 @@
+ world.a((EntityHuman) null, 1008, new BlockPosition(this.b), 0);
+ EntityLargeFireball entitylargefireball = new EntityLargeFireball(world, this.b, d2, d3, d4);
+
+- entitylargefireball.yield = this.b.cd();
++ // CraftBukkit - set bukkitYield when setting explosionpower
++ entitylargefireball.bukkitYield = entitylargefireball.yield = this.b.cd();
+ entitylargefireball.locX = this.b.locX + vec3d.a * d1;
+ entitylargefireball.locY = this.b.locY + (double) (this.b.length / 2.0F) + 0.5D;
+ entitylargefireball.locZ = this.b.locZ + vec3d.c * d1;
diff --git a/nms-patches/PathfinderGoalHurtByTarget.patch b/nms-patches/PathfinderGoalHurtByTarget.patch
new file mode 100644
index 00000000..c0763af1
--- /dev/null
+++ b/nms-patches/PathfinderGoalHurtByTarget.patch
@@ -0,0 +1,19 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalHurtByTarget.java 2014-11-27 08:59:46.829421283 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalHurtByTarget.java 2014-11-27 08:42:10.156850903 +1100
+@@ -23,7 +23,7 @@
+ }
+
+ public void c() {
+- this.e.setGoalTarget(this.e.getLastDamager());
++ this.e.setGoalTarget(this.e.getLastDamager(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason
+ this.b = this.e.bd();
+ if (this.a) {
+ double d0 = this.f();
+@@ -58,6 +58,6 @@
+ }
+
+ protected void a(EntityCreature entitycreature, EntityLiving entityliving) {
+- entitycreature.setGoalTarget(entityliving);
++ entitycreature.setGoalTarget(entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason
+ }
+ }
diff --git a/nms-patches/PathfinderGoalMakeLove.patch b/nms-patches/PathfinderGoalMakeLove.patch
new file mode 100644
index 00000000..ce58bcfe
--- /dev/null
+++ b/nms-patches/PathfinderGoalMakeLove.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalMakeLove.java 2014-11-27 08:59:46.829421283 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalMakeLove.java 2014-11-27 08:42:10.140850934 +1100
+@@ -87,7 +87,7 @@
+ this.b.o(false);
+ entityvillager.setAgeRaw(-24000);
+ entityvillager.setPositionRotation(this.b.locX, this.b.locY, this.b.locZ, 0.0F, 0.0F);
+- this.d.addEntity(entityvillager);
++ this.d.addEntity(entityvillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
+ this.d.broadcastEntityEffect(entityvillager, (byte) 12);
+ }
+ }
diff --git a/nms-patches/PathfinderGoalNearestAttackableTarget.patch b/nms-patches/PathfinderGoalNearestAttackableTarget.patch
new file mode 100644
index 00000000..438278b0
--- /dev/null
+++ b/nms-patches/PathfinderGoalNearestAttackableTarget.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalNearestAttackableTarget.java 2014-11-27 08:59:46.833421266 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalNearestAttackableTarget.java 2014-11-27 08:42:10.168850880 +1100
+@@ -48,7 +48,7 @@
+ }
+
+ public void c() {
+- this.e.setGoalTarget(this.d);
++ this.e.setGoalTarget(this.d, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // Craftbukkit - reason
+ super.c();
+ }
+ }
diff --git a/nms-patches/PathfinderGoalNearestAttackableTargetInsentient.patch b/nms-patches/PathfinderGoalNearestAttackableTargetInsentient.patch
new file mode 100644
index 00000000..fefd3ad8
--- /dev/null
+++ b/nms-patches/PathfinderGoalNearestAttackableTargetInsentient.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalNearestAttackableTargetInsentient.java 2014-11-27 08:59:46.833421266 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalNearestAttackableTargetInsentient.java 2014-11-27 08:42:10.084851043 +1100
+@@ -54,7 +54,7 @@
+ }
+
+ public void c() {
+- this.b.setGoalTarget(this.e);
++ this.b.setGoalTarget(this.e, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason
+ super.c();
+ }
+
diff --git a/nms-patches/PathfinderGoalOwnerHurtByTarget.patch b/nms-patches/PathfinderGoalOwnerHurtByTarget.patch
new file mode 100644
index 00000000..b8b709f2
--- /dev/null
+++ b/nms-patches/PathfinderGoalOwnerHurtByTarget.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalOwnerHurtByTarget.java 2014-11-27 08:59:46.833421266 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalOwnerHurtByTarget.java 2014-11-27 08:42:10.096851020 +1100
+@@ -30,7 +30,7 @@
+ }
+
+ public void c() {
+- this.e.setGoalTarget(this.b);
++ this.e.setGoalTarget(this.b, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit - reason
+ EntityLiving entityliving = this.a.getOwner();
+
+ if (entityliving != null) {
diff --git a/nms-patches/PathfinderGoalOwnerHurtTarget.patch b/nms-patches/PathfinderGoalOwnerHurtTarget.patch
new file mode 100644
index 00000000..6e0843f1
--- /dev/null
+++ b/nms-patches/PathfinderGoalOwnerHurtTarget.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalOwnerHurtTarget.java 2014-11-27 08:59:46.837421248 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalOwnerHurtTarget.java 2014-11-27 08:42:10.144850927 +1100
+@@ -30,7 +30,7 @@
+ }
+
+ public void c() {
+- this.e.setGoalTarget(this.b);
++ this.e.setGoalTarget(this.b, org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit - reason
+ EntityLiving entityliving = this.a.getOwner();
+
+ if (entityliving != null) {
diff --git a/nms-patches/PathfinderGoalPanic.patch b/nms-patches/PathfinderGoalPanic.patch
new file mode 100644
index 00000000..e6b5dc80
--- /dev/null
+++ b/nms-patches/PathfinderGoalPanic.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalPanic.java 2014-11-27 08:59:46.837421248 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalPanic.java 2014-11-27 08:42:10.084851043 +1100
+@@ -36,6 +36,12 @@
+ }
+
+ public boolean b() {
++ // CraftBukkit start - introduce a temporary timeout hack until this is fixed properly
++ if ((this.b.ticksLived - this.b.bd/*getHurtTimestamp*/()) > 100) {
++ this.b.b((EntityLiving) null);
++ return false;
++ }
++ // CraftBukkit end
+ return !this.b.getNavigation().m();
+ }
+ }
diff --git a/nms-patches/PathfinderGoalSelector.patch b/nms-patches/PathfinderGoalSelector.patch
new file mode 100644
index 00000000..e4b9428f
--- /dev/null
+++ b/nms-patches/PathfinderGoalSelector.patch
@@ -0,0 +1,32 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalSelector.java 2014-11-27 08:59:46.837421248 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalSelector.java 2014-11-27 08:42:10.164850887 +1100
+@@ -6,11 +6,15 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import org.bukkit.craftbukkit.util.UnsafeList; // CraftBukkit
++
+ public class PathfinderGoalSelector {
+
+ private static final Logger a = LogManager.getLogger();
+- private List b = Lists.newArrayList();
+- private List c = Lists.newArrayList();
++ // CraftBukkit start - ArrayList -> UnsafeList
++ private List b = new UnsafeList();
++ private List c = new UnsafeList();
++ // CraftBukkit end
+ private final MethodProfiler d;
+ private int e;
+ private int f = 3;
+@@ -107,9 +111,11 @@
+ if (pathfindergoalselectoritem1 != pathfindergoalselectoritem) {
+ if (pathfindergoalselectoritem.b >= pathfindergoalselectoritem1.b) {
+ if (!this.a(pathfindergoalselectoritem, pathfindergoalselectoritem1) && this.c.contains(pathfindergoalselectoritem1)) {
++ ((UnsafeList.Itr) iterator).valid = false; // CraftBukkit - mark iterator for reuse
+ return false;
+ }
+ } else if (!pathfindergoalselectoritem1.a.i() && this.c.contains(pathfindergoalselectoritem1)) {
++ ((UnsafeList.Itr) iterator).valid = false; // CraftBukkit - mark iterator for reuse
+ return false;
+ }
+ }
diff --git a/nms-patches/PathfinderGoalSilverfishHideInBlock.patch b/nms-patches/PathfinderGoalSilverfishHideInBlock.patch
new file mode 100644
index 00000000..31d6582b
--- /dev/null
+++ b/nms-patches/PathfinderGoalSilverfishHideInBlock.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalSilverfishHideInBlock.java 2014-11-27 08:59:46.841421230 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalSilverfishHideInBlock.java 2014-11-27 08:42:10.176850864 +1100
+@@ -51,6 +51,11 @@
+ IBlockData iblockdata = world.getType(blockposition);
+
+ if (BlockMonsterEggs.d(iblockdata)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition.getX(), blockposition.getY(), blockposition.getZ(), Blocks.MONSTER_EGG, Block.getId(BlockMonsterEggs.getById(iblockdata.getBlock().toLegacyData(iblockdata)))).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ world.setTypeAndData(blockposition, Blocks.MONSTER_EGG.getBlockData().set(BlockMonsterEggs.VARIANT, EnumMonsterEggVarient.a(iblockdata)), 3);
+ this.silverfish.y();
+ this.silverfish.die();
diff --git a/nms-patches/PathfinderGoalSilverfishWakeOthers.patch b/nms-patches/PathfinderGoalSilverfishWakeOthers.patch
new file mode 100644
index 00000000..f8fb1845
--- /dev/null
+++ b/nms-patches/PathfinderGoalSilverfishWakeOthers.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalSilverfishWakeOthers.java 2014-11-27 08:59:46.841421230 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalSilverfishWakeOthers.java 2014-11-27 08:42:10.144850927 +1100
+@@ -36,6 +36,11 @@
+ IBlockData iblockdata = world.getType(blockposition1);
+
+ if (iblockdata.getBlock() == Blocks.MONSTER_EGG) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ(), Blocks.AIR, 0).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ if (world.getGameRules().getBoolean("mobGriefing")) {
+ world.setAir(blockposition1, true);
+ } else {
diff --git a/nms-patches/PathfinderGoalSit.patch b/nms-patches/PathfinderGoalSit.patch
new file mode 100644
index 00000000..f7333321
--- /dev/null
+++ b/nms-patches/PathfinderGoalSit.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalSit.java 2014-11-27 08:59:46.845421213 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalSit.java 2014-11-27 08:42:10.096851020 +1100
+@@ -12,7 +12,7 @@
+
+ public boolean a() {
+ if (!this.entity.isTamed()) {
+- return false;
++ return this.willSit && this.entity.getGoalTarget() == null; // CraftBukkit - Allow sitting for wild animals
+ } else if (this.entity.V()) {
+ return false;
+ } else if (!this.entity.onGround) {
diff --git a/nms-patches/PathfinderGoalTame.patch b/nms-patches/PathfinderGoalTame.patch
new file mode 100644
index 00000000..94aa0e1b
--- /dev/null
+++ b/nms-patches/PathfinderGoalTame.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalTame.java 2014-11-27 08:59:46.845421213 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalTame.java 2014-11-27 08:42:10.152850911 +1100
+@@ -45,7 +45,8 @@
+ int i = this.entity.getTemper();
+ int j = this.entity.getMaxDomestication();
+
+- if (j > 0 && this.entity.bb().nextInt(j) < i) {
++ // CraftBukkit - fire EntityTameEvent
++ if (j > 0 && this.entity.bb().nextInt(j) < i && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.entity, (EntityHuman) this.entity.passenger).isCancelled() && this.entity.passenger instanceof EntityHuman) {
+ this.entity.h((EntityHuman) this.entity.passenger);
+ this.entity.world.broadcastEntityEffect(this.entity, (byte) 7);
+ return;
+@@ -54,8 +55,16 @@
+ this.entity.u(5);
+ }
+
+- this.entity.passenger.mount((Entity) null);
+- this.entity.passenger = null;
++ // CraftBukkit start - Handle dismounting to account for VehicleExitEvent being fired.
++ if (this.entity.passenger != null) {
++ this.entity.passenger.mount((Entity) null);
++ // If the entity still has a passenger, then a plugin cancelled the event.
++ if (this.entity.passenger != null) {
++ return;
++ }
++ }
++ // this.entity.passenger = null;
++ // CraftBukkit end
+ this.entity.cU();
+ this.entity.world.broadcastEntityEffect(this.entity, (byte) 6);
+ }
diff --git a/nms-patches/PathfinderGoalTargetNearestPlayer.patch b/nms-patches/PathfinderGoalTargetNearestPlayer.patch
new file mode 100644
index 00000000..054d1b3c
--- /dev/null
+++ b/nms-patches/PathfinderGoalTargetNearestPlayer.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PathfinderGoalTargetNearestPlayer.java 2014-11-27 08:59:46.845421213 +1100
++++ src/main/java/net/minecraft/server/PathfinderGoalTargetNearestPlayer.java 2014-11-27 08:42:10.120850973 +1100
+@@ -59,7 +59,7 @@
+ }
+
+ public void c() {
+- this.b.setGoalTarget(this.e);
++ this.b.setGoalTarget(this.e, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - added reason
+ super.c();
+ }
+
diff --git a/nms-patches/PlayerChunk.patch b/nms-patches/PlayerChunk.patch
new file mode 100644
index 00000000..6170f804
--- /dev/null
+++ b/nms-patches/PlayerChunk.patch
@@ -0,0 +1,104 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerChunk.java 2014-11-27 08:59:46.849421195 +1100
++++ src/main/java/net/minecraft/server/PlayerChunk.java 2014-11-27 08:42:10.136850942 +1100
+@@ -3,6 +3,11 @@
+ import com.google.common.collect.Lists;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
++import java.util.HashMap;
++// CraftBukkit end
++
+ class PlayerChunk {
+
+ private final List b;
+@@ -12,16 +17,26 @@
+ private int f;
+ private long g;
+ final PlayerChunkMap playerChunkMap;
++
++ // CraftBukkit start - add fields
++ private final HashMap<EntityPlayer, Runnable> players = new HashMap<EntityPlayer, Runnable>();
++ private boolean loaded = false;
++ private Runnable loadedRunnable = new Runnable() {
++ public void run() {
++ PlayerChunk.this.loaded = true;
++ }
++ };
++ // CraftBukkit end
+
+ public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
+ this.playerChunkMap = playerchunkmap;
+ this.b = Lists.newArrayList();
+ this.dirtyBlocks = new short[64];
+ this.location = new ChunkCoordIntPair(i, j);
+- playerchunkmap.a().chunkProviderServer.getChunkAt(i, j);
++ playerchunkmap.a().chunkProviderServer.getChunkAt(i, j, loadedRunnable); // CraftBukkit
+ }
+
+- public void a(EntityPlayer entityplayer) {
++ public void a(final EntityPlayer entityplayer) { // CraftBukkit - added final to argument
+ if (this.b.contains(entityplayer)) {
+ PlayerChunkMap.c().debug("Failed to add player. {} already is in chunk {}, {}", new Object[] { entityplayer, Integer.valueOf(this.location.x), Integer.valueOf(this.location.z)});
+ } else {
+@@ -30,18 +45,50 @@
+ }
+
+ this.b.add(entityplayer);
+- entityplayer.chunkCoordIntPairQueue.add(this.location);
++ // CraftBukkit start - use async chunk io
++ Runnable playerRunnable;
++ if (this.loaded) {
++ playerRunnable = null;
++ entityplayer.chunkCoordIntPairQueue.add(this.location);
++ } else {
++ playerRunnable = new Runnable() {
++ public void run() {
++ entityplayer.chunkCoordIntPairQueue.add(PlayerChunk.this.location);
++ }
++ };
++ this.playerChunkMap.a().chunkProviderServer.getChunkAt(this.location.x, this.location.z, playerRunnable);
++ }
++
++ this.players.put(entityplayer, playerRunnable);
++ // CraftBukkit end
+ }
+ }
+
+ public void b(EntityPlayer entityplayer) {
+ if (this.b.contains(entityplayer)) {
++ // CraftBukkit start - If we haven't loaded yet don't load the chunk just so we can clean it up
++ if (!this.loaded) {
++ ChunkIOExecutor.dropQueuedChunkLoad(this.playerChunkMap.a(), this.location.x, this.location.z, this.players.get(entityplayer));
++ this.b.remove(entityplayer);
++ this.players.remove(entityplayer);
++
++ if (this.b.isEmpty()) {
++ ChunkIOExecutor.dropQueuedChunkLoad(this.playerChunkMap.a(), this.location.x, this.location.z, this.loadedRunnable);
++ long i = (long) this.location.x + 2147483647L | (long) this.location.z + 2147483647L << 32;
++ PlayerChunkMap.b(this.playerChunkMap).remove(i);
++ PlayerChunkMap.c(this.playerChunkMap).remove(this);
++ }
++
++ return;
++ }
++ // CraftBukkit end
+ Chunk chunk = PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z);
+
+ if (chunk.isReady()) {
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutMapChunk(chunk, true, 0));
+ }
+
++ this.players.remove(entityplayer); // CraftBukkit
+ this.b.remove(entityplayer);
+ entityplayer.chunkCoordIntPairQueue.remove(this.location);
+ if (this.b.isEmpty()) {
+@@ -122,7 +169,7 @@
+ if (this.dirtyCount == 64) {
+ i = this.location.x * 16;
+ j = this.location.z * 16;
+- this.a((Packet) (new PacketPlayOutMapChunk(PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z), false, this.f)));
++ this.a((Packet) (new PacketPlayOutMapChunk(PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z), (this.f == 0xFFFF), this.f))); // CraftBukkit - send everything (including biome) if all sections flagged
+
+ for (k = 0; k < 16; ++k) {
+ if ((this.f & 1 << k) != 0) {
diff --git a/nms-patches/PlayerChunkMap.patch b/nms-patches/PlayerChunkMap.patch
new file mode 100644
index 00000000..48b86df8
--- /dev/null
+++ b/nms-patches/PlayerChunkMap.patch
@@ -0,0 +1,208 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerChunkMap.java 2014-11-27 08:59:46.849421195 +1100
++++ src/main/java/net/minecraft/server/PlayerChunkMap.java 2014-11-27 08:42:10.152850911 +1100
+@@ -7,17 +7,24 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.util.Collections;
++import java.util.Queue;
++import java.util.LinkedList;
++// CraftBukkit end
++
+ public class PlayerChunkMap {
+
+ private static final Logger a = LogManager.getLogger();
+ private final WorldServer world;
+ private final List managedPlayers = Lists.newArrayList();
+ private final LongHashMap d = new LongHashMap();
+- private final List e = Lists.newArrayList();
+- private final List f = Lists.newArrayList();
++ private final Queue e = new java.util.concurrent.ConcurrentLinkedQueue(); // CraftBukkit ArrayList -> ConcurrentLinkedQueue
++ private final Queue f = new java.util.concurrent.ConcurrentLinkedQueue(); // CraftBukkit ArrayList -> ConcurrentLinkedQueue
+ private int g;
+ private long h;
+ private final int[][] i = new int[][] { { 1, 0}, { 0, 1}, { -1, 0}, { 0, -1}};
++ private boolean wasNotEmpty; // CraftBukkit - add field
+
+ public PlayerChunkMap(WorldServer worldserver) {
+ this.world = worldserver;
+@@ -35,28 +42,39 @@
+
+ if (i - this.h > 8000L) {
+ this.h = i;
+-
+- for (j = 0; j < this.f.size(); ++j) {
+- playerchunk = (PlayerChunk) this.f.get(j);
++
++ // CraftBukkit start - Use iterator
++ java.util.Iterator iterator = this.f.iterator();
++ while (iterator.hasNext()) {
++ playerchunk = (PlayerChunk) iterator.next();
+ playerchunk.b();
+ playerchunk.a();
+ }
+ } else {
+- for (j = 0; j < this.e.size(); ++j) {
+- playerchunk = (PlayerChunk) this.e.get(j);
++ java.util.Iterator iterator = this.e.iterator();
++
++ while (iterator.hasNext()) {
++ playerchunk = (PlayerChunk) iterator.next();
+ playerchunk.b();
++ iterator.remove();
++ // CraftBukkit end
+ }
+ }
+
+- this.e.clear();
++ // this.e.clear(); // CraftBukkit - Removals are already covered
+ if (this.managedPlayers.isEmpty()) {
++ if (!wasNotEmpty) return; // CraftBukkit - Only do unload when we go from non-empty to empty
+ WorldProvider worldprovider = this.world.worldProvider;
+
+ if (!worldprovider.e()) {
+ this.world.chunkProviderServer.b();
+ }
++ // CraftBukkit start
++ wasNotEmpty = false;
++ } else {
++ wasNotEmpty = true;
+ }
+-
++ // CraftBukkit end
+ }
+
+ public boolean a(int i, int j) {
+@@ -77,6 +95,16 @@
+
+ return playerchunk;
+ }
++
++ // CraftBukkit start - add method
++ public final boolean isChunkInUse(int x, int z) {
++ PlayerChunk pi = a(x, z, false);
++ if (pi != null) {
++ return (PlayerChunk.b(pi).size() > 0);
++ }
++ return false;
++ }
++ // CraftBukkit end
+
+ public void flagDirty(BlockPosition blockposition) {
+ int i = blockposition.getX() >> 4;
+@@ -95,13 +123,22 @@
+
+ entityplayer.d = entityplayer.locX;
+ entityplayer.e = entityplayer.locZ;
++
++ // CraftBukkit start - Load nearby chunks first
++ List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
+
+ for (int k = i - this.g; k <= i + this.g; ++k) {
+ for (int l = j - this.g; l <= j + this.g; ++l) {
+- this.a(k, l, true).a(entityplayer);
++ chunkList.add(new ChunkCoordIntPair(k, l));
+ }
+ }
+-
++
++ Collections.sort(chunkList, new ChunkCoordComparator(entityplayer));
++ for (ChunkCoordIntPair pair : chunkList) {
++ this.a(pair.x, pair.z, true).a(entityplayer);
++ }
++ // CraftBukkit end
++
+ this.managedPlayers.add(entityplayer);
+ this.b(entityplayer);
+ }
+@@ -188,12 +225,13 @@
+ int i1 = this.g;
+ int j1 = i - k;
+ int k1 = j - l;
++ List<ChunkCoordIntPair> chunksToLoad = new LinkedList<ChunkCoordIntPair>(); // CraftBukkit
+
+ if (j1 != 0 || k1 != 0) {
+ for (int l1 = i - i1; l1 <= i + i1; ++l1) {
+ for (int i2 = j - i1; i2 <= j + i1; ++i2) {
+ if (!this.a(l1, i2, k, l, i1)) {
+- this.a(l1, i2, true).a(entityplayer);
++ chunksToLoad.add(new ChunkCoordIntPair(l1, i2)); // CraftBukkit
+ }
+
+ if (!this.a(l1 - j1, i2 - k1, i, j, i1)) {
+@@ -209,6 +247,17 @@
+ this.b(entityplayer);
+ entityplayer.d = entityplayer.locX;
+ entityplayer.e = entityplayer.locZ;
++
++ // CraftBukkit start - send nearest chunks first
++ Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
++ for (ChunkCoordIntPair pair : chunksToLoad) {
++ this.a(pair.x, pair.z, true).a(entityplayer);
++ }
++
++ if (j1 > 1 || j1 < -1 || k1 > 1 || k1 < -1) {
++ Collections.sort(entityplayer.chunkCoordIntPairQueue, new ChunkCoordComparator(entityplayer));
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -274,11 +323,54 @@
+ return playerchunkmap.d;
+ }
+
+- static List c(PlayerChunkMap playerchunkmap) {
++ static Queue c(PlayerChunkMap playerchunkmap) { // CraftBukkit List -> Queue
+ return playerchunkmap.f;
+ }
+
+- static List d(PlayerChunkMap playerchunkmap) {
++ static Queue d(PlayerChunkMap playerchunkmap) { // CraftBukkit List -> Queue
+ return playerchunkmap.e;
+ }
++
++ // CraftBukkit start - Sorter to load nearby chunks first
++ private static class ChunkCoordComparator implements java.util.Comparator<ChunkCoordIntPair> {
++ private int x;
++ private int z;
++
++ public ChunkCoordComparator (EntityPlayer entityplayer) {
++ x = (int) entityplayer.locX >> 4;
++ z = (int) entityplayer.locZ >> 4;
++ }
++
++ public int compare(ChunkCoordIntPair a, ChunkCoordIntPair b) {
++ if (a.equals(b)) {
++ return 0;
++ }
++
++ // Subtract current position to set center point
++ int ax = a.x - this.x;
++ int az = a.z - this.z;
++ int bx = b.x - this.x;
++ int bz = b.z - this.z;
++
++ int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz));
++ if (result != 0) {
++ return result;
++ }
++
++ if (ax < 0) {
++ if (bx < 0) {
++ return bz - az;
++ } else {
++ return -1;
++ }
++ } else {
++ if (bx < 0) {
++ return 1;
++ } else {
++ return az - bz;
++ }
++ }
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/PlayerConnection.patch b/nms-patches/PlayerConnection.patch
new file mode 100644
index 00000000..577557e6
--- /dev/null
+++ b/nms-patches/PlayerConnection.patch
@@ -0,0 +1,1458 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerConnection.java 2014-11-27 08:59:46.853421177 +1100
++++ src/main/java/net/minecraft/server/PlayerConnection.java 2014-11-27 08:42:10.148850918 +1100
+@@ -16,6 +16,48 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.util.concurrent.ExecutionException;
++import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
++import java.util.HashSet;
++
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.craftbukkit.util.LazyPlayerSet;
++import org.bukkit.craftbukkit.util.Waitable;
++
++import org.bukkit.Location;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Event;
++import org.bukkit.event.block.Action;
++import org.bukkit.event.block.SignChangeEvent;
++import org.bukkit.event.inventory.ClickType;
++import org.bukkit.event.inventory.CraftItemEvent;
++import org.bukkit.event.inventory.InventoryAction;
++import org.bukkit.event.inventory.InventoryClickEvent;
++import org.bukkit.event.inventory.InventoryCreativeEvent;
++import org.bukkit.event.inventory.InventoryType.SlotType;
++import org.bukkit.event.player.AsyncPlayerChatEvent;
++import org.bukkit.event.player.PlayerAnimationEvent;
++import org.bukkit.event.player.PlayerChatEvent;
++import org.bukkit.event.player.PlayerCommandPreprocessEvent;
++import org.bukkit.event.player.PlayerInteractEntityEvent;
++import org.bukkit.event.player.PlayerInteractAtEntityEvent;
++import org.bukkit.event.player.PlayerItemHeldEvent;
++import org.bukkit.event.player.PlayerKickEvent;
++import org.bukkit.event.player.PlayerMoveEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++import org.bukkit.event.player.PlayerToggleFlightEvent;
++import org.bukkit.event.player.PlayerToggleSneakEvent;
++import org.bukkit.event.player.PlayerToggleSprintEvent;
++import org.bukkit.inventory.CraftingInventory;
++import org.bukkit.inventory.InventoryView;
++import org.bukkit.util.NumberConversions;
++// CraftBukkit end
++
+ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerListBox {
+
+ private static final Logger c = LogManager.getLogger();
+@@ -29,13 +71,17 @@
+ private int i;
+ private long j;
+ private long k;
+- private int chatThrottle;
++ // CraftBukkit start - multithreaded fields
++ private volatile int chatThrottle;
++ private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle");
++ // CraftBukkit end
+ private int m;
+ private IntHashMap n = new IntHashMap();
+ private double o;
+ private double p;
+ private double q;
+ public boolean checkMovement = true;
++ private boolean processedDisconnect; // CraftBukkit - added
+
+ public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer) {
+ this.minecraftServer = minecraftserver;
+@@ -43,7 +89,37 @@
+ networkmanager.a((PacketListener) this);
+ this.player = entityplayer;
+ entityplayer.playerConnection = this;
++
++ // CraftBukkit start - add fields and methods
++ this.server = minecraftserver.server;
++ }
++
++ private final org.bukkit.craftbukkit.CraftServer server;
++ private int lastTick = MinecraftServer.currentTick;
++ private int lastDropTick = MinecraftServer.currentTick;
++ private int dropCount = 0;
++ private static final int SURVIVAL_PLACE_DISTANCE_SQUARED = 6 * 6;
++ private static final int CREATIVE_PLACE_DISTANCE_SQUARED = 7 * 7;
++
++ // Get position of last block hit for BlockDamageLevel.STOPPED
++ private double lastPosX = Double.MAX_VALUE;
++ private double lastPosY = Double.MAX_VALUE;
++ private double lastPosZ = Double.MAX_VALUE;
++ private float lastPitch = Float.MAX_VALUE;
++ private float lastYaw = Float.MAX_VALUE;
++ private boolean justTeleported = false;
++
++ // For the PacketPlayOutBlockPlace hack :(
++ Long lastPacket;
++
++ // Store the last block right clicked and what type it was
++ private Item lastMaterial;
++
++ public CraftPlayer getPlayer() {
++ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
+ }
++ private final static HashSet<Integer> invalidItems = new HashSet<Integer>(java.util.Arrays.asList(8, 9, 10, 11, 26, 34, 36, 43, 51, 52, 55, 59, 60, 62, 63, 64, 68, 71, 74, 75, 83, 90, 92, 93, 94, 104, 105, 115, 117, 118, 119, 125, 127, 132, 140, 141, 142, 144)); // TODO: Check after every update.
++ // CraftBukkit end
+
+ public void c() {
+ this.h = false;
+@@ -57,9 +133,14 @@
+ }
+
+ this.minecraftServer.methodProfiler.b();
++ // CraftBukkit start
++ for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ;
++ /* Use thread-safe field access instead
+ if (this.chatThrottle > 0) {
+ --this.chatThrottle;
+ }
++ */
++ // CraftBukkit end
+
+ if (this.m > 0) {
+ --this.m;
+@@ -76,11 +157,27 @@
+ }
+
+ public void disconnect(String s) {
++ // CraftBukkit start - fire PlayerKickEvent
++ String leaveMessage = EnumChatFormat.YELLOW + this.player.getName() + " left the game.";
++
++ PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), s, leaveMessage);
++
++ if (this.server.getServer().isRunning()) {
++ this.server.getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ // Do not kick the player
++ return;
++ }
++ // Send the possibly modified leave message
++ s = event.getReason();
++ // CraftBukkit end
+ ChatComponentText chatcomponenttext = new ChatComponentText(s);
+
+ this.networkManager.a(new PacketPlayOutKickDisconnect(chatcomponenttext), new PlayerConnectionFuture(this, chatcomponenttext), new GenericFutureListener[0]);
+ this.networkManager.k();
+- Futures.getUnchecked(this.minecraftServer.postToMainThread(new PlayerConnectionDisconnector(this)));
++ this.minecraftServer.postToMainThread(new PlayerConnectionDisconnector(this)); // CraftBukkit - Don't wait
+ }
+
+ public void a(PacketPlayInSteerVehicle packetplayinsteervehicle) {
+@@ -90,6 +187,13 @@
+
+ public void a(PacketPlayInFlying packetplayinflying) {
+ PlayerConnectionUtils.ensureMainThread(packetplayinflying, this, this.player.u());
++ // CraftBukkit start - Check for NaN
++ if (Double.isNaN(packetplayinflying.x) || Double.isNaN(packetplayinflying.y) || Double.isNaN(packetplayinflying.z)) {
++ c.warn(player.getName() + " was caught trying to crash the server with an invalid position.");
++ getPlayer().kickPlayer("Nope!");
++ return;
++ }
++ // CraftBukkit end
+ WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension);
+
+ this.h = true;
+@@ -108,8 +212,65 @@
+ this.checkMovement = true;
+ }
+ }
++ // CraftBukkit start - fire PlayerMoveEvent
++ Player player = this.getPlayer();
++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location.
++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
++
++ // If the packet contains movement information then we update the To location with the correct XYZ.
++ if (packetplayinflying.hasPos && !(packetplayinflying.hasPos && packetplayinflying.y == -999.0D)) {
++ to.setX(packetplayinflying.x);
++ to.setY(packetplayinflying.y);
++ to.setZ(packetplayinflying.z);
++ }
++
++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch.
++ if (packetplayinflying.hasLook) {
++ to.setYaw(packetplayinflying.yaw);
++ to.setPitch(packetplayinflying.pitch);
++ }
++
++ // Prevent 40 event-calls for less than a single pixel of movement >.>
++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2);
++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch());
++
++ if ((delta > 1f / 256 || deltaAngle > 10f) && (this.checkMovement && !this.player.dead)) {
++ this.lastPosX = to.getX();
++ this.lastPosY = to.getY();
++ this.lastPosZ = to.getZ();
++ this.lastYaw = to.getYaw();
++ this.lastPitch = to.getPitch();
++
++ // Skip the first time we do this
++ if (from.getX() != Double.MAX_VALUE) {
++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
++ this.server.getPluginManager().callEvent(event);
++
++ // If the event is cancelled we move the player back to their old location.
++ if (event.isCancelled()) {
++ this.player.playerConnection.sendPacket(new PacketPlayOutPosition(from.getX(), from.getY() + 1.6200000047683716D, from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()));
++ return;
++ }
++
++ /* If a Plugin has changed the To destination then we teleport the Player
++ there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
++ We only do this if the Event was not cancelled. */
++ if (!to.equals(event.getTo()) && !event.isCancelled()) {
++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.UNKNOWN);
++ return;
++ }
++
++ /* Check to see if the Players Location has some how changed during the call of the event.
++ This can happen due to a plugin teleporting the player instead of using .setTo() */
++ if (!from.equals(this.getPlayer().getLocation()) && this.justTeleported) {
++ this.justTeleported = false;
++ return;
++ }
++ }
++ }
+
+- if (this.checkMovement) {
++ if (this.checkMovement && !this.player.dead) {
++ // CraftBukkit end
+ this.f = this.e;
+ double d7;
+ double d8;
+@@ -203,12 +364,14 @@
+ double d11 = d7 - this.player.locX;
+ double d12 = d8 - this.player.locY;
+ double d13 = d9 - this.player.locZ;
+- double d14 = Math.min(Math.abs(d11), Math.abs(this.player.motX));
+- double d15 = Math.min(Math.abs(d12), Math.abs(this.player.motY));
+- double d16 = Math.min(Math.abs(d13), Math.abs(this.player.motZ));
++ // CraftBukkit start - min to max
++ double d14 = Math.max(Math.abs(d11), Math.abs(this.player.motX));
++ double d15 = Math.max(Math.abs(d12), Math.abs(this.player.motY));
++ double d16 = Math.max(Math.abs(d13), Math.abs(this.player.motZ));
++ // CraftBukkit end
+ double d17 = d14 * d14 + d15 * d15 + d16 * d16;
+
+- if (d17 > 100.0D && (!this.minecraftServer.S() || !this.minecraftServer.R().equals(this.player.getName()))) {
++ if (d17 > 100.0D && this.checkMovement && (!this.minecraftServer.S() || !this.minecraftServer.R().equals(this.player.getName()))) { // CraftBukkit - Added this.checkMovement condition to solve this check being triggered by teleports
+ PlayerConnection.c.warn(this.player.getName() + " moved too quickly! " + d11 + "," + d12 + "," + d13 + " (" + d14 + ", " + d15 + ", " + d16 + ")");
+ this.a(this.o, this.p, this.q, this.player.yaw, this.player.pitch);
+ return;
+@@ -281,6 +444,49 @@
+ }
+
+ public void a(double d0, double d1, double d2, float f, float f1, Set set) {
++ // CraftBukkit start - Delegate to teleport(Location)
++ Player player = this.getPlayer();
++ Location from = player.getLocation();
++ Location to = new Location(this.getPlayer().getWorld(), d0, d1, d2, f, f1);
++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from, to, PlayerTeleportEvent.TeleportCause.UNKNOWN);
++ this.server.getPluginManager().callEvent(event);
++
++ from = event.getFrom();
++ to = event.isCancelled() ? from : event.getTo();
++
++ this.teleport(to, set);
++ }
++
++ public void teleport(Location dest) {
++ teleport(dest, Collections.emptySet());
++ }
++
++ public void teleport(Location dest, Set set) {
++ double d0, d1, d2;
++ float f, f1;
++
++ d0 = dest.getX();
++ d1 = dest.getY();
++ d2 = dest.getZ();
++ f = dest.getYaw();
++ f1 = dest.getPitch();
++
++ // TODO: make sure this is the best way to address this.
++ if (Float.isNaN(f)) {
++ f = 0;
++ }
++
++ if (Float.isNaN(f1)) {
++ f1 = 0;
++ }
++
++ this.lastPosX = d0;
++ this.lastPosY = d1;
++ this.lastPosZ = d2;
++ this.lastYaw = f;
++ this.lastPitch = f1;
++ this.justTeleported = true;
++ // CraftBukkit end
+ this.checkMovement = false;
+ this.o = d0;
+ this.p = d1;
+@@ -314,32 +520,49 @@
+
+ public void a(PacketPlayInBlockDig packetplayinblockdig) {
+ PlayerConnectionUtils.ensureMainThread(packetplayinblockdig, this, this.player.u());
++ if (this.player.dead) return; // CraftBukkit
+ WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension);
+ BlockPosition blockposition = packetplayinblockdig.a();
+
+ this.player.z();
++ // CraftBukkit start
+ switch (SwitchHelperCommandActionType.a[packetplayinblockdig.c().ordinal()]) {
+- case 1:
++ case 1: // DROP_ITEM
+ if (!this.player.v()) {
++ // limit how quickly items can be dropped
++ // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick.
++ if (this.lastDropTick != MinecraftServer.currentTick) {
++ this.dropCount = 0;
++ this.lastDropTick = MinecraftServer.currentTick;
++ } else {
++ // Else we increment the drop count and check the amount.
++ this.dropCount++;
++ if (this.dropCount >= 20) {
++ this.c.warn(this.player.getName() + " dropped their items too quickly!");
++ this.disconnect("You dropped your items too quickly (Hacking?)");
++ return;
++ }
++ }
++ // CraftBukkit end
+ this.player.a(false);
+ }
+
+ return;
+
+- case 2:
++ case 2: // DROP_ALL_ITEMS
+ if (!this.player.v()) {
+ this.player.a(true);
+ }
+
+ return;
+
+- case 3:
++ case 3: // RELEASE_USE_ITEM
+ this.player.bT();
+ return;
+
+- case 4:
+- case 5:
+- case 6:
++ case 4: // START_DESTROY_BLOCK
++ case 5: // ABORT_DESTROY_BLOCK
++ case 6: // STOP_DESTROY_BLOCK
+ double d0 = this.player.locX - ((double) blockposition.getX() + 0.5D);
+ double d1 = this.player.locY - ((double) blockposition.getY() + 0.5D) + 1.5D;
+ double d2 = this.player.locZ - ((double) blockposition.getZ() + 0.5D);
+@@ -354,7 +577,15 @@
+ if (!this.minecraftServer.a(worldserver, blockposition, this.player) && worldserver.af().a(blockposition)) {
+ this.player.playerInteractManager.a(blockposition, packetplayinblockdig.b());
+ } else {
++ // CraftBukkit start - fire PlayerInteractEvent
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, blockposition, packetplayinblockdig.b(), this.player.inventory.getItemInHand());
+ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(worldserver, blockposition));
++ // Update any tile entity data for this block
++ TileEntity tileentity = worldserver.getTileEntity(blockposition);
++ if (tileentity != null) {
++ this.player.playerConnection.sendPacket(tileentity.getUpdatePacket());
++ }
++ // CraftBukkit end
+ }
+ } else {
+ if (packetplayinblockdig.c() == EnumPlayerDigType.STOP_DESTROY_BLOCK) {
+@@ -374,11 +605,38 @@
+ default:
+ throw new IllegalArgumentException("Invalid player action");
+ }
++ // CraftBukkit end
+ }
+
+ public void a(PacketPlayInBlockPlace packetplayinblockplace) {
+ PlayerConnectionUtils.ensureMainThread(packetplayinblockplace, this, this.player.u());
+ WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension);
++
++ // CraftBukkit start
++ if (this.player.dead) return;
++
++ // This is a horrible hack needed because the client sends 2 packets on 'right mouse click'
++ // aimed at a block. We shouldn't need to get the second packet if the data is handled
++ // but we cannot know what the client will do, so we might still get it
++ //
++ // If the time between packets is small enough, and the 'signature' similar, we discard the
++ // second one. This sadly has to remain until Mojang makes their packets saner. :(
++ // -- Grum
++ if (packetplayinblockplace.getFace() == 255) {
++ if (packetplayinblockplace.getItemStack() != null && packetplayinblockplace.getItemStack().getItem() == this.lastMaterial && this.lastPacket != null && packetplayinblockplace.timestamp - this.lastPacket < 100) {
++ this.lastPacket = null;
++ return;
++ }
++ } else {
++ this.lastMaterial = packetplayinblockplace.getItemStack() == null ? null : packetplayinblockplace.getItemStack().getItem();
++ this.lastPacket = packetplayinblockplace.timestamp;
++ }
++ // CraftBukkit - if rightclick decremented the item, always send the update packet. */
++ // this is not here for CraftBukkit's own functionality; rather it is to fix
++ // a notch bug where the item doesn't update correctly.
++ boolean always = false;
++ // CraftBukkit end
++
+ ItemStack itemstack = this.player.inventory.getItemInHand();
+ boolean flag = false;
+ BlockPosition blockposition = packetplayinblockplace.a();
+@@ -390,7 +648,18 @@
+ return;
+ }
+
+- this.player.playerInteractManager.useItem(this.player, worldserver, itemstack);
++ // CraftBukkit start
++ int itemstackAmount = itemstack.count;
++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack);
++ if (event.useItemInHand() != Event.Result.DENY) {
++ this.player.playerInteractManager.useItem(this.player, this.player.world, itemstack);
++ }
++
++ // CraftBukkit - notch decrements the counter by 1 in the above method with food,
++ // snowballs and so forth, but he does it in a place that doesn't cause the
++ // inventory update packet to get sent
++ always = (itemstack.count != itemstackAmount) || itemstack.getItem() == Item.getItemOf(Blocks.WATERLILY);
++ // CraftBukkit end
+ } else if (blockposition.getY() >= this.minecraftServer.getMaxBuildHeight() - 1 && (enumdirection == EnumDirection.UP || blockposition.getY() >= this.minecraftServer.getMaxBuildHeight())) {
+ ChatMessage chatmessage = new ChatMessage("build.tooHigh", new Object[] { Integer.valueOf(this.minecraftServer.getMaxBuildHeight())});
+
+@@ -398,9 +667,21 @@
+ this.player.playerConnection.sendPacket(new PacketPlayOutChat(chatmessage));
+ flag = true;
+ } else {
++ // CraftBukkit start - Check if we can actually do something over this large a distance
++ Location eyeLoc = this.getPlayer().getEyeLocation();
++ double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ());
++ if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) {
++ return;
++ }
++
++ if (!worldserver.af().a(blockposition)) {
++ return;
++ }
++
+ if (this.checkMovement && this.player.e((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && !this.minecraftServer.a(worldserver, blockposition, this.player) && worldserver.af().a(blockposition)) {
+- this.player.playerInteractManager.interact(this.player, worldserver, itemstack, blockposition, enumdirection, packetplayinblockplace.d(), packetplayinblockplace.e(), packetplayinblockplace.f());
++ always = !this.player.playerInteractManager.interact(this.player, worldserver, itemstack, blockposition, enumdirection, packetplayinblockplace.d(), packetplayinblockplace.e(), packetplayinblockplace.f());
+ }
++ // CraftBukkit end
+
+ flag = true;
+ }
+@@ -423,7 +704,8 @@
+
+ this.player.activeContainer.b();
+ this.player.g = false;
+- if (!ItemStack.matches(this.player.inventory.getItemInHand(), packetplayinblockplace.getItemStack())) {
++ // CraftBukkit - TODO CHECK IF NEEDED -- new if structure might not need 'always'. Kept it in for now, but may be able to remove in future
++ if (!ItemStack.matches(this.player.inventory.getItemInHand(), packetplayinblockplace.getItemStack()) || always) {
+ this.sendPacket(new PacketPlayOutSetSlot(this.player.activeContainer.windowId, slot.rawSlotIndex, this.player.inventory.getItemInHand()));
+ }
+ }
+@@ -437,8 +719,8 @@
+ WorldServer[] aworldserver = this.minecraftServer.worldServer;
+ int i = aworldserver.length;
+
+- for (int j = 0; j < i; ++j) {
+- WorldServer worldserver = aworldserver[j];
++ // CraftBukkit - use the worlds array list
++ for (WorldServer worldserver : minecraftServer.worlds) {
+
+ if (worldserver != null) {
+ entity = packetplayinspectate.a(worldserver);
+@@ -455,6 +737,7 @@
+ WorldServer worldserver1 = this.player.u();
+ WorldServer worldserver2 = (WorldServer) entity.world;
+
++ /* CraftBukkit start - replace with bukkit handling for multi-world
+ this.player.dimension = entity.dimension;
+ this.sendPacket(new PacketPlayOutRespawn(this.player.dimension, worldserver1.getDifficulty(), worldserver1.getWorldData().getType(), this.player.playerInteractManager.getGameMode()));
+ worldserver1.removeEntity(this.player);
+@@ -472,6 +755,9 @@
+ this.player.playerInteractManager.a(worldserver2);
+ this.minecraftServer.getPlayerList().b(this.player, worldserver2);
+ this.minecraftServer.getPlayerList().updateClient(this.player);
++ */
++ this.player.getBukkitEntity().teleport(entity.getBukkitEntity());
++ // CraftBukkit end
+ } else {
+ this.player.enderTeleportTo(entity.locX, entity.locY, entity.locZ);
+ }
+@@ -483,14 +769,29 @@
+ public void a(PacketPlayInResourcePackStatus packetplayinresourcepackstatus) {}
+
+ public void a(IChatBaseComponent ichatbasecomponent) {
+- PlayerConnection.c.info(this.player.getName() + " lost connection: " + ichatbasecomponent);
++ // CraftBukkit start - Rarely it would send a disconnect line twice
++ if (this.processedDisconnect) {
++ return;
++ } else {
++ this.processedDisconnect = true;
++ }
++ // CraftBukkit end
++ c.info(this.player.getName() + " lost connection: " + ichatbasecomponent.c()); // CraftBukkit - Don't toString the component
+ this.minecraftServer.aF();
++ // CraftBukkit start - Replace vanilla quit message handling with our own.
++ /*
+ ChatMessage chatmessage = new ChatMessage("multiplayer.player.left", new Object[] { this.player.getScoreboardDisplayName()});
+
+ chatmessage.getChatModifier().setColor(EnumChatFormat.YELLOW);
+ this.minecraftServer.getPlayerList().sendMessage(chatmessage);
++ */
++
+ this.player.q();
+- this.minecraftServer.getPlayerList().disconnect(this.player);
++ String quitMessage = this.minecraftServer.getPlayerList().disconnect(this.player);
++ if ((quitMessage != null) && (quitMessage.length() > 0)) {
++ this.minecraftServer.getPlayerList().sendMessage(CraftChatMessage.fromString(quitMessage));
++ }
++ // CraftBukkit end
+ if (this.minecraftServer.S() && this.player.getName().equals(this.minecraftServer.R())) {
+ PlayerConnection.c.info("Stopping singleplayer server as player logged out");
+ this.minecraftServer.safeShutdown();
+@@ -511,6 +812,15 @@
+ return;
+ }
+ }
++
++ // CraftBukkit start
++ if (packet == null) {
++ return;
++ } else if (packet instanceof PacketPlayOutSpawnPosition) {
++ PacketPlayOutSpawnPosition packet6 = (PacketPlayOutSpawnPosition) packet;
++ this.player.compassTarget = new Location(this.getPlayer().getWorld(), packet6.position.getX(), packet6.position.getY(), packet6.position.getZ());
++ }
++ // CraftBukkit end
+
+ try {
+ this.networkManager.handle(packet);
+@@ -524,18 +834,34 @@
+ }
+
+ public void a(PacketPlayInHeldItemSlot packetplayinhelditemslot) {
++ // CraftBukkit start
++ if (this.player.dead) return;
+ PlayerConnectionUtils.ensureMainThread(packetplayinhelditemslot, this, this.player.u());
+ if (packetplayinhelditemslot.a() >= 0 && packetplayinhelditemslot.a() < PlayerInventory.getHotbarSize()) {
++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getPlayer(), this.player.inventory.itemInHandIndex, packetplayinhelditemslot.a());
++ this.server.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ this.sendPacket(new PacketPlayOutHeldItemSlot(this.player.inventory.itemInHandIndex));
++ this.player.z(); // RENAME
++ return;
++ }
++ // CraftBukkit end
+ this.player.inventory.itemInHandIndex = packetplayinhelditemslot.a();
+ this.player.z();
+ } else {
+ PlayerConnection.c.warn(this.player.getName() + " tried to set an invalid carried item");
++ this.disconnect("Nope!"); // CraftBukkit
+ }
+ }
+
+ public void a(PacketPlayInChat packetplayinchat) {
+- PlayerConnectionUtils.ensureMainThread(packetplayinchat, this, this.player.u());
+- if (this.player.getChatFlags() == EnumChatVisibility.HIDDEN) {
++ // CraftBukkit start - async chat
++ boolean isSync = packetplayinchat.a().startsWith("/");
++ if (packetplayinchat.a().startsWith("/")) {
++ PlayerConnectionUtils.ensureMainThread(packetplayinchat, this, this.player.u());
++ }
++ // CraftBukkit end
++ if (this.player.dead || this.player.getChatFlags() == EnumChatVisibility.HIDDEN) { // CraftBukkit - dead men tell no tales
+ ChatMessage chatmessage = new ChatMessage("chat.cannotSend", new Object[0]);
+
+ chatmessage.getChatModifier().setColor(EnumChatFormat.RED);
+@@ -548,39 +874,248 @@
+
+ for (int i = 0; i < s.length(); ++i) {
+ if (!SharedConstants.isAllowedChatCharacter(s.charAt(i))) {
+- this.disconnect("Illegal characters in chat");
++ // CraftBukkit start - threadsafety
++ if (!isSync) {
++ Waitable waitable = new Waitable() {
++ @Override
++ protected Object evaluate() {
++ PlayerConnection.this.disconnect("Illegal characters in chat");
++ return null;
++ }
++ };
++
++ this.minecraftServer.processQueue.add(waitable);
++
++ try {
++ waitable.get();
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt();
++ } catch (ExecutionException e) {
++ throw new RuntimeException(e);
++ }
++ } else {
++ this.disconnect("Illegal characters in chat");
++ }
++ // CraftBukkit end
+ return;
+ }
+ }
+
+- if (s.startsWith("/")) {
+- this.handleCommand(s);
++ // CraftBukkit start
++ if (isSync) {
++ try {
++ this.minecraftServer.server.playerCommandState = true;
++ this.handleCommand(s);
++ } finally {
++ this.minecraftServer.server.playerCommandState = false;
++ }
++ } else if (s.isEmpty()) {
++ c.warn(this.player.getName() + " tried to send an empty message");
++ } else if (getPlayer().isConversing()) {
++ getPlayer().acceptConversationInput(s);
++ } else if (this.player.getChatFlags() == EnumChatVisibility.SYSTEM) { // Re-add "Command Only" flag check
++ ChatMessage chatmessage = new ChatMessage("chat.cannotSend", new Object[0]);
++
++ chatmessage.getChatModifier().setColor(EnumChatFormat.RED);
++ this.sendPacket(new PacketPlayOutChat(chatmessage));
++ } else if (true) {
++ this.chat(s, true);
++ // CraftBukkit end - the below is for reference. :)
+ } else {
+ ChatMessage chatmessage1 = new ChatMessage("chat.type.text", new Object[] { this.player.getScoreboardDisplayName(), s});
+
+ this.minecraftServer.getPlayerList().sendMessage(chatmessage1, false);
+ }
+
+- this.chatThrottle += 20;
+- if (this.chatThrottle > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) {
+- this.disconnect("disconnect.spam");
+- }
++ // CraftBukkit start - replaced with thread safe throttle
++ // this.chatThrottle += 20;
++ if (chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) {
++ if (!isSync) {
++ 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 {
++ this.disconnect("disconnect.spam");
++ }
++ // CraftBukkit end
++ }
+ }
+ }
++
++ // CraftBukkit start - add method
++ public void chat(String s, boolean async) {
++ if (s.isEmpty() || this.player.getChatFlags() == EnumChatVisibility.HIDDEN) {
++ return;
++ }
++
++ if (!async && s.startsWith("/")) {
++ this.handleCommand(s);
++ } else if (this.player.getChatFlags() == EnumChatVisibility.SYSTEM) {
++ // Do nothing, this is coming from a plugin
++ } else {
++ Player player = this.getPlayer();
++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet());
++ this.server.getPluginManager().callEvent(event);
++
++ if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) {
++ // Evil plugins still listening to deprecated event
++ final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients());
++ queueEvent.setCancelled(event.isCancelled());
++ Waitable waitable = new Waitable() {
++ @Override
++ protected Object evaluate() {
++ org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent);
++
++ if (queueEvent.isCancelled()) {
++ return null;
++ }
++
++ String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage());
++ PlayerConnection.this.minecraftServer.console.sendMessage(message);
++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) {
++ for (Object player : PlayerConnection.this.minecraftServer.getPlayerList().players) {
++ ((EntityPlayer) player).sendMessage(CraftChatMessage.fromString(message));
++ }
++ } else {
++ for (Player player : queueEvent.getRecipients()) {
++ player.sendMessage(message);
++ }
++ }
++ return null;
++ }};
++ if (async) {
++ minecraftServer.processQueue.add(waitable);
++ } else {
++ waitable.run();
++ }
++ try {
++ waitable.get();
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
++ } catch (ExecutionException e) {
++ throw new RuntimeException("Exception processing chat event", e.getCause());
++ }
++ } else {
++ if (event.isCancelled()) {
++ return;
++ }
++
++ s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage());
++ minecraftServer.console.sendMessage(s);
++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) {
++ for (Object recipient : minecraftServer.getPlayerList().players) {
++ ((EntityPlayer) recipient).sendMessage(CraftChatMessage.fromString(s));
++ }
++ } else {
++ for (Player recipient : event.getRecipients()) {
++ recipient.sendMessage(s);
++ }
++ }
++ }
++ }
++ }
++ // CraftBukkit end
+
+ private void handleCommand(String s) {
+- this.minecraftServer.getCommandHandler().a(this.player, s);
++ // CraftBukkit start - whole method
++ this.c.info(this.player.getName() + " issued server command: " + s);
++
++ CraftPlayer player = this.getPlayer();
++
++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet());
++ this.server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ try {
++ if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) {
++ 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);
++ return;
++ }
++ // this.minecraftServer.getCommandHandler().a(this.player, s);
++ // CraftBukkit end
+ }
+
+ public void a(PacketPlayInArmAnimation packetplayinarmanimation) {
++ if (this.player.dead) return; // CraftBukkit
+ PlayerConnectionUtils.ensureMainThread(packetplayinarmanimation, this, this.player.u());
+ this.player.z();
++ // CraftBukkit start - Raytrace to look for 'rogue armswings'
++ float f = 1.0F;
++ float f1 = this.player.lastPitch + (this.player.pitch - this.player.lastPitch) * f;
++ float f2 = this.player.lastYaw + (this.player.yaw - this.player.lastYaw) * f;
++ double d0 = this.player.lastX + (this.player.locX - this.player.lastX) * (double) f;
++ double d1 = this.player.lastY + (this.player.locY - this.player.lastY) * (double) f + 1.62D - (double) this.player.getHeadHeight();
++ double d2 = this.player.lastZ + (this.player.locZ - this.player.lastZ) * (double) f;
++ Vec3D vec3d = new Vec3D(d0, d1, d2);
++
++ float f3 = MathHelper.cos(-f2 * 0.017453292F - 3.1415927F);
++ float f4 = MathHelper.sin(-f2 * 0.017453292F - 3.1415927F);
++ float f5 = -MathHelper.cos(-f1 * 0.017453292F);
++ float f6 = MathHelper.sin(-f1 * 0.017453292F);
++ float f7 = f4 * f5;
++ float f8 = f3 * f5;
++ double d3 = 5.0D;
++ Vec3D vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3);
++ MovingObjectPosition movingobjectposition = this.player.world.rayTrace(vec3d, vec3d1, false);
++
++ if (movingobjectposition == null || movingobjectposition.type != EnumMovingObjectType.BLOCK) {
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.inventory.getItemInHand());
++ }
++
++ // Arm swing animation
++ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getPlayer());
++ this.server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) return;
++ // CraftBukkit end
+ this.player.bv();
+ }
+
+ public void a(PacketPlayInEntityAction packetplayinentityaction) {
+ PlayerConnectionUtils.ensureMainThread(packetplayinentityaction, this, this.player.u());
++ // CraftBukkit start
++ if (this.player.dead) return;
++ switch (packetplayinentityaction.b()) {
++ case START_SNEAKING:
++ case STOP_SNEAKING:
++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getPlayer(), packetplayinentityaction.b() == EnumPlayerAction.START_SNEAKING);
++ this.server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ break;
++ case START_SPRINTING:
++ case STOP_SPRINTING:
++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getPlayer(), packetplayinentityaction.b() == EnumPlayerAction.START_SPRINTING);
++ this.server.getPluginManager().callEvent(e2);
++
++ if (e2.isCancelled()) {
++ return;
++ }
++ break;
++ }
+ this.player.z();
+ switch (SwitchHelperCommandActionType.b[packetplayinentityaction.b().ordinal()]) {
+ case 1:
+@@ -601,7 +1136,7 @@
+
+ case 5:
+ this.player.a(false, true, true);
+- this.checkMovement = false;
++ // this.checkMovement = false; // CraftBukkit - this is handled in teleport
+ break;
+
+ case 6:
+@@ -623,6 +1158,7 @@
+ }
+
+ public void a(PacketPlayInUseEntity packetplayinuseentity) {
++ if (this.player.dead) return; // CraftBukkit
+ PlayerConnectionUtils.ensureMainThread(packetplayinuseentity, this, this.player.u());
+ WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension);
+ Entity entity = packetplayinuseentity.a((World) worldserver);
+@@ -637,18 +1173,72 @@
+ }
+
+ if (this.player.h(entity) < d0) {
++ ItemStack itemInHand = this.player.inventory.getItemInHand(); // CraftBukkit
++
++ if (packetplayinuseentity.a() == EnumEntityUseAction.INTERACT
++ || packetplayinuseentity.a() == EnumEntityUseAction.INTERACT_AT) {
++ // CraftBukkit start
++ boolean triggerTagUpdate = itemInHand != null && itemInHand.getItem() == Items.NAME_TAG && entity instanceof EntityInsentient;
++ boolean triggerChestUpdate = itemInHand != null && itemInHand.getItem() == Item.getItemOf(Blocks.CHEST) && entity instanceof EntityHorse;
++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof EntityInsentient;
++ PlayerInteractEntityEvent event;
++ if (packetplayinuseentity.a() == EnumEntityUseAction.INTERACT) {
++ event = new PlayerInteractEntityEvent((Player) this.getPlayer(), entity.getBukkitEntity());
++ } else {
++ Vec3D target = packetplayinuseentity.b();
++ event = new PlayerInteractAtEntityEvent((Player) this.getPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(target.a, target.b, target.c));
++ }
++ this.server.getPluginManager().callEvent(event);
++
++ if (triggerLeashUpdate && (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != Items.LEAD)) {
++ // Refresh the current leash state
++ this.sendPacket(new PacketPlayOutAttachEntity(1, entity, ((EntityInsentient) entity).getLeashHolder()));
++ }
++
++ if (triggerTagUpdate && (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != Items.NAME_TAG)) {
++ // Refresh the current entity metadata
++ this.sendPacket(new PacketPlayOutEntityMetadata(entity.getId(), entity.datawatcher, true));
++ }
++ if (triggerChestUpdate && (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != Item.getItemOf(Blocks.CHEST))) {
++ this.sendPacket(new PacketPlayOutEntityMetadata(entity.getId(), entity.datawatcher, true));
++ }
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ }
++
+ if (packetplayinuseentity.a() == EnumEntityUseAction.INTERACT) {
+ this.player.u(entity);
++
++ // CraftBukkit start
++ if (itemInHand != null && itemInHand.count <= -1) {
++ this.player.updateInventory(this.player.activeContainer);
++ }
++ // CraftBukkit end
+ } else if (packetplayinuseentity.a() == EnumEntityUseAction.INTERACT_AT) {
+ entity.a((EntityHuman) this.player, packetplayinuseentity.b());
++
++ // CraftBukkit start
++ if (itemInHand != null && itemInHand.count <= -1) {
++ this.player.updateInventory(this.player.activeContainer);
++ }
++ // CraftBukkit end
+ } else if (packetplayinuseentity.a() == EnumEntityUseAction.ATTACK) {
+- if (entity instanceof EntityItem || entity instanceof EntityExperienceOrb || entity instanceof EntityArrow || entity == this.player) {
++ if (entity instanceof EntityItem || entity instanceof EntityExperienceOrb || entity instanceof EntityArrow || (entity == this.player && !player.v())) { // CraftBukkit, RENAME
+ this.disconnect("Attempting to attack an invalid entity");
+ this.minecraftServer.warning("Player " + this.player.getName() + " tried to attack an invalid entity");
+ return;
+ }
+
+ this.player.attack(entity);
++
++ // CraftBukkit start
++ if (itemInHand != null && itemInHand.count <= -1) {
++ this.player.updateInventory(this.player.activeContainer);
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -663,7 +1253,8 @@
+ switch (SwitchHelperCommandActionType.c[enumclientcommand.ordinal()]) {
+ case 1:
+ if (this.player.viewingCredits) {
+- this.player = this.minecraftServer.getPlayerList().moveToWorld(this.player, 0, true);
++ // this.player = this.minecraftServer.getPlayerList().moveToWorld(this.player, 0, true);
++ this.minecraftServer.getPlayerList().changeDimension(this.player, 0, PlayerTeleportEvent.TeleportCause.END_PORTAL); // CraftBukkit - reroute logic through custom portal management
+ } else if (this.player.u().getWorldData().isHardcore()) {
+ if (this.minecraftServer.S() && this.player.getName().equals(this.minecraftServer.R())) {
+ this.player.playerConnection.disconnect("You have died. Game over, man, it\'s game over!");
+@@ -694,11 +1285,17 @@
+ }
+
+ public void a(PacketPlayInCloseWindow packetplayinclosewindow) {
++ if (this.player.dead) return; // CraftBukkit
+ PlayerConnectionUtils.ensureMainThread(packetplayinclosewindow, this, this.player.u());
++
++ CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit
++
+ this.player.p();
+ }
+
+ public void a(PacketPlayInWindowClick packetplayinwindowclick) {
++ if (this.player.dead) return; // CraftBukkit
++
+ PlayerConnectionUtils.ensureMainThread(packetplayinwindowclick, this, this.player.u());
+ this.player.z();
+ if (this.player.activeContainer.windowId == packetplayinwindowclick.a() && this.player.activeContainer.c(this.player)) {
+@@ -711,7 +1308,263 @@
+
+ this.player.a(this.player.activeContainer, (List) arraylist);
+ } else {
+- ItemStack itemstack = this.player.activeContainer.clickItem(packetplayinwindowclick.b(), packetplayinwindowclick.c(), packetplayinwindowclick.f(), this.player);
++ // ItemStack itemstack = this.player.activeContainer.clickItem(packetplayinwindowclick.b(), packetplayinwindowclick.c(), packetplayinwindowclick.f(), this.player);
++ // CraftBukkit start - Call InventoryClickEvent
++ if (packetplayinwindowclick.b() < -1 && packetplayinwindowclick.b() != -999) {
++ return;
++ }
++
++ InventoryView inventory = this.player.activeContainer.getBukkitView();
++ SlotType type = CraftInventoryView.getSlotType(inventory, packetplayinwindowclick.b());
++
++ InventoryClickEvent event = null;
++ ClickType click = ClickType.UNKNOWN;
++ InventoryAction action = InventoryAction.UNKNOWN;
++
++ ItemStack itemstack = null;
++
++ if (packetplayinwindowclick.b() == -1) {
++ type = SlotType.OUTSIDE; // override
++ click = packetplayinwindowclick.c() == 0 ? ClickType.WINDOW_BORDER_LEFT : ClickType.WINDOW_BORDER_RIGHT;
++ action = InventoryAction.NOTHING;
++ } else if (packetplayinwindowclick.f() == 0) {
++ if (packetplayinwindowclick.c() == 0) {
++ click = ClickType.LEFT;
++ } else if (packetplayinwindowclick.c() == 1) {
++ click = ClickType.RIGHT;
++ }
++ if (packetplayinwindowclick.c() == 0 || packetplayinwindowclick.c() == 1) {
++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves
++ if (packetplayinwindowclick.b() == -999) {
++ if (player.inventory.getCarried() != null) {
++ action = packetplayinwindowclick.c() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR;
++ }
++ } else {
++ Slot slot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (slot != null) {
++ ItemStack clickedItem = slot.getItem();
++ ItemStack cursor = player.inventory.getCarried();
++ if (clickedItem == null) {
++ if (cursor != null) {
++ action = packetplayinwindowclick.c() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE;
++ }
++ } else if (slot.isAllowed(player)) {
++ if (cursor == null) {
++ action = packetplayinwindowclick.c() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF;
++ } else if (slot.isAllowed(cursor)) {
++ if (clickedItem.doMaterialsMatch(cursor) && ItemStack.equals(clickedItem, cursor)) {
++ int toPlace = packetplayinwindowclick.c() == 0 ? cursor.count : 1;
++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.count);
++ toPlace = Math.min(toPlace, slot.inventory.getMaxStackSize() - clickedItem.count);
++ if (toPlace == 1) {
++ action = InventoryAction.PLACE_ONE;
++ } else if (toPlace == cursor.count) {
++ action = InventoryAction.PLACE_ALL;
++ } else if (toPlace < 0) {
++ action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks
++ } else if (toPlace != 0) {
++ action = InventoryAction.PLACE_SOME;
++ }
++ } else if (cursor.count <= slot.getMaxStackSize()) {
++ action = InventoryAction.SWAP_WITH_CURSOR;
++ }
++ } else if (cursor.getItem() == clickedItem.getItem() && (!cursor.usesData() || cursor.getData() == clickedItem.getData()) && ItemStack.equals(cursor, clickedItem)) {
++ if (clickedItem.count >= 0) {
++ if (clickedItem.count + cursor.count <= cursor.getMaxStackSize()) {
++ // As of 1.5, this is result slots only
++ action = InventoryAction.PICKUP_ALL;
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ } else if (packetplayinwindowclick.f() == 1) {
++ if (packetplayinwindowclick.c() == 0) {
++ click = ClickType.SHIFT_LEFT;
++ } else if (packetplayinwindowclick.c() == 1) {
++ click = ClickType.SHIFT_RIGHT;
++ }
++ if (packetplayinwindowclick.c() == 0 || packetplayinwindowclick.c() == 1) {
++ if (packetplayinwindowclick.b() < 0) {
++ action = InventoryAction.NOTHING;
++ } else {
++ Slot slot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (slot != null && slot.isAllowed(this.player) && slot.hasItem()) {
++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ }
++ } else if (packetplayinwindowclick.f() == 2) {
++ if (packetplayinwindowclick.c() >= 0 && packetplayinwindowclick.c() < 9) {
++ click = ClickType.NUMBER_KEY;
++ Slot clickedSlot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (clickedSlot.isAllowed(player)) {
++ ItemStack hotbar = this.player.inventory.getItem(packetplayinwindowclick.c());
++ boolean canCleanSwap = hotbar == null || (clickedSlot.inventory == player.inventory && clickedSlot.isAllowed(hotbar)); // the slot will accept the hotbar item
++ if (clickedSlot.hasItem()) {
++ if (canCleanSwap) {
++ action = InventoryAction.HOTBAR_SWAP;
++ } else {
++ int firstEmptySlot = player.inventory.getFirstEmptySlotIndex();
++ if (firstEmptySlot > -1) {
++ action = InventoryAction.HOTBAR_MOVE_AND_READD;
++ } else {
++ action = InventoryAction.NOTHING; // This is not sane! Mojang: You should test for other slots of same type
++ }
++ }
++ } else if (!clickedSlot.hasItem() && hotbar != null && clickedSlot.isAllowed(hotbar)) {
++ action = InventoryAction.HOTBAR_SWAP;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ // Special constructor for number key
++ event = new InventoryClickEvent(inventory, type, packetplayinwindowclick.b(), click, action, packetplayinwindowclick.c());
++ }
++ } else if (packetplayinwindowclick.f() == 3) {
++ if (packetplayinwindowclick.c() == 2) {
++ click = ClickType.MIDDLE;
++ if (packetplayinwindowclick.b() == -999) {
++ action = InventoryAction.NOTHING;
++ } else {
++ Slot slot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (slot != null && slot.hasItem() && player.abilities.canInstantlyBuild && player.inventory.getCarried() == null) {
++ action = InventoryAction.CLONE_STACK;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ } else {
++ click = ClickType.UNKNOWN;
++ action = InventoryAction.UNKNOWN;
++ }
++ } else if (packetplayinwindowclick.f() == 4) {
++ if (packetplayinwindowclick.b() >= 0) {
++ if (packetplayinwindowclick.c() == 0) {
++ click = ClickType.DROP;
++ Slot slot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (slot != null && slot.hasItem() && slot.isAllowed(player) && slot.getItem() != null && slot.getItem().getItem() != Item.getItemOf(Blocks.AIR)) {
++ action = InventoryAction.DROP_ONE_SLOT;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ } else if (packetplayinwindowclick.c() == 1) {
++ click = ClickType.CONTROL_DROP;
++ Slot slot = this.player.activeContainer.getSlot(packetplayinwindowclick.b());
++ if (slot != null && slot.hasItem() && slot.isAllowed(player) && slot.getItem() != null && slot.getItem().getItem() != Item.getItemOf(Blocks.AIR)) {
++ action = InventoryAction.DROP_ALL_SLOT;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ } else {
++ // Sane default (because this happens when they are holding nothing. Don't ask why.)
++ click = ClickType.LEFT;
++ if (packetplayinwindowclick.c() == 1) {
++ click = ClickType.RIGHT;
++ }
++ action = InventoryAction.NOTHING;
++ }
++ } else if (packetplayinwindowclick.f() == 5) {
++ itemstack = this.player.activeContainer.clickItem(packetplayinwindowclick.b(), packetplayinwindowclick.c(), 5, this.player);
++ } else if (packetplayinwindowclick.f() == 6) {
++ click = ClickType.DOUBLE_CLICK;
++ action = InventoryAction.NOTHING;
++ if (packetplayinwindowclick.b() >= 0 && this.player.inventory.getCarried() != null) {
++ ItemStack cursor = this.player.inventory.getCarried();
++ action = InventoryAction.NOTHING;
++ // Quick check for if we have any of the item
++ if (inventory.getTopInventory().contains(org.bukkit.Material.getMaterial(Item.getId(cursor.getItem()))) || inventory.getBottomInventory().contains(org.bukkit.Material.getMaterial(Item.getId(cursor.getItem())))) {
++ action = InventoryAction.COLLECT_TO_CURSOR;
++ }
++ }
++ }
++ // TODO check on updates
++
++ if (packetplayinwindowclick.f() != 5) {
++ if (click == ClickType.NUMBER_KEY) {
++ event = new InventoryClickEvent(inventory, type, packetplayinwindowclick.b(), click, action, packetplayinwindowclick.c());
++ } else {
++ event = new InventoryClickEvent(inventory, type, packetplayinwindowclick.b(), click, action);
++ }
++
++ org.bukkit.inventory.Inventory top = inventory.getTopInventory();
++ if (packetplayinwindowclick.b() == 0 && top instanceof CraftingInventory) {
++ org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe();
++ if (recipe != null) {
++ if (click == ClickType.NUMBER_KEY) {
++ event = new CraftItemEvent(recipe, inventory, type, packetplayinwindowclick.b(), click, action, packetplayinwindowclick.c());
++ } else {
++ event = new CraftItemEvent(recipe, inventory, type, packetplayinwindowclick.b(), click, action);
++ }
++ }
++ }
++
++ server.getPluginManager().callEvent(event);
++
++ switch (event.getResult()) {
++ case ALLOW:
++ case DEFAULT:
++ itemstack = this.player.activeContainer.clickItem(packetplayinwindowclick.b(), packetplayinwindowclick.c(), packetplayinwindowclick.f(), this.player);
++ break;
++ case DENY:
++ /* Needs enum constructor in InventoryAction
++ if (action.modifiesOtherSlots()) {
++
++ } else {
++ if (action.modifiesCursor()) {
++ this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried()));
++ }
++ if (action.modifiesClicked()) {
++ this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem()));
++ }
++ }*/
++ switch (action) {
++ // Modified other slots
++ case PICKUP_ALL:
++ case MOVE_TO_OTHER_INVENTORY:
++ case HOTBAR_MOVE_AND_READD:
++ case HOTBAR_SWAP:
++ case COLLECT_TO_CURSOR:
++ case UNKNOWN:
++ this.player.updateInventory(this.player.activeContainer);
++ break;
++ // Modified cursor and clicked
++ case PICKUP_SOME:
++ case PICKUP_HALF:
++ case PICKUP_ONE:
++ case PLACE_ALL:
++ case PLACE_SOME:
++ case PLACE_ONE:
++ case SWAP_WITH_CURSOR:
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, this.player.inventory.getCarried()));
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(this.player.activeContainer.windowId, packetplayinwindowclick.b(), this.player.activeContainer.getSlot(packetplayinwindowclick.b()).getItem()));
++ break;
++ // Modified clicked only
++ case DROP_ALL_SLOT:
++ case DROP_ONE_SLOT:
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(this.player.activeContainer.windowId, packetplayinwindowclick.b(), this.player.activeContainer.getSlot(packetplayinwindowclick.b()).getItem()));
++ break;
++ // Modified cursor only
++ case DROP_ALL_CURSOR:
++ case DROP_ONE_CURSOR:
++ case CLONE_STACK:
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, this.player.inventory.getCarried()));
++ break;
++ // Nothing
++ case NOTHING:
++ break;
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
+
+ if (ItemStack.matches(packetplayinwindowclick.e(), itemstack)) {
+ this.player.playerConnection.sendPacket(new PacketPlayOutTransaction(packetplayinwindowclick.a(), packetplayinwindowclick.d(), true));
+@@ -772,8 +1625,50 @@
+ }
+
+ boolean flag1 = packetplayinsetcreativeslot.a() >= 1 && packetplayinsetcreativeslot.a() < 36 + PlayerInventory.getHotbarSize();
+- boolean flag2 = itemstack == null || itemstack.getItem() != null;
++ // CraftBukkit - Add invalidItems check
++ boolean flag2 = itemstack == null || itemstack.getItem() != null && !invalidItems.contains(Item.getId(itemstack.getItem()));
+ boolean flag3 = itemstack == null || itemstack.getData() >= 0 && itemstack.count <= 64 && itemstack.count > 0;
++
++
++ // CraftBukkit start - Call click event
++ if (flag || (flag1 && !ItemStack.matches(this.player.defaultContainer.getSlot(packetplayinsetcreativeslot.a()).getItem(), packetplayinsetcreativeslot.getItemStack()))) { // Insist on valid slot
++
++ org.bukkit.entity.HumanEntity player = this.player.getBukkitEntity();
++ InventoryView inventory = new CraftInventoryView(player, player.getInventory(), this.player.defaultContainer);
++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packetplayinsetcreativeslot.getItemStack());
++
++ SlotType type = SlotType.QUICKBAR;
++ if (flag) {
++ type = SlotType.OUTSIDE;
++ } else if (packetplayinsetcreativeslot.a() < 36) {
++ if (packetplayinsetcreativeslot.a() >= 5 && packetplayinsetcreativeslot.a() < 9) {
++ type = SlotType.ARMOR;
++ } else {
++ type = SlotType.CONTAINER;
++ }
++ }
++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packetplayinsetcreativeslot.a(), item);
++ server.getPluginManager().callEvent(event);
++
++ itemstack = CraftItemStack.asNMSCopy(event.getCursor());
++
++ switch (event.getResult()) {
++ case ALLOW:
++ // Plugin cleared the id / stacksize checks
++ flag2 = flag3 = true;
++ break;
++ case DEFAULT:
++ break;
++ case DENY:
++ // Reset the slot
++ if (packetplayinsetcreativeslot.a() >= 0) {
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(this.player.defaultContainer.windowId, packetplayinsetcreativeslot.a(), this.player.defaultContainer.getSlot(packetplayinsetcreativeslot.a()).getItem()));
++ this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(-1, -1, null));
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
+
+ if (flag1 && flag2 && flag3) {
+ if (itemstack == null) {
+@@ -796,6 +1691,7 @@
+ }
+
+ public void a(PacketPlayInTransaction packetplayintransaction) {
++ if (this.player.dead) return; // CraftBukkit
+ PlayerConnectionUtils.ensureMainThread(packetplayintransaction, this, this.player.u());
+ Short oshort = (Short) this.n.get(this.player.activeContainer.windowId);
+
+@@ -806,6 +1702,7 @@
+ }
+
+ public void a(PacketPlayInUpdateSign packetplayinupdatesign) {
++ if (this.player.dead) return; // CraftBukkit
+ PlayerConnectionUtils.ensureMainThread(packetplayinupdatesign, this, this.player.u());
+ this.player.z();
+ WorldServer worldserver = this.minecraftServer.getWorldServer(this.player.dimension);
+@@ -822,10 +1719,24 @@
+
+ if (!tileentitysign.b() || tileentitysign.c() != this.player) {
+ this.minecraftServer.warning("Player " + this.player.getName() + " just tried to change non-editable sign");
++ this.sendPacket(new PacketPlayOutUpdateSign(tileentity.world, packetplayinupdatesign.a(), tileentitysign.lines)); // CraftBukkit
+ return;
+ }
+
+- System.arraycopy(packetplayinupdatesign.b(), 0, tileentitysign.lines, 0, 4);
++ // CraftBukkit start
++ Player player = this.server.getPlayer(this.player);
++ int x = packetplayinupdatesign.a().getX();
++ int y = packetplayinupdatesign.a().getY();
++ int z = packetplayinupdatesign.a().getZ();
++ SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.server.getPlayer(this.player), org.bukkit.craftbukkit.block.CraftSign.revertComponents(packetplayinupdatesign.b()));
++ this.server.getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ System.arraycopy(org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.getLines()), 0, tileentitysign.lines, 0, 4);
++ tileentitysign.isEditable = false;
++ }
++ // System.arraycopy(packetplayinupdatesign.b(), 0, tileentitysign.lines, 0, 4);
++ // CraftBukkit end
+ tileentitysign.update();
+ worldserver.notify(blockposition);
+ }
+@@ -847,11 +1758,28 @@
+
+ public void a(PacketPlayInAbilities packetplayinabilities) {
+ PlayerConnectionUtils.ensureMainThread(packetplayinabilities, this, this.player.u());
+- this.player.abilities.isFlying = packetplayinabilities.isFlying() && this.player.abilities.canFly;
++ // CraftBukkit start
++ if (this.player.abilities.canFly && this.player.abilities.isFlying != packetplayinabilities.isFlying()) {
++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.server.getPlayer(this.player), packetplayinabilities.isFlying());
++ this.server.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ this.player.abilities.isFlying = packetplayinabilities.isFlying(); // Actually set the player's flying status
++ } else {
++ this.player.updateAbilities(); // Tell the player their ability was reverted
++ }
++ }
++ // CraftBukkit end
+ }
+
+ public void a(PacketPlayInTabComplete packetplayintabcomplete) {
+ PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.u());
++ // CraftBukkit start
++ if (chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) {
++ this.disconnect("disconnect.spam");
++ return;
++ }
++ // CraftBukkit end
++
+ ArrayList arraylist = Lists.newArrayList();
+ Iterator iterator = this.minecraftServer.tabCompleteCommand(this.player, packetplayintabcomplete.a(), packetplayintabcomplete.b()).iterator();
+
+@@ -891,13 +1819,15 @@
+ itemstack1 = this.player.inventory.getItemInHand();
+ if (itemstack1 != null) {
+ if (itemstack.getItem() == Items.WRITABLE_BOOK && itemstack.getItem() == itemstack1.getItem()) {
+- itemstack1.a("pages", (NBTBase) itemstack.getTag().getList("pages", 8));
++ // itemstack1.a("pages", (NBTBase) itemstack.getTag().getList("pages", 8));
++ CraftEventFactory.handleEditBookEvent(player, itemstack); // CraftBukkit
+ }
+
+ return;
+ }
+ } catch (Exception exception) {
+ PlayerConnection.c.error("Couldn\'t handle book info", exception);
++ this.disconnect("Invalid book data!"); // CraftBukkit
+ return;
+ } finally {
+ packetdataserializer.release();
+@@ -909,27 +1839,31 @@
+
+ try {
+ itemstack = packetdataserializer.i();
+- if (itemstack == null) {
+- return;
+- }
++ if (itemstack != null) {
++ if (!ItemWrittenBook.b(itemstack.getTag())) {
++ throw new IOException("Invalid book tag!");
++ }
+
+- if (!ItemWrittenBook.b(itemstack.getTag())) {
+- throw new IOException("Invalid book tag!");
+- }
++ itemstack1 = this.player.inventory.getItemInHand();
++ if (itemstack1 == null) {
++ return;
++ }
+
+- itemstack1 = this.player.inventory.getItemInHand();
+- if (itemstack1 != null) {
+ if (itemstack.getItem() == Items.WRITTEN_BOOK && itemstack1.getItem() == Items.WRITABLE_BOOK) {
+- itemstack1.a("author", (NBTBase) (new NBTTagString(this.player.getName())));
+- itemstack1.a("title", (NBTBase) (new NBTTagString(itemstack.getTag().getString("title"))));
+- itemstack1.a("pages", (NBTBase) itemstack.getTag().getList("pages", 8));
+- itemstack1.setItem(Items.WRITTEN_BOOK);
++ // CraftBukkit start
++ // itemstack1.a("author", (NBTBase) (new NBTTagString(this.player.getName())));
++ // itemstack1.a("title", (NBTBase) (new NBTTagString(itemstack.getTag().getString("title"))));
++ // itemstack1.a("pages", (NBTBase) itemstack.getTag().getList("pages", 8));
++ // itemstack1.setItem(Items.WRITTEN_BOOK);
++ CraftEventFactory.handleEditBookEvent(player, itemstack);
++ // CraftBukkit end
+ }
+
+ return;
+ }
+ } catch (Exception exception1) {
+ PlayerConnection.c.error("Couldn\'t sign book", exception1);
++ this.disconnect("Invalid book data!"); // CraftBukkit
+ return;
+ } finally {
+ packetdataserializer.release();
+@@ -946,6 +1880,7 @@
+ }
+ } catch (Exception exception2) {
+ PlayerConnection.c.error("Couldn\'t select trade", exception2);
++ this.disconnect("Invalid trade data!"); // CraftBukkit
+ }
+ } else if ("MC|AdvCdm".equals(packetplayincustompayload.a())) {
+ if (!this.minecraftServer.getEnableCommandBlock()) {
+@@ -986,6 +1921,7 @@
+ }
+ } catch (Exception exception3) {
+ PlayerConnection.c.error("Couldn\'t set command block", exception3);
++ this.disconnect("Invalid CommandBlock data!"); // CraftBukkit
+ } finally {
+ packetdataserializer.release();
+ }
+@@ -1011,6 +1947,7 @@
+ }
+ } catch (Exception exception4) {
+ PlayerConnection.c.error("Couldn\'t set beacon", exception4);
++ this.disconnect("Invalid beacon data!"); // CraftBukkit
+ }
+ }
+ } else if ("MC|ItemName".equals(packetplayincustompayload.a()) && this.player.activeContainer instanceof ContainerAnvil) {
+@@ -1026,6 +1963,27 @@
+ containeranvil.a("");
+ }
+ }
++ // CraftBukkit start
++ else if (packetplayincustompayload.a().equals("REGISTER")) {
++ String channels = packetplayincustompayload.b().toString(com.google.common.base.Charsets.UTF_8);
++ for (String channel : channels.split("\0")) {
++ getPlayer().addChannel(channel);
++ }
++ } else if (packetplayincustompayload.a().equals("UNREGISTER")) {
++ String channels = packetplayincustompayload.b().toString(com.google.common.base.Charsets.UTF_8);
++ for (String channel : channels.split("\0")) {
++ getPlayer().removeChannel(channel);
++ }
++ } else {
++ byte[] data = new byte[packetplayincustompayload.b().readableBytes()];
++ packetplayincustompayload.b().readBytes(data);
++ server.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packetplayincustompayload.a(), data);
++ }
++ // CraftBukkit end
++ }
+
++ // CraftBukkit start - Add "isDisconnected" method
++ public final boolean isDisconnected() {
++ return !this.player.joining && !NetworkManager.a(this.networkManager).config().isAutoRead();
+ }
+ }
diff --git a/nms-patches/PlayerDatFileConverter.patch b/nms-patches/PlayerDatFileConverter.patch
new file mode 100644
index 00000000..a1286509
--- /dev/null
+++ b/nms-patches/PlayerDatFileConverter.patch
@@ -0,0 +1,33 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerDatFileConverter.java 2014-11-27 08:59:46.857421159 +1100
++++ src/main/java/net/minecraft/server/PlayerDatFileConverter.java 2014-11-27 08:42:10.168850880 +1100
+@@ -47,6 +47,30 @@
+ private void a(File file, String s, String s1) {
+ File file1 = new File(this.d, s + ".dat");
+ File file2 = new File(file, s1 + ".dat");
++
++ // CraftBukkit start - Use old file name to seed lastKnownName
++ NBTTagCompound root = null;
++
++ try {
++ root = NBTCompressedStreamTools.a(new java.io.FileInputStream(file1));
++ } catch (Exception exception) {
++ exception.printStackTrace();
++ }
++
++ if (root != null) {
++ if (!root.hasKey("bukkit")) {
++ root.set("bukkit", new NBTTagCompound());
++ }
++ NBTTagCompound data = root.getCompound("bukkit");
++ data.setString("lastKnownName", s);
++
++ try {
++ NBTCompressedStreamTools.a(root, new java.io.FileOutputStream(file2));
++ } catch (Exception exception) {
++ exception.printStackTrace();
++ }
++ }
++ // CraftBukkit end
+
+ NameReferencingFileConverter.a(file);
+ if (!file1.renameTo(file2)) {
diff --git a/nms-patches/PlayerInteractManager.patch b/nms-patches/PlayerInteractManager.patch
new file mode 100644
index 00000000..ac0ab569
--- /dev/null
+++ b/nms-patches/PlayerInteractManager.patch
@@ -0,0 +1,289 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerInteractManager.java 2014-11-27 08:59:46.857421159 +1100
++++ src/main/java/net/minecraft/server/PlayerInteractManager.java 2014-11-27 08:42:10.108850996 +1100
+@@ -1,5 +1,13 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.event.block.BlockBreakEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.Event;
++import org.bukkit.event.block.Action;
++import org.bukkit.event.player.PlayerInteractEvent;
++// CraftBukkit end
++
+ public class PlayerInteractManager {
+
+ public World world;
+@@ -50,7 +58,7 @@
+ }
+
+ public void a() {
+- ++this.currentTick;
++ this.currentTick = MinecraftServer.currentTick; // CraftBukkit;
+ float f;
+ int i;
+
+@@ -95,6 +103,19 @@
+ }
+
+ public void a(BlockPosition blockposition, EnumDirection enumdirection) {
++ // CraftBukkit start
++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, blockposition, enumdirection, this.player.inventory.getItemInHand());
++ if (event.isCancelled()) {
++ // Let the client know the block still exists
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ // Update any tile entity data for this block
++ TileEntity tileentity = this.world.getTileEntity(blockposition);
++ if (tileentity != null) {
++ this.player.playerConnection.sendPacket(tileentity.getUpdatePacket());
++ }
++ return;
++ }
++ // CraftBukkit end
+ if (this.isCreative()) {
+ if (!this.world.douseFire((EntityHuman) null, blockposition, enumdirection)) {
+ this.breakBlock(blockposition);
+@@ -121,15 +142,49 @@
+ }
+ }
+
+- this.world.douseFire((EntityHuman) null, blockposition, enumdirection);
++ // this.world.douseFire((EntityHuman) null, blockposition, enumdirection); // CraftBukkit - Moved down
+ this.lastDigTick = this.currentTick;
+ float f = 1.0F;
+
+- if (block.getMaterial() != Material.AIR) {
++ // CraftBukkit start - Swings at air do *NOT* exist.
++ if (event.useInteractedBlock() == Event.Result.DENY) {
++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
++ IBlockData data = this.world.getType(blockposition);
++ if (block == Blocks.WOODEN_DOOR) {
++ // For some reason *BOTH* the bottom/top part have to be marked updated.
++ boolean bottom = data.get(BlockDoor.HALF) == EnumDoorHalf.LOWER;
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, bottom ? blockposition.up() : blockposition.down()));
++ } else if (block == Blocks.TRAPDOOR) {
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ }
++ } else if (block.getMaterial() != Material.AIR) {
+ block.attack(this.world, blockposition, this.player);
+ f = block.getDamage(this.player, this.player.world, blockposition);
++ // Allow fire punching to be blocked
++ this.world.douseFire((EntityHuman) null, blockposition, enumdirection);
++ }
++
++ if (event.useItemInHand() == Event.Result.DENY) {
++ // If we 'insta destroyed' then the client needs to be informed.
++ if (f > 1.0f) {
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ }
++ return;
++ }
++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.player.inventory.getItemInHand(), f >= 1.0f);
++
++ if (blockEvent.isCancelled()) {
++ // Let the client know the block still exists
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ return;
+ }
+
++ if (blockEvent.getInstaBreak()) {
++ f = 2.0f;
++ }
++ // CraftBukkit end
++
+ if (block.getMaterial() != Material.AIR && f >= 1.0F) {
+ this.breakBlock(blockposition);
+ } else {
+@@ -146,6 +201,7 @@
+
+ public void a(BlockPosition blockposition) {
+ if (blockposition.equals(this.f)) {
++ this.currentTick = MinecraftServer.currentTick; // CraftBukkit
+ int i = this.currentTick - this.lastDigTick;
+ Block block = this.world.getType(blockposition).getBlock();
+
+@@ -163,6 +219,10 @@
+ this.j = this.lastDigTick;
+ }
+ }
++ // CraftBukkit start - Force block reset to client
++ } else {
++ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ // CraftBukkit end
+ }
+
+ }
+@@ -186,12 +246,72 @@
+ }
+
+ public boolean breakBlock(BlockPosition blockposition) {
+- if (this.gamemode.d() && this.player.bz() != null && this.player.bz().getItem() instanceof ItemSword) {
++ // CraftBukkit start - fire BlockBreakEvent
++ BlockBreakEvent event = null;
++
++ if (this.player instanceof EntityPlayer) {
++ org.bukkit.block.Block block = this.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++
++ // Sword + Creative mode pre-cancel
++ boolean isSwordNoBreak = this.gamemode.d() && this.player.bz() != null && this.player.bz().getItem() instanceof ItemSword;
++
++ // Tell client the block is gone immediately then process events
++ // Don't tell the client if its a creative sword break because its not broken!
++ if (world.getTileEntity(blockposition) == null && !isSwordNoBreak) {
++ PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(this.world, blockposition);
++ packet.block = Blocks.AIR.getBlockData();
++ ((EntityPlayer) this.player).playerConnection.sendPacket(packet);
++ }
++
++ event = new BlockBreakEvent(block, this.player.getBukkitEntity());
++
++ // Sword + Creative mode pre-cancel
++ event.setCancelled(isSwordNoBreak);
++
++ // Calculate default block experience
++ IBlockData nmsData = this.world.getType(blockposition);
++ Block nmsBlock = nmsData.getBlock();
++
++ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.b(nmsBlock)) {
++ // Copied from block.a(World world, EntityHuman entityhuman, BlockPosition blockposition, IBlockData iblockdata, TileEntity tileentity)
++ if (!(nmsBlock.G() && EnchantmentManager.hasSilkTouchEnchantment(this.player))) {
++ int data = block.getData();
++ int bonusLevel = EnchantmentManager.getBonusBlockLootEnchantmentLevel(this.player);
++
++ event.setExpToDrop(nmsBlock.getExpDrop(this.world, nmsData, bonusLevel));
++ }
++ }
++
++ this.world.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ if (isSwordNoBreak) {
++ return false;
++ }
++ // Let the client know the block still exists
++ ((EntityPlayer) this.player).playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
++ // Update any tile entity data for this block
++ TileEntity tileentity = this.world.getTileEntity(blockposition);
++ if (tileentity != null) {
++ this.player.playerConnection.sendPacket(tileentity.getUpdatePacket());
++ }
++ return false;
++ }
++ }
++ if (false && this.gamemode.d() && this.player.bz() != null && this.player.bz().getItem() instanceof ItemSword) {
+ return false;
+ } else {
+ IBlockData iblockdata = this.world.getType(blockposition);
++ if (iblockdata.getBlock() == Blocks.AIR) return false; // CraftBukkit - A plugin set block to air without cancelling
+ TileEntity tileentity = this.world.getTileEntity(blockposition);
+-
++
++ // CraftBukkit start - Special case skulls, their item data comes from a tile entity
++ if (iblockdata.getBlock() == Blocks.SKULL && !this.isCreative()) {
++ iblockdata.getBlock().dropNaturally(world, blockposition, iblockdata, 1.0F, 0);
++ return this.c(blockposition);
++ }
++ // CraftBukkit end
++
+ if (this.gamemode.c()) {
+ if (this.gamemode == EnumGamemode.SPECTATOR) {
+ return false;
+@@ -229,7 +349,13 @@
+ if (flag && flag1) {
+ iblockdata.getBlock().a(this.world, this.player, blockposition, iblockdata, tileentity);
+ }
++ }
++
++ // CraftBukkit start - Drop event experience
++ if (flag && event != null) {
++ iblockdata.getBlock().dropExperience(this.world, blockposition, event.getExpToDrop());
+ }
++ // CraftBukkit end
+
+ return flag;
+ }
+@@ -268,6 +394,7 @@
+ }
+
+ public boolean interact(EntityHuman entityhuman, World world, ItemStack itemstack, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2) {
++ /* CraftBukkit start - whole method
+ if (this.gamemode == EnumGamemode.SPECTATOR) {
+ TileEntity tileentity = world.getTileEntity(blockposition);
+
+@@ -312,6 +439,75 @@
+ return itemstack.placeItem(entityhuman, world, blockposition, enumdirection, f, f1, f2);
+ }
+ }
++ // Interract event */
++ IBlockData blockdata = world.getType(blockposition);
++ boolean result = false;
++ if (blockdata.getBlock() != Blocks.AIR) {
++ boolean cancelledBlock = false;
++
++ if (this.gamemode == EnumGamemode.SPECTATOR) {
++ TileEntity tileentity = world.getTileEntity(blockposition);
++ cancelledBlock = !(tileentity instanceof ITileInventory || tileentity instanceof IInventory);
++ }
++
++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(entityhuman, Action.RIGHT_CLICK_BLOCK, blockposition, enumdirection, itemstack, cancelledBlock);
++
++ if (event.useInteractedBlock() == Event.Result.DENY) {
++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
++ if (blockdata.getBlock() instanceof BlockDoor) {
++ boolean bottom = blockdata.get(BlockDoor.HALF) == EnumDoorHalf.LOWER;
++ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutBlockChange(world, bottom ? blockposition.up() : blockposition.down()));
++ }
++ result = (event.useItemInHand() != Event.Result.ALLOW);
++ } else if (this.gamemode == EnumGamemode.SPECTATOR) {
++ TileEntity tileentity = world.getTileEntity(blockposition);
++
++ if (tileentity instanceof ITileInventory) {
++ Block block = world.getType(blockposition).getBlock();
++ ITileInventory itileinventory = (ITileInventory) tileentity;
++
++ if (itileinventory instanceof TileEntityChest && block instanceof BlockChest) {
++ itileinventory = ((BlockChest) block).d(world, blockposition);
++ }
++
++ if (itileinventory != null) {
++ entityhuman.openContainer(itileinventory);
++ return true;
++ }
++ } else if (tileentity instanceof IInventory) {
++ entityhuman.openContainer((IInventory) tileentity);
++ return true;
++ }
++
++ return false;
++ } else if (!entityhuman.isSneaking() || itemstack == null) {
++ result = blockdata.getBlock().interact(world, blockposition, blockdata, entityhuman, enumdirection, f, f1, f2);
++ }
++
++ if (itemstack != null && !result) {
++ int j1 = itemstack.getData();
++ int k1 = itemstack.count;
++
++ result = itemstack.placeItem(entityhuman, world, blockposition, enumdirection, f, f1, f2);
++
++ // The item count should not decrement in Creative mode.
++ if (this.isCreative()) {
++ itemstack.setData(j1);
++ itemstack.count = k1;
++ }
++ }
++
++ // If we have 'true' and no explicit deny *or* an explicit allow -- run the item part of the hook
++ if (itemstack != null && ((!result && event.useItemInHand() != Event.Result.DENY) || event.useItemInHand() == Event.Result.ALLOW)) {
++ if (itemstack.getItem() instanceof ItemBucket) {
++ this.useItem(entityhuman, world, itemstack);
++ } else {
++ itemstack.placeItem(entityhuman, world, blockposition, enumdirection, f, f1, f2);
++ }
++ }
++ }
++ return result;
++ // CraftBukkit end
+ }
+
+ public void a(WorldServer worldserver) {
diff --git a/nms-patches/PlayerInventory.patch b/nms-patches/PlayerInventory.patch
new file mode 100644
index 00000000..e4b36670
--- /dev/null
+++ b/nms-patches/PlayerInventory.patch
@@ -0,0 +1,100 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerInventory.java 2014-11-27 08:59:46.861421142 +1100
++++ src/main/java/net/minecraft/server/PlayerInventory.java 2014-11-27 08:42:10.172850872 +1100
+@@ -2,6 +2,13 @@
+
+ import java.util.concurrent.Callable;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class PlayerInventory implements IInventory {
+
+ public ItemStack[] items = new ItemStack[36];
+@@ -10,6 +17,39 @@
+ public EntityHuman player;
+ private ItemStack f;
+ public boolean e;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public ItemStack[] getArmorContents() {
++ return this.armor;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return this.player.getBukkitEntity();
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public PlayerInventory(EntityHuman entityhuman) {
+ this.player = entityhuman;
+@@ -42,6 +82,22 @@
+
+ return -1;
+ }
++
++ // CraftBukkit start - Watch method above! :D
++ public int canHold(ItemStack itemstack) {
++ int remains = itemstack.count;
++ for (int i = 0; i < this.items.length; ++i) {
++ if (this.items[i] == null) return itemstack.count;
++
++ // Taken from firstPartial(ItemStack)
++ if (this.items[i] != null && this.items[i].getItem() == itemstack.getItem() && this.items[i].isStackable() && this.items[i].count < this.items[i].getMaxStackSize() && this.items[i].count < this.getMaxStackSize() && (!this.items[i].usesData() || this.items[i].getData() == itemstack.getData()) && ItemStack.equals(this.items[i], itemstack)) {
++ remains -= (this.items[i].getMaxStackSize() < this.getMaxStackSize() ? this.items[i].getMaxStackSize() : this.getMaxStackSize()) - this.items[i].count;
++ }
++ if (remains <= 0) return itemstack.count;
++ }
++ return itemstack.count - remains;
++ }
++ // CraftBukkit end
+
+ public int getFirstEmptySlotIndex() {
+ for (int i = 0; i < this.items.length; ++i) {
+@@ -382,7 +438,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean b(Block block) {
+@@ -458,6 +514,11 @@
+ }
+
+ public ItemStack getCarried() {
++ // CraftBukkit start
++ if (this.f != null && this.f.count == 0) {
++ this.setCarried(null);
++ }
++ // CraftBukkit end
+ return this.f;
+ }
+
diff --git a/nms-patches/PlayerList.patch b/nms-patches/PlayerList.patch
new file mode 100644
index 00000000..f38a5b9d
--- /dev/null
+++ b/nms-patches/PlayerList.patch
@@ -0,0 +1,788 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerList.java 2014-11-27 08:59:46.861421142 +1100
++++ src/main/java/net/minecraft/server/PlayerList.java 2014-11-27 08:42:10.160850895 +1100
+@@ -18,6 +18,25 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
++
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.TravelAgent;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerChangedWorldEvent;
++import org.bukkit.event.player.PlayerPortalEvent;
++import org.bukkit.event.player.PlayerJoinEvent;
++import org.bukkit.event.player.PlayerLoginEvent;
++import org.bukkit.event.player.PlayerQuitEvent;
++import org.bukkit.event.player.PlayerRespawnEvent;
++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
++import org.bukkit.util.Vector;
++// CraftBukkit end
++
+ public abstract class PlayerList {
+
+ public static final File a = new File("banned-players.json");
+@@ -27,7 +46,7 @@
+ private static final Logger h = LogManager.getLogger();
+ private static final SimpleDateFormat i = new SimpleDateFormat("yyyy-MM-dd \'at\' HH:mm:ss z");
+ private final MinecraftServer server;
+- public final List players = Lists.newArrayList();
++ public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
+ public final Map f = Maps.newHashMap();
+ private final GameProfileBanList k;
+ private final IpBanList l;
+@@ -42,7 +61,15 @@
+ private boolean t;
+ private int u;
+
++ // CraftBukkit start
++ private CraftServer cserver;
++
+ public PlayerList(MinecraftServer minecraftserver) {
++ this.cserver = minecraftserver.server = new CraftServer(minecraftserver, this);
++ minecraftserver.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance();
++ minecraftserver.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(minecraftserver.server));
++ // CraftBukkit end
++
+ this.k = new GameProfileBanList(PlayerList.a);
+ this.l = new IpBanList(PlayerList.b);
+ this.operators = new OpList(PlayerList.c);
+@@ -71,7 +98,8 @@
+ s1 = networkmanager.getSocketAddress().toString();
+ }
+
+- PlayerList.h.info(entityplayer.getName() + "[" + s1 + "] logged in with entity id " + entityplayer.getId() + " at (" + entityplayer.locX + ", " + entityplayer.locY + ", " + entityplayer.locZ + ")");
++ // CraftBukkit - Moved message to after join
++ // PlayerList.h.info(entityplayer.getName() + "[" + s1 + "] logged in with entity id " + entityplayer.getId() + " at (" + entityplayer.locX + ", " + entityplayer.locY + ", " + entityplayer.locZ + ")");
+ WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension);
+ WorldData worlddata = worldserver.getWorldData();
+ BlockPosition blockposition = worldserver.getSpawn();
+@@ -80,6 +108,7 @@
+ PlayerConnection playerconnection = new PlayerConnection(this.server, networkmanager, entityplayer);
+
+ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), worlddata.isHardcore(), worldserver.worldProvider.getDimension(), worldserver.getDifficulty(), this.getMaxPlayers(), worlddata.getType(), worldserver.getGameRules().getBoolean("reducedDebugInfo")));
++ entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
+ playerconnection.sendPacket(new PacketPlayOutCustomPayload("MC|Brand", (new PacketDataSerializer(Unpooled.buffer())).a(this.getServer().getServerModName())));
+ playerconnection.sendPacket(new PacketPlayOutServerDifficulty(worlddata.y(), worlddata.z()));
+ playerconnection.sendPacket(new PacketPlayOutSpawnPosition(blockposition));
+@@ -89,6 +118,7 @@
+ entityplayer.getStatisticManager().updateStatistics(entityplayer);
+ this.sendScoreboard((ScoreboardServer) worldserver.getScoreboard(), entityplayer);
+ this.server.aF();
++ /* CraftBukkit start - login message is handled in the event
+ ChatMessage chatmessage;
+
+ if (!entityplayer.getName().equalsIgnoreCase(s)) {
+@@ -99,7 +129,9 @@
+
+ chatmessage.getChatModifier().setColor(EnumChatFormat.YELLOW);
+ this.sendMessage(chatmessage);
++ // CraftBukkit end */
+ this.onPlayerJoin(entityplayer);
++ worldserver = server.getWorldServer(entityplayer.dimension); // CraftBukkit - Update in case join event changed it
+ playerconnection.a(entityplayer.locX, entityplayer.locY, entityplayer.locZ, entityplayer.yaw, entityplayer.pitch);
+ this.b(entityplayer, worldserver);
+ if (this.server.getResourcePack().length() > 0) {
+@@ -126,6 +158,8 @@
+ }
+ }
+
++ // CraftBukkit - Moved from above, added world
++ PlayerList.h.info(entityplayer.getName() + "[" + s1 + "] logged in with entity id " + entityplayer.getId() + " at ([" + entityplayer.world.worldData.getName() + "] " + entityplayer.locX + ", " + entityplayer.locY + ", " + entityplayer.locZ + ")");
+ }
+
+ public void sendScoreboard(ScoreboardServer scoreboardserver, EntityPlayer entityplayer) {
+@@ -158,6 +192,7 @@
+ }
+
+ public void setPlayerFileData(WorldServer[] aworldserver) {
++ if (playerFileData != null) return; // CraftBukkit
+ this.playerFileData = aworldserver[0].getDataManager().getPlayerFileData();
+ aworldserver[0].af().a((IWorldBorderListener) (new WorldBorderListener(this)));
+ }
+@@ -178,7 +213,7 @@
+ }
+
+ public NBTTagCompound a(EntityPlayer entityplayer) {
+- NBTTagCompound nbttagcompound = this.server.worldServer[0].getWorldData().i();
++ NBTTagCompound nbttagcompound = this.server.worlds.get(0).getWorldData().i(); // CraftBukkit
+ NBTTagCompound nbttagcompound1;
+
+ if (entityplayer.getName().equals(this.server.R()) && nbttagcompound != null) {
+@@ -205,30 +240,69 @@
+ public void onPlayerJoin(EntityPlayer entityplayer) {
+ this.players.add(entityplayer);
+ this.f.put(entityplayer.getUniqueID(), entityplayer);
+- this.sendAll(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[] { entityplayer}));
++ // this.sendAll(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[] { entityplayer})); // CraftBukkit - replaced with loop below
+ WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension);
+
+- worldserver.addEntity(entityplayer);
+- this.a(entityplayer, (WorldServer) null);
++ // CraftBukkit start
++ PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getName() + " joined the game.");
++ cserver.getPluginManager().callEvent(playerJoinEvent);
++
++ String joinMessage = playerJoinEvent.getJoinMessage();
++
++ if (joinMessage != null && joinMessage.length() > 0) {
++ for (IChatBaseComponent line : org.bukkit.craftbukkit.util.CraftChatMessage.fromString(joinMessage)) {
++ server.getPlayerList().sendAll(new PacketPlayOutChat(line));
++ }
++ }
++
++ ChunkIOExecutor.adjustPoolSize(getPlayerCount());
++ // CraftBukkit end
++
++ // CraftBukkit start - sendAll above replaced with this loop
++ PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, entityplayer);
+
+ for (int i = 0; i < this.players.size(); ++i) {
+ EntityPlayer entityplayer1 = (EntityPlayer) this.players.get(i);
+
++ if (entityplayer1.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) {
++ entityplayer1.playerConnection.sendPacket(packet);
++ }
++
++ if (!entityplayer.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) {
++ continue;
++ }
++
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[] { entityplayer1}));
+ }
++ // CraftBukkit end
+
++ // CraftBukkit start - Only add if the player wasn't moved in the event
++ if (entityplayer.world == worldserver && !worldserver.players.contains(entityplayer)) {
++ worldserver.addEntity(entityplayer);
++ this.a(entityplayer, (WorldServer) null);
++ }
++ // CraftBukkit end
+ }
+
+ public void d(EntityPlayer entityplayer) {
+ entityplayer.u().getPlayerChunkMap().movePlayer(entityplayer);
+ }
+
+- public void disconnect(EntityPlayer entityplayer) {
++ public String disconnect(EntityPlayer entityplayer) { // CraftBukkit - return string
+ entityplayer.b(StatisticList.f);
++
++ // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(entityplayer);
++
++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getName() + " left the game.");
++ cserver.getPluginManager().callEvent(playerQuitEvent);
++ entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
++ // CraftBukkit end
++
+ this.savePlayerFile(entityplayer);
+ WorldServer worldserver = entityplayer.u();
+
+- if (entityplayer.vehicle != null) {
++ if (entityplayer.vehicle != null && !(entityplayer.vehicle instanceof EntityPlayer)) { // CraftBukkit - Don't remove players
+ worldserver.removeEntity(entityplayer.vehicle);
+ PlayerList.h.debug("removing player mount");
+ }
+@@ -238,13 +312,40 @@
+ this.players.remove(entityplayer);
+ this.f.remove(entityplayer.getUniqueID());
+ this.o.remove(entityplayer.getUniqueID());
+- this.sendAll(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[] { entityplayer}));
++ // CraftBukkit start
++ // this.sendAll(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[] { entityplayer}));
++ PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, entityplayer);
++ for (int i = 0; i < players.size(); i++) {
++ EntityPlayer entityplayer1 = (EntityPlayer) this.players.get(i);
++
++ if (entityplayer1.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) {
++ entityplayer1.playerConnection.sendPacket(packet);
++ } else {
++ entityplayer1.getBukkitEntity().removeDisconnectingPlayer(entityplayer.getBukkitEntity());
++ }
++ }
++ // This removes the scoreboard (and player reference) for the specific player in the manager
++ cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
++ // CraftBukkit end
++
++ ChunkIOExecutor.adjustPoolSize(this.getPlayerCount()); // CraftBukkit
++
++ return playerQuitEvent.getQuitMessage(); // CraftBukkit
+ }
+
+- public String attemptLogin(SocketAddress socketaddress, GameProfile gameprofile) {
++ // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
++ public EntityPlayer attemptLogin(LoginListener loginlistener, GameProfile gameprofile, String hostname) {
++ // Instead of kicking then returning, we need to store the kick reason
++ // in the event, check with plugins to see if it's ok, and THEN kick
++ // depending on the outcome.
++ SocketAddress socketaddress = loginlistener.networkManager.getSocketAddress();
++
++ EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), gameprofile, new PlayerInteractManager(server.getWorldServer(0)));
++ Player player = entity.getBukkitEntity();
++ PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress());
+ String s;
+
+- if (this.k.isBanned(gameprofile)) {
++ if (getProfileBans().isBanned(gameprofile) && !getProfileBans().get(gameprofile).hasExpired()) {
+ GameProfileBanEntry gameprofilebanentry = (GameProfileBanEntry) this.k.get(gameprofile);
+
+ s = "You are banned from this server!\nReason: " + gameprofilebanentry.getReason();
+@@ -252,10 +353,12 @@
+ s = s + "\nYour ban will be removed on " + PlayerList.i.format(gameprofilebanentry.getExpires());
+ }
+
+- return s;
++ // return s;
++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s);
+ } else if (!this.isWhitelisted(gameprofile)) {
+- return "You are not white-listed on this server!";
+- } else if (this.l.isBanned(socketaddress)) {
++ // return "You are not white-listed on this server!";
++ event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, "You are not white-listed on this server!");
++ } else if (getIPBans().isBanned(socketaddress) && !getIPBans().get(socketaddress).hasExpired()) {
+ IpBanEntry ipbanentry = this.l.get(socketaddress);
+
+ s = "Your IP address is banned from this server!\nReason: " + ipbanentry.getReason();
+@@ -263,13 +366,24 @@
+ s = s + "\nYour ban will be removed on " + PlayerList.i.format(ipbanentry.getExpires());
+ }
+
+- return s;
++ // return s;
++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s);
+ } else {
+- return this.players.size() >= this.maxPlayers ? "The server is full!" : null;
++ // 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");
++ }
++ }
++
++ cserver.getPluginManager().callEvent(event);
++ if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
++ loginlistener.disconnect(event.getKickMessage());
++ return null;
+ }
++ return entity;
+ }
+
+- public EntityPlayer processLogin(GameProfile gameprofile) {
++ public EntityPlayer processLogin(GameProfile gameprofile, EntityPlayer player) { // CraftBukkit - added EntityPlayer
+ UUID uuid = EntityHuman.a(gameprofile);
+ ArrayList arraylist = Lists.newArrayList();
+
+@@ -289,6 +403,7 @@
+ entityplayer.playerConnection.disconnect("You logged in from another location");
+ }
+
++ /* CraftBukkit start
+ Object object;
+
+ if (this.server.W()) {
+@@ -298,17 +413,25 @@
+ }
+
+ return new EntityPlayer(this.server, this.server.getWorldServer(0), gameprofile, (PlayerInteractManager) object);
++ // */
++ return player;
++ // CraftBukkit end
+ }
+
++ // CraftBukkit start
+ public EntityPlayer moveToWorld(EntityPlayer entityplayer, int i, boolean flag) {
++ return this.moveToWorld(entityplayer, i, flag, null, true);
++ }
++ public EntityPlayer moveToWorld(EntityPlayer entityplayer, int i, boolean flag, Location location, boolean avoidSuffocation) {
+ entityplayer.u().getTracker().untrackPlayer(entityplayer);
+- entityplayer.u().getTracker().untrackEntity(entityplayer);
++ // entityplayer.u().getTracker().untrackEntity(entityplayer); // CraftBukkit
+ entityplayer.u().getPlayerChunkMap().removePlayer(entityplayer);
+ this.players.remove(entityplayer);
+ this.server.getWorldServer(entityplayer.dimension).removeEntity(entityplayer);
+ BlockPosition blockposition = entityplayer.getBed();
+ boolean flag1 = entityplayer.isRespawnForced();
+
++ /* CraftBukkit start
+ entityplayer.dimension = i;
+ Object object;
+
+@@ -319,80 +442,270 @@
+ }
+
+ EntityPlayer entityplayer1 = new EntityPlayer(this.server, this.server.getWorldServer(entityplayer.dimension), entityplayer.getProfile(), (PlayerInteractManager) object);
++ // */
++ EntityPlayer entityplayer1 = entityplayer;
++ org.bukkit.World fromWorld = entityplayer.getBukkitEntity().getWorld();
++ entityplayer.viewingCredits = false;
++ // CraftBukkit end
+
+ entityplayer1.playerConnection = entityplayer.playerConnection;
+ entityplayer1.copyTo(entityplayer, flag);
+ entityplayer1.d(entityplayer.getId());
+ entityplayer1.o(entityplayer);
+- WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension);
++ // WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension); // CraftBukkit - handled later
+
+- this.a(entityplayer1, entityplayer, worldserver);
++ // this.a(entityplayer1, entityplayer, worldserver); // CraftBukkit - removed
+ BlockPosition blockposition1;
+
+- if (blockposition != null) {
+- blockposition1 = EntityHuman.getBed(this.server.getWorldServer(entityplayer.dimension), blockposition, flag1);
+- if (blockposition1 != null) {
+- entityplayer1.setPositionRotation((double) ((float) blockposition1.getX() + 0.5F), (double) ((float) blockposition1.getY() + 0.1F), (double) ((float) blockposition1.getZ() + 0.5F), 0.0F, 0.0F);
+- entityplayer1.setRespawnPosition(blockposition, flag1);
+- } else {
+- entityplayer1.playerConnection.sendPacket(new PacketPlayOutGameStateChange(0, 0.0F));
++ // CraftBukkit start - fire PlayerRespawnEvent
++ if (location == null) {
++ boolean isBedSpawn = false;
++ CraftWorld cworld = (CraftWorld) this.server.server.getWorld(entityplayer.spawnWorld);
++ if (cworld != null && blockposition != null) {
++ blockposition1 = EntityHuman.getBed(cworld.getHandle(), blockposition, flag1);
++ if (blockposition1 != null) {
++ isBedSpawn = true;
++ location = new Location(cworld, blockposition1.getX() + 0.5, blockposition1.getY(), blockposition1.getZ() + 0.5);
++ } else {
++ entityplayer1.setRespawnPosition(null, true);
++ entityplayer1.playerConnection.sendPacket(new PacketPlayOutGameStateChange(0, 0.0F));
++ }
+ }
++
++ if (location == null) {
++ cworld = (CraftWorld) this.server.server.getWorlds().get(0);
++ blockposition = cworld.getHandle().getSpawn();
++ location = new Location(cworld, blockposition.getX() + 0.5, blockposition.getY(), blockposition.getZ() + 0.5);
++ }
++
++ Player respawnPlayer = cserver.getPlayer(entityplayer1);
++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn);
++ cserver.getPluginManager().callEvent(respawnEvent);
++
++ location = respawnEvent.getRespawnLocation();
++ entityplayer.reset();
++ } else {
++ location.setWorld(server.getWorldServer(i).getWorld());
+ }
++ WorldServer worldserver = ((CraftWorld) location.getWorld()).getHandle();
++ entityplayer1.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
++ // CraftBukkit end
+
+ worldserver.chunkProviderServer.getChunkAt((int) entityplayer1.locX >> 4, (int) entityplayer1.locZ >> 4);
+
+- while (!worldserver.getCubes(entityplayer1, entityplayer1.getBoundingBox()).isEmpty() && entityplayer1.locY < 256.0D) {
++ while (avoidSuffocation && !worldserver.getCubes(entityplayer1, entityplayer1.getBoundingBox()).isEmpty() && entityplayer1.locY < 256.0D) { // CraftBukkit
+ entityplayer1.setPosition(entityplayer1.locX, entityplayer1.locY + 1.0D, entityplayer1.locZ);
+ }
+
+- entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(entityplayer1.dimension, entityplayer1.world.getDifficulty(), entityplayer1.world.getWorldData().getType(), entityplayer1.playerInteractManager.getGameMode()));
++ // CraftBukkit start
++ byte actualDimension = (byte) (worldserver.getWorld().getEnvironment().getId());
++ // Force the client to refresh their chunk cache
++ entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn((byte) (actualDimension >= 0 ? -1 : 0), worldserver.getDifficulty(), worldserver.getWorldData().getType(), entityplayer.playerInteractManager.getGameMode()));
++ entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(actualDimension, worldserver.getDifficulty(), worldserver.getWorldData().getType(), entityplayer1.playerInteractManager.getGameMode()));
++ entityplayer1.spawnIn(worldserver);
++ entityplayer1.dead = false;
++ entityplayer1.playerConnection.teleport(new Location(worldserver.getWorld(), entityplayer1.locX, entityplayer1.locY, entityplayer1.locZ, entityplayer1.yaw, entityplayer1.pitch));
++ entityplayer1.setSneaking(false);
+ blockposition1 = worldserver.getSpawn();
+- entityplayer1.playerConnection.a(entityplayer1.locX, entityplayer1.locY, entityplayer1.locZ, entityplayer1.yaw, entityplayer1.pitch);
++ // entityplayer1.playerConnection.a(entityplayer1.locX, entityplayer1.locY, entityplayer1.locZ, entityplayer1.yaw, entityplayer1.pitch);
++ // CraftBukkit end
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutSpawnPosition(blockposition1));
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutExperience(entityplayer1.exp, entityplayer1.expTotal, entityplayer1.expLevel));
+ this.b(entityplayer1, worldserver);
+- worldserver.getPlayerChunkMap().addPlayer(entityplayer1);
+- worldserver.addEntity(entityplayer1);
+- this.players.add(entityplayer1);
+- this.f.put(entityplayer1.getUniqueID(), entityplayer1);
+- entityplayer1.syncInventory();
++ if (!entityplayer.playerConnection.isDisconnected()) {
++ worldserver.getPlayerChunkMap().addPlayer(entityplayer1);
++ worldserver.addEntity(entityplayer1);
++ this.players.add(entityplayer1);
++ this.f.put(entityplayer1.getUniqueID(), entityplayer1);
++ }
++ // Added from changeDimension
++ updateClient(entityplayer); // Update health, etc...
++ entityplayer.updateAbilities();
++ for (Object o1 : entityplayer.getEffects()) {
++ MobEffect mobEffect = (MobEffect) o1;
++ entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEffect(entityplayer.getId(), mobEffect));
++ }
++ // entityplayer1.syncInventory();
++ // CraftBukkit end
+ entityplayer1.setHealth(entityplayer1.getHealth());
++
++ // CraftBukkit start
++ // Don't fire on respawn
++ if (fromWorld != location.getWorld()) {
++ PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(entityplayer.getBukkitEntity(), fromWorld);
++ server.server.getPluginManager().callEvent(event);
++ }
++
++ // Save player file again if they were disconnected
++ if (entityplayer.playerConnection.isDisconnected()) {
++ this.savePlayerFile(entityplayer);
++ }
++ // CraftBukkit end
++
+ return entityplayer1;
+ }
+
+- public void changeDimension(EntityPlayer entityplayer, int i) {
+- int j = entityplayer.dimension;
+- WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension);
++ // CraftBukkit start - Replaced the standard handling of portals with a more customised method.
++ public void changeDimension(EntityPlayer entityplayer, int i, TeleportCause cause) {
++ WorldServer exitWorld = null;
++ if (entityplayer.dimension < CraftWorld.CUSTOM_DIMENSION_OFFSET) { // plugins must specify exit from custom Bukkit worlds
++ // only target existing worlds (compensate for allow-nether/allow-end as false)
++ for (WorldServer world : this.server.worlds) {
++ if (world.dimension == i) {
++ exitWorld = world;
++ }
++ }
++ }
+
+- entityplayer.dimension = i;
+- WorldServer worldserver1 = this.server.getWorldServer(entityplayer.dimension);
++ Location enter = entityplayer.getBukkitEntity().getLocation();
++ Location exit = null;
++ boolean useTravelAgent = false; // don't use agent for custom worlds or return from THE_END
++ if (exitWorld != null) {
++ if ((cause == TeleportCause.END_PORTAL) && (i == 0)) {
++ // THE_END -> NORMAL; use bed if available, otherwise default spawn
++ exit = ((org.bukkit.craftbukkit.entity.CraftPlayer) entityplayer.getBukkitEntity()).getBedSpawnLocation();
++ if (exit == null || ((CraftWorld) exit.getWorld()).getHandle().dimension != 0) {
++ exit = exitWorld.getWorld().getSpawnLocation();
++ }
++ } else {
++ // NORMAL <-> NETHER or NORMAL -> THE_END
++ exit = this.calculateTarget(enter, exitWorld);
++ useTravelAgent = true;
++ }
++ }
+
+- entityplayer.playerConnection.sendPacket(new PacketPlayOutRespawn(entityplayer.dimension, entityplayer.world.getDifficulty(), entityplayer.world.getWorldData().getType(), entityplayer.playerInteractManager.getGameMode()));
+- worldserver.removeEntity(entityplayer);
+- entityplayer.dead = false;
+- this.changeWorld(entityplayer, j, worldserver, worldserver1);
+- this.a(entityplayer, worldserver);
+- entityplayer.playerConnection.a(entityplayer.locX, entityplayer.locY, entityplayer.locZ, entityplayer.yaw, entityplayer.pitch);
+- entityplayer.playerInteractManager.a(worldserver1);
+- this.b(entityplayer, worldserver1);
+- this.updateClient(entityplayer);
+- Iterator iterator = entityplayer.getEffects().iterator();
++ TravelAgent agent = exit != null ? (TravelAgent) ((CraftWorld) exit.getWorld()).getHandle().getTravelAgent() : org.bukkit.craftbukkit.CraftTravelAgent.DEFAULT; // return arbitrary TA to compensate for implementation dependent plugins
++ PlayerPortalEvent event = new PlayerPortalEvent(entityplayer.getBukkitEntity(), enter, exit, agent, cause);
++ event.useTravelAgent(useTravelAgent);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled() || event.getTo() == null) {
++ return;
++ }
++
++ exit = event.useTravelAgent() ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo();
++ if (exit == null) {
++ return;
++ }
++ exitWorld = ((CraftWorld) exit.getWorld()).getHandle();
++
++ Vector velocity = entityplayer.getBukkitEntity().getVelocity();
++ boolean before = exitWorld.chunkProviderServer.forceChunkLoad;
++ exitWorld.chunkProviderServer.forceChunkLoad = true;
++ exitWorld.getTravelAgent().adjustExit(entityplayer, exit, velocity);
++ exitWorld.chunkProviderServer.forceChunkLoad = before;
++
++ this.moveToWorld(entityplayer, exitWorld.dimension, true, exit, false); // Vanilla doesn't check for suffocation when handling portals, so neither should we
++ if (entityplayer.motX != velocity.getX() || entityplayer.motY != velocity.getY() || entityplayer.motZ != velocity.getZ()) {
++ entityplayer.getBukkitEntity().setVelocity(velocity);
++ }
++ }
+
+- while (iterator.hasNext()) {
+- MobEffect mobeffect = (MobEffect) iterator.next();
++ public void changeWorld(Entity entity, int i, WorldServer worldserver, WorldServer worldserver1) {
++ // CraftBukkit start - Split into modular functions
++ Location exit = calculateTarget(entity.getBukkitEntity().getLocation(), worldserver1);
++ repositionEntity(entity, exit, true);
++ }
++
++ // Copy of original changeWorld(Entity, int, WorldServer, WorldServer) method with only location calculation logic
++ public Location calculateTarget(Location enter, World target) {
++ WorldServer worldserver = ((CraftWorld) enter.getWorld()).getHandle();
++ WorldServer worldserver1 = ((CraftWorld) target.getWorld()).getHandle();
++ int i = worldserver.dimension;
++
++ double y = enter.getY();
++ float yaw = enter.getYaw();
++ float pitch = enter.getPitch();
++ double d0 = enter.getX();
++ double d1 = enter.getZ();
++ double d2 = 8.0D;
++ /*
++ double d0 = entity.locX;
++ double d1 = entity.locZ;
++ double d2 = 8.0D;
++ float f = entity.yaw;
++
++ worldserver.methodProfiler.a("moving");
++ */
++ if (worldserver1.dimension == -1) {
++ d0 = MathHelper.a(d0 / d2, worldserver1.af().b() + 16.0D, worldserver1.af().d() - 16.0D);
++ d1 = MathHelper.a(d1 / d2, worldserver1.af().c() + 16.0D, worldserver1.af().e() - 16.0D);
++ /*
++ entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch);
++ if (entity.isAlive()) {
++ worldserver.entityJoinedWorld(entity, false);
++ }
++ */
++ } else if (worldserver1.dimension == 0) {
++ d0 = MathHelper.a(d0 * d2, worldserver1.af().b() + 16.0D, worldserver1.af().d() - 16.0D);
++ d1 = MathHelper.a(d1 * d2, worldserver1.af().c() + 16.0D, worldserver1.af().e() - 16.0D);
++ /*
++ entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch);
++ if (entity.isAlive()) {
++ worldserver.entityJoinedWorld(entity, false);
++ }
++ */
++ } else {
++ BlockPosition blockposition;
++
++ if (i == 1) {
++ // use default NORMAL world spawn instead of target
++ worldserver1 = this.server.worlds.get(0);
++ blockposition = worldserver1.getSpawn();
++ } else {
++ blockposition = worldserver1.getDimensionSpawn();
++ }
++
++ d0 = (double) blockposition.getX();
++ y = (double) blockposition.getY();
++ d1 = (double) blockposition.getZ();
++ /*
++ entity.setPositionRotation(d0, entity.locY, d1, 90.0F, 0.0F);
++ if (entity.isAlive()) {
++ worldserver.entityJoinedWorld(entity, false);
++ }
++ */
++ }
+
+- entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEffect(entityplayer.getId(), mobeffect));
++ // worldserver.methodProfiler.b();
++ if (i != 1) {
++ worldserver.methodProfiler.a("placing");
++ d0 = (double) MathHelper.clamp((int) d0, -29999872, 29999872);
++ d1 = (double) MathHelper.clamp((int) d1, -29999872, 29999872);
++ /*
++ if (entity.isAlive()) {
++ entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch);
++ worldserver1.getTravelAgent().a(entity, f);
++ worldserver1.addEntity(entity);
++ worldserver1.entityJoinedWorld(entity, false);
++ }
++
++ worldserver.methodProfiler.b();
++ */
+ }
+
++ // entity.spawnIn(worldserver1);
++ return new Location(worldserver1.getWorld(), d0, y, d1, yaw, pitch);
+ }
+
+- public void changeWorld(Entity entity, int i, WorldServer worldserver, WorldServer worldserver1) {
++ // copy of original a(Entity, int, WorldServer, WorldServer) method with only entity repositioning logic
++ public void repositionEntity(Entity entity, Location exit, boolean portal) {
++ WorldServer worldserver = (WorldServer) entity.world;
++ WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle();
++ int i = worldserver.dimension;
++
++ /*
+ double d0 = entity.locX;
+ double d1 = entity.locZ;
+ double d2 = 8.0D;
+ float f = entity.yaw;
+
+ worldserver.methodProfiler.a("moving");
++ */
++ entity.setPositionRotation(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch());
++ if (entity.isAlive()) {
++ worldserver.entityJoinedWorld(entity, false);
++ }
++ /*
+ if (entity.dimension == -1) {
+ d0 = MathHelper.a(d0 / d2, worldserver1.af().b() + 16.0D, worldserver1.af().d() - 16.0D);
+ d1 = MathHelper.a(d1 / d2, worldserver1.af().c() + 16.0D, worldserver1.af().e() - 16.0D);
+@@ -411,6 +724,8 @@
+ BlockPosition blockposition;
+
+ if (i == 1) {
++ // use default NORMAL world spawn instead of target
++ worldserver1 = this.server.worlds.get(0);
+ blockposition = worldserver1.getSpawn();
+ } else {
+ blockposition = worldserver1.getDimensionSpawn();
+@@ -424,15 +739,26 @@
+ worldserver.entityJoinedWorld(entity, false);
+ }
+ }
++ */
+
+ worldserver.methodProfiler.b();
+ if (i != 1) {
+ worldserver.methodProfiler.a("placing");
++ /*
+ d0 = (double) MathHelper.clamp((int) d0, -29999872, 29999872);
+ d1 = (double) MathHelper.clamp((int) d1, -29999872, 29999872);
++ */
+ if (entity.isAlive()) {
+- entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch);
+- worldserver1.getTravelAgent().a(entity, f);
++ // entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch);
++ // worldserver1.getTravelAgent().a(entity, f);
++ if (portal) {
++ Vector velocity = entity.getBukkitEntity().getVelocity();
++ worldserver1.getTravelAgent().adjustExit(entity, exit, velocity);
++ entity.setPositionRotation(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch());
++ if (entity.motX != velocity.getX() || entity.motY != velocity.getY() || entity.motZ != velocity.getZ()) {
++ entity.getBukkitEntity().setVelocity(velocity);
++ }
++ }
+ worldserver1.addEntity(entity);
+ worldserver1.entityJoinedWorld(entity, false);
+ }
+@@ -441,6 +767,7 @@
+ }
+
+ entity.spawnIn(worldserver1);
++ // CraftBukkit end
+ }
+
+ public void tick() {
+@@ -549,10 +876,24 @@
+
+ public void addOp(GameProfile gameprofile) {
+ this.operators.add(new OpListEntry(gameprofile, this.server.p()));
++
++ // CraftBukkit start
++ Player player = server.server.getPlayer(gameprofile.getId());
++ if (player != null) {
++ player.recalculatePermissions();
++ }
++ // CraftBukkit end
+ }
+
+ public void removeOp(GameProfile gameprofile) {
+ this.operators.remove(gameprofile);
++
++ // CraftBukkit start
++ Player player = server.server.getPlayer(gameprofile.getId());
++ if (player != null) {
++ player.recalculatePermissions();
++ }
++ // CraftBukkit end
+ }
+
+ public boolean isWhitelisted(GameProfile gameprofile) {
+@@ -560,7 +901,7 @@
+ }
+
+ public boolean isOp(GameProfile gameprofile) {
+- return this.operators.d(gameprofile) || this.server.S() && this.server.worldServer[0].getWorldData().v() && this.server.R().equalsIgnoreCase(gameprofile.getName()) || this.t;
++ return this.operators.d(gameprofile) || this.server.S() && this.server.worlds.get(0).getWorldData().v() && this.server.R().equalsIgnoreCase(gameprofile.getName()) || this.t; // CraftBukkit
+ }
+
+ public EntityPlayer getPlayer(String s) {
+@@ -587,6 +928,12 @@
+ for (int j = 0; j < this.players.size(); ++j) {
+ EntityPlayer entityplayer = (EntityPlayer) this.players.get(j);
+
++ // CraftBukkit start - Test if player receiving packet can see the source of the packet
++ if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) {
++ continue;
++ }
++ // CraftBukkit end
++
+ if (entityplayer != entityhuman && entityplayer.dimension == i) {
+ double d4 = d0 - entityplayer.locX;
+ double d5 = d1 - entityplayer.locY;
+@@ -634,21 +981,25 @@
+ public void reloadWhitelist() {}
+
+ public void b(EntityPlayer entityplayer, WorldServer worldserver) {
+- WorldBorder worldborder = this.server.worldServer[0].af();
++ WorldBorder worldborder = this.server.worlds.get(0).af(); // CraftBukkit
+
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutWorldBorder(worldborder, EnumWorldBorderAction.INITIALIZE));
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutUpdateTime(worldserver.getTime(), worldserver.getDayTime(), worldserver.getGameRules().getBoolean("doDaylightCycle")));
+ if (worldserver.S()) {
+- entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(1, 0.0F));
+- entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(7, worldserver.j(1.0F)));
+- entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(8, worldserver.h(1.0F)));
++ // CraftBukkit start - handle player weather
++ // entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(1, 0.0F));
++ // entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(7, worldserver.j(1.0F)));
++ // entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(8, worldserver.h(1.0F)));
++ entityplayer.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false);
++ // CraftBukkit end
+ }
+
+ }
+
+ public void updateClient(EntityPlayer entityplayer) {
+ entityplayer.updateInventory(entityplayer.defaultContainer);
+- entityplayer.triggerHealthUpdate();
++ // entityplayer.triggerHealthUpdate();
++ entityplayer.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange
+ entityplayer.playerConnection.sendPacket(new PacketPlayOutHeldItemSlot(entityplayer.inventory.itemInHandIndex));
+ }
+
+@@ -661,7 +1012,7 @@
+ }
+
+ public String[] getSeenPlayers() {
+- return this.server.worldServer[0].getDataManager().getPlayerFileData().getSeenPlayers();
++ return this.server.worlds.get(0).getDataManager().getPlayerFileData().getSeenPlayers(); // CraftBukkit
+ }
+
+ public boolean getHasWhitelist() {
+@@ -711,10 +1062,17 @@
+
+ public void v() {
+ for (int i = 0; i < this.players.size(); ++i) {
+- ((EntityPlayer) this.players.get(i)).playerConnection.disconnect("Server closed");
++ ((EntityPlayer) this.players.get(i)).playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message
+ }
++ }
+
++ // CraftBukkit start
++ public void sendMessage(IChatBaseComponent[] iChatBaseComponents) {
++ for (IChatBaseComponent component : iChatBaseComponents) {
++ sendMessage(component, true);
++ }
+ }
++ // CraftBukkit end
+
+ public void sendMessage(IChatBaseComponent ichatbasecomponent, boolean flag) {
+ this.server.sendMessage(ichatbasecomponent);
+@@ -754,11 +1112,10 @@
+ public void a(int i) {
+ this.r = i;
+ if (this.server.worldServer != null) {
+- WorldServer[] aworldserver = this.server.worldServer;
+- int j = aworldserver.length;
+-
+- for (int k = 0; k < j; ++k) {
+- WorldServer worldserver = aworldserver[k];
++ // CraftBukkit start
++ for (int k = 0; k < server.worlds.size(); ++k) {
++ WorldServer worldserver = server.worlds.get(0);
++ // CraftBukkit end
+
+ if (worldserver != null) {
+ worldserver.getPlayerChunkMap().a(i);
diff --git a/nms-patches/PlayerSelector.patch b/nms-patches/PlayerSelector.patch
new file mode 100644
index 00000000..e5583a10
--- /dev/null
+++ b/nms-patches/PlayerSelector.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PlayerSelector.java 2014-11-27 08:59:46.865421124 +1100
++++ src/main/java/net/minecraft/server/PlayerSelector.java 2014-11-27 08:42:10.140850934 +1100
+@@ -52,6 +52,11 @@
+ }
+
+ public static List getPlayers(ICommandListener icommandlistener, String s, Class oclass) {
++ // CraftBukkit start - disable playerselections for ICommandListeners other than command blocks
++ if (!(icommandlistener instanceof CommandBlockListenerAbstract)) {
++ return com.google.common.collect.ImmutableList.of();
++ }
++ // CraftBukkit end
+ Matcher matcher = PlayerSelector.a.matcher(s);
+
+ if (matcher.matches() && icommandlistener.a(1, "@")) {
diff --git a/nms-patches/PortalCreator.patch b/nms-patches/PortalCreator.patch
new file mode 100644
index 00000000..5bb4eae9
--- /dev/null
+++ b/nms-patches/PortalCreator.patch
@@ -0,0 +1,102 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PortalCreator.java 2014-11-27 08:59:46.865421124 +1100
++++ src/main/java/net/minecraft/server/PortalCreator.java 2014-11-27 08:42:10.132850949 +1100
+@@ -1,5 +1,7 @@
+ package net.minecraft.server;
+
++import org.bukkit.event.world.PortalCreateEvent; // CraftBukkit
++
+ public class PortalCreator {
+
+ private final World a;
+@@ -10,6 +12,7 @@
+ private BlockPosition f;
+ private int g;
+ private int h;
++ java.util.Collection<org.bukkit.block.Block> blocks = new java.util.HashSet<org.bukkit.block.Block>(); // CraftBukkit - add field
+
+ public PortalCreator(World world, BlockPosition blockposition, EnumAxis enumaxis) {
+ this.a = world;
+@@ -60,6 +63,10 @@
+ }
+
+ protected int a() {
++ // CraftBukkit start
++ this.blocks.clear();
++ org.bukkit.World bworld = this.a.getWorld();
++ // CraftBukkit end
+ int i;
+
+ label56:
+@@ -80,11 +87,21 @@
+ block = this.a.getType(blockposition.shift(this.d)).getBlock();
+ if (block != Blocks.OBSIDIAN) {
+ break label56;
++ // CraftBukkit start - add the block to our list
++ } else {
++ BlockPosition pos = blockposition.shift(this.d);
++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ // CraftBukkit end
+ }
+ } else if (i == this.h - 1) {
+ block = this.a.getType(blockposition.shift(this.c)).getBlock();
+ if (block != Blocks.OBSIDIAN) {
+ break label56;
++ // CraftBukkit start - add the block to our list
++ } else {
++ BlockPosition pos = blockposition.shift(this.c);
++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -94,6 +111,11 @@
+ if (this.a.getType(this.f.shift(this.c, i).up(this.g)).getBlock() != Blocks.OBSIDIAN) {
+ this.g = 0;
+ break;
++ // CraftBukkit start - add the block to our list
++ } else {
++ BlockPosition pos = this.f.shift(this.c, i).up(this.g);
++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ // CraftBukkit end
+ }
+ }
+
+@@ -115,15 +137,36 @@
+ return this.f != null && this.h >= 2 && this.h <= 21 && this.g >= 3 && this.g <= 21;
+ }
+
+- public void c() {
++ // CraftBukkit start - return boolean
++ public boolean c() {
++ org.bukkit.World bworld = this.a.getWorld();
++
++ // Copy below for loop
+ for (int i = 0; i < this.h; ++i) {
+ BlockPosition blockposition = this.f.shift(this.c, i);
+
+ for (int j = 0; j < this.g; ++j) {
+- this.a.setTypeAndData(blockposition.up(j), Blocks.PORTAL.getBlockData().set(BlockPortal.AXIS, this.b), 2);
++ BlockPosition pos = blockposition.up(j);
++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
+ }
+ }
+
++ PortalCreateEvent event = new PortalCreateEvent(blocks, bworld, PortalCreateEvent.CreateReason.FIRE);
++ this.a.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ for (int i = 0; i < this.h; ++i) {
++ BlockPosition blockposition = this.f.shift(this.c, i);
++
++ for (int j = 0; j < this.g; ++j) {
++ this.a.setTypeAndData(blockposition.up(j), Blocks.PORTAL.getBlockData().set(BlockPortal.AXIS, this.b), 2);
++ }
++ }
++
++ return true; // Craft Bukkit
+ }
+
+ public static int a(PortalCreator portalcreator) {
diff --git a/nms-patches/PortalTravelAgent.patch b/nms-patches/PortalTravelAgent.patch
new file mode 100644
index 00000000..1a6d02d9
--- /dev/null
+++ b/nms-patches/PortalTravelAgent.patch
@@ -0,0 +1,273 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PortalTravelAgent.java 2014-11-27 08:59:46.869421107 +1100
++++ src/main/java/net/minecraft/server/PortalTravelAgent.java 2014-11-27 08:42:10.096851020 +1100
+@@ -5,6 +5,12 @@
+ import java.util.List;
+ import java.util.Random;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.event.entity.EntityPortalExitEvent;
++import org.bukkit.util.Vector;
++// CraftBukkit end
++
+ public class PortalTravelAgent {
+
+ private final WorldServer a;
+@@ -27,8 +33,21 @@
+ int i = MathHelper.floor(entity.locX);
+ int j = MathHelper.floor(entity.locY) - 1;
+ int k = MathHelper.floor(entity.locZ);
++ // CraftBukkit start - Modularize end portal creation
++ BlockPosition created = this.createEndPortal(entity.locX, entity.locY, entity.locZ);
++ entity.setPositionRotation((double) created.getX(), (double) created.getY(), (double) created.getZ(), entity.yaw, 0.0F);
++ entity.motX = entity.motY = entity.motZ = 0.0D;
++ }
++ }
++
++ // Split out from original a(Entity, double, double, double, float) method in order to enable being called from createPortal
++ private BlockPosition createEndPortal(double x, double y, double z) {
++ int i = MathHelper.floor(x);
++ int j = MathHelper.floor(y) - 1;
++ int k = MathHelper.floor(z);
+ byte b0 = 1;
+ byte b1 = 0;
++ // CraftBukkit end
+
+ for (int l = -2; l <= 2; ++l) {
+ for (int i1 = -2; i1 <= 2; ++i1) {
+@@ -43,16 +62,63 @@
+ }
+ }
+
+- entity.setPositionRotation((double) i, (double) j, (double) k, entity.yaw, 0.0F);
+- entity.motX = entity.motY = entity.motZ = 0.0D;
++ // CraftBukkit start
++ return new BlockPosition(i, k, k);
++ }
++
++ // use logic based on creation to verify end portal
++ private BlockPosition findEndPortal(BlockPosition portal) {
++ int i = portal.getX();
++ int j = portal.getY() - 1;
++ int k = portal.getZ();
++ byte b0 = 1;
++ byte b1 = 0;
++
++ for (int l = -2; l <= 2; ++l) {
++ for (int i1 = -2; i1 <= 2; ++i1) {
++ for (int j1 = -1; j1 < 3; ++j1) {
++ int k1 = i + i1 * b0 + l * b1;
++ int l1 = j + j1;
++ int i2 = k + i1 * b1 - l * b0;
++ boolean flag = j1 < 0;
++
++ if (this.a.getType(new BlockPosition(k1, l1, i2)).getBlock() != (flag ? Blocks.OBSIDIAN : Blocks.AIR)) {
++ return null;
++ }
++ }
++ }
+ }
++ return new BlockPosition(i, j, k);
+ }
++ // CraftBukkit end
+
+ public boolean b(Entity entity, float f) {
+- boolean flag = true;
++ // CraftBukkit start - Modularize portal search process and entity teleportation
++ BlockPosition found = this.findPortal(entity.locX, entity.locY, entity.locZ, 128);
++ if (found == null) {
++ return false;
++ }
++
++ Location exit = new Location(this.a.getWorld(), found.getX(), found.getY(), found.getZ(), f, entity.pitch);
++ Vector velocity = entity.getBukkitEntity().getVelocity();
++ this.adjustExit(entity, exit, velocity);
++ entity.setPositionRotation(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch());
++ if (entity.motX != velocity.getX() || entity.motY != velocity.getY() || entity.motZ != velocity.getZ()) {
++ entity.getBukkitEntity().setVelocity(velocity);
++ }
++ return true;
++ }
++
++ public BlockPosition findPortal(double x, double y, double z, int short1) {
++ if (this.a.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END) {
++ return this.findEndPortal(this.a.worldProvider.h());
++ }
++ // CraftBukkit end
+ double d0 = -1.0D;
+- int i = MathHelper.floor(entity.locX);
+- int j = MathHelper.floor(entity.locZ);
++ // CraftBukkit start
++ int i = MathHelper.floor(x);
++ int j = MathHelper.floor(z);
++ // CraftBukkit end
+ boolean flag1 = true;
+ Object object = BlockPosition.ZERO;
+ long k = ChunkCoordIntPair.a(i, j);
+@@ -65,7 +131,7 @@
+ chunkcoordinatesportal.b = this.a.getTime();
+ flag1 = false;
+ } else {
+- BlockPosition blockposition = new BlockPosition(entity);
++ BlockPosition blockposition = new BlockPosition(x, y, z);
+
+ for (int l = -128; l <= 128; ++l) {
+ BlockPosition blockposition1;
+@@ -95,7 +161,29 @@
+ this.c.put(k, new ChunkCoordinatesPortal(this, (BlockPosition) object, this.a.getTime()));
+ this.d.add(Long.valueOf(k));
+ }
++ // CraftBukkit start - Move entity teleportation logic into exit
++ return (BlockPosition) object;
++ } else {
++ return null;
++ }
++ }
+
++ // Entity repositioning logic split out from original b method and combined with repositioning logic for The End from original a method
++ public void adjustExit(Entity entity, Location position, Vector velocity) {
++ Location from = position.clone();
++ Vector before = velocity.clone();
++ BlockPosition object = new BlockPosition(position.getBlockX(), position.getBlockY(), position.getBlockZ());
++ float f = position.getYaw();
++
++ if (this.a.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END) {
++ // entity.setPositionRotation((double) i, (double) j, (double) k, entity.yaw, 0.0F);
++ // entity.motX = entity.motY = entity.motZ = 0.0D;
++ position.setPitch(0.0F);
++ velocity.setX(0);
++ velocity.setY(0);
++ velocity.setZ(0);
++ } else {
++ // CraftBukkit end
+ double d2 = (double) ((BlockPosition) object).getX() + 0.5D;
+ double d3 = (double) ((BlockPosition) object).getY() + 0.5D;
+ double d4 = (double) ((BlockPosition) object).getZ() + 0.5D;
+@@ -170,21 +258,46 @@
+ f6 = 1.0F;
+ }
+
+- double d5 = entity.motX;
+- double d6 = entity.motZ;
+-
+- entity.motX = d5 * (double) f3 + d6 * (double) f6;
+- entity.motZ = d5 * (double) f5 + d6 * (double) f4;
+- entity.yaw = f - (float) (enumdirection1.b() * 90) + (float) (enumdirection.b() * 90);
++ // CraftBukkit start
++ double d5 = velocity.getX();
++ double d6 = velocity.getZ();
++ // CraftBukkit end
++
++ // CraftBukkit start - Adjust position and velocity instances instead of entity
++ velocity.setX(d5 * (double) f3 + d6 * (double) f6);
++ velocity.setZ(d5 * (double) f5 + d6 * (double) f4);
++ f = f - (float) (enumdirection1.b() * 90) + (float) (enumdirection.b() * 90);
+ } else {
+- entity.motX = entity.motY = entity.motZ = 0.0D;
++ velocity.setX(0);
++ velocity.setY(0);
++ velocity.setZ(0);
+ }
+
+- entity.setPositionRotation(d2, d3, d4, entity.yaw, entity.pitch);
+- return true;
++ // entity.setPositionRotation(d2, d3, d4, entity.yaw, entity.pitch);
++ position.setX(d2);
++ position.setY(d3);
++ position.setZ(d4);
++ position.setYaw(f);
++ }
++ EntityPortalExitEvent event = new EntityPortalExitEvent(entity.getBukkitEntity(), from, position, before, velocity);
++ this.a.getServer().getPluginManager().callEvent(event);
++ Location to = event.getTo();
++ if (event.isCancelled() || to == null || !entity.isAlive()) {
++ position.setX(from.getX());
++ position.setY(from.getY());
++ position.setZ(from.getZ());
++ position.setYaw(from.getYaw());
++ position.setPitch(from.getPitch());
++ velocity.copy(before);
+ } else {
+- return false;
++ position.setX(to.getX());
++ position.setY(to.getY());
++ position.setZ(to.getZ());
++ position.setYaw(to.getYaw());
++ position.setPitch(to.getPitch());
++ velocity.copy(event.getAfter()); // event.getAfter() will never be null, as setAfter() will cause an NPE if null is passed in
+ }
++ // CraftBukkit end
+ }
+
+ private boolean a(BlockPosition blockposition) {
+@@ -192,11 +305,22 @@
+ }
+
+ public boolean a(Entity entity) {
+- byte b0 = 16;
++ // CraftBukkit start - Allow for portal creation to be based on coordinates instead of entity
++ return this.createPortal(entity.locX, entity.locY, entity.locZ, 16);
++ }
++
++ public boolean createPortal(double x, double y, double z, int b0) {
++ if (this.a.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END) {
++ createEndPortal(x, y, z);
++ return true;
++ }
++ // CraftBukkit end
+ double d0 = -1.0D;
+- int i = MathHelper.floor(entity.locX);
+- int j = MathHelper.floor(entity.locY);
+- int k = MathHelper.floor(entity.locZ);
++ // CraftBukkit start
++ int i = MathHelper.floor(x);
++ int j = MathHelper.floor(y);
++ int k = MathHelper.floor(z);
++ // CraftBukkit end
+ int l = i;
+ int i1 = j;
+ int j1 = k;
+@@ -220,10 +344,10 @@
+ double d4;
+
+ for (i2 = i - b0; i2 <= i + b0; ++i2) {
+- d1 = (double) i2 + 0.5D - entity.locX;
++ d1 = (double) i2 + 0.5D - x; // CraftBukkit
+
+ for (j2 = k - b0; j2 <= k + b0; ++j2) {
+- d2 = (double) j2 + 0.5D - entity.locZ;
++ d2 = (double) j2 + 0.5D - z; // CraftBukkit
+
+ label271:
+ for (k2 = this.a.V() - 1; k2 >= 0; --k2) {
+@@ -254,7 +378,7 @@
+ }
+ }
+
+- d3 = (double) k2 + 0.5D - entity.locY;
++ d3 = (double) k2 + 0.5D - y; // CraftBukkit
+ d4 = d1 * d1 + d3 * d3 + d2 * d2;
+ if (d0 < 0.0D || d4 < d0) {
+ d0 = d4;
+@@ -271,10 +395,10 @@
+
+ if (d0 < 0.0D) {
+ for (i2 = i - b0; i2 <= i + b0; ++i2) {
+- d1 = (double) i2 + 0.5D - entity.locX;
++ d1 = (double) i2 + 0.5D - x; // CraftBukkit
+
+ for (j2 = k - b0; j2 <= k + b0; ++j2) {
+- d2 = (double) j2 + 0.5D - entity.locZ;
++ d2 = (double) j2 + 0.5D - z; // CraftBukkit
+
+ label219:
+ for (k2 = this.a.V() - 1; k2 >= 0; --k2) {
+@@ -298,7 +422,7 @@
+ }
+ }
+
+- d3 = (double) k2 + 0.5D - entity.locY;
++ d3 = (double) k2 + 0.5D - y; // CraftBukkit
+ d4 = d1 * d1 + d3 * d3 + d2 * d2;
+ if (d0 < 0.0D || d4 < d0) {
+ d0 = d4;
diff --git a/nms-patches/PropertyManager.patch b/nms-patches/PropertyManager.patch
new file mode 100644
index 00000000..9812b439
--- /dev/null
+++ b/nms-patches/PropertyManager.patch
@@ -0,0 +1,94 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/PropertyManager.java 2014-11-27 08:59:46.869421107 +1100
++++ src/main/java/net/minecraft/server/PropertyManager.java 2014-11-27 08:42:10.100851012 +1100
+@@ -8,6 +8,8 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import joptsimple.OptionSet; // CraftBukkit
++
+ public class PropertyManager {
+
+ private static final Logger a = LogManager.getLogger();
+@@ -39,8 +41,25 @@
+ PropertyManager.a.warn(file + " does not exist");
+ this.a();
+ }
++ }
++
++ // CraftBukkit start
++ private OptionSet options = null;
++
++ public PropertyManager(final OptionSet options) {
++ this((File) options.valueOf("config"));
++
++ this.options = options;
++ }
++
++ private <T> T getOverride(String name, T value) {
++ if ((this.options != null) && (this.options.has(name))) {
++ return (T) this.options.valueOf(name);
++ }
+
++ return value;
+ }
++ // CraftBukkit end
+
+ public void a() {
+ PropertyManager.a.info("Generating new properties file");
+@@ -51,6 +70,12 @@
+ FileOutputStream fileoutputstream = null;
+
+ try {
++ // CraftBukkit start - Don't attempt writing to file if it's read only
++ if (this.file.exists() && !this.file.canWrite()) {
++ return;
++ }
++ // CraftBukkit end
++
+ fileoutputstream = new FileOutputStream(this.file);
+ this.properties.store(fileoutputstream, "Minecraft server properties");
+ } catch (Exception exception) {
+@@ -80,36 +105,36 @@
+ this.savePropertiesFile();
+ }
+
+- return this.properties.getProperty(s, s1);
++ return getOverride(s, this.properties.getProperty(s, s1)); // CraftBukkit
+ }
+
+ public int getInt(String s, int i) {
+ try {
+- return Integer.parseInt(this.getString(s, "" + i));
++ return getOverride(s, Integer.parseInt(this.getString(s, "" + i))); // CraftBukkit
+ } catch (Exception exception) {
+ this.properties.setProperty(s, "" + i);
+ this.savePropertiesFile();
+- return i;
++ return getOverride(s, i); // CraftBukkit
+ }
+ }
+
+ public long getLong(String s, long i) {
+ try {
+- return Long.parseLong(this.getString(s, "" + i));
++ return getOverride(s, Long.parseLong(this.getString(s, "" + i))); // CraftBukkit
+ } catch (Exception exception) {
+ this.properties.setProperty(s, "" + i);
+ this.savePropertiesFile();
+- return i;
++ return getOverride(s ,i); // CraftBukkit
+ }
+ }
+
+ public boolean getBoolean(String s, boolean flag) {
+ try {
+- return Boolean.parseBoolean(this.getString(s, "" + flag));
++ return getOverride(s, Boolean.parseBoolean(this.getString(s, "" + flag))); // CraftBukkit
+ } catch (Exception exception) {
+ this.properties.setProperty(s, "" + flag);
+ this.savePropertiesFile();
+- return flag;
++ return getOverride(s, flag); // CraftBukkit
+ }
+ }
+
diff --git a/nms-patches/RecipeArmorDye.patch b/nms-patches/RecipeArmorDye.patch
new file mode 100644
index 00000000..80002455
--- /dev/null
+++ b/nms-patches/RecipeArmorDye.patch
@@ -0,0 +1,18 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipeArmorDye.java 2014-11-27 08:59:46.869421107 +1100
++++ src/main/java/net/minecraft/server/RecipeArmorDye.java 2014-11-27 08:42:10.116850981 +1100
+@@ -3,9 +3,13 @@
+ import com.google.common.collect.Lists;
+ import java.util.ArrayList;
+
+-public class RecipeArmorDye implements IRecipe {
++public class RecipeArmorDye extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- public RecipeArmorDye() {}
++ // CraftBukkit start - Delegate to new parent class with bogus info
++ public RecipeArmorDye() {
++ super(new ItemStack(Items.LEATHER_HELMET, 0, 0), java.util.Arrays.asList(new ItemStack(Items.DYE, 0, 5)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ ItemStack itemstack = null;
diff --git a/nms-patches/RecipeBookClone.patch b/nms-patches/RecipeBookClone.patch
new file mode 100644
index 00000000..5d14c2db
--- /dev/null
+++ b/nms-patches/RecipeBookClone.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipeBookClone.java 2014-11-27 08:59:46.873421089 +1100
++++ src/main/java/net/minecraft/server/RecipeBookClone.java 2014-11-27 08:42:10.144850927 +1100
+@@ -1,8 +1,12 @@
+ package net.minecraft.server;
+
+-public class RecipeBookClone implements IRecipe {
++public class RecipeBookClone extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- public RecipeBookClone() {}
++ // CraftBukkit start - Delegate to new parent class
++ public RecipeBookClone() {
++ super(new ItemStack(Items.WRITTEN_BOOK, 0, -1), java.util.Arrays.asList(new ItemStack(Items.WRITABLE_BOOK, 0, 0)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ int i = 0;
diff --git a/nms-patches/RecipeFireworks.patch b/nms-patches/RecipeFireworks.patch
new file mode 100644
index 00000000..b7b388d0
--- /dev/null
+++ b/nms-patches/RecipeFireworks.patch
@@ -0,0 +1,21 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipeFireworks.java 2014-11-27 08:59:46.873421089 +1100
++++ src/main/java/net/minecraft/server/RecipeFireworks.java 2014-11-27 08:42:10.088851036 +1100
+@@ -3,11 +3,15 @@
+ import com.google.common.collect.Lists;
+ import java.util.ArrayList;
+
+-public class RecipeFireworks implements IRecipe {
++public class RecipeFireworks extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+ private ItemStack a;
+-
+- public RecipeFireworks() {}
++
++ // CraftBukkit start - Delegate to new parent class with bogus info
++ public RecipeFireworks() {
++ super(new ItemStack(Items.FIREWORKS, 0, 0), java.util.Arrays.asList(new ItemStack(Items.GUNPOWDER, 0, 5)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ this.a = null;
diff --git a/nms-patches/RecipeMapClone.patch b/nms-patches/RecipeMapClone.patch
new file mode 100644
index 00000000..a33ee9e9
--- /dev/null
+++ b/nms-patches/RecipeMapClone.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipeMapClone.java 2014-11-27 08:59:46.873421089 +1100
++++ src/main/java/net/minecraft/server/RecipeMapClone.java 2014-11-27 08:42:10.152850911 +1100
+@@ -1,8 +1,12 @@
+ package net.minecraft.server;
+
+-public class RecipeMapClone implements IRecipe {
++public class RecipeMapClone extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- public RecipeMapClone() {}
++ // CraftBukkit start - Delegate to new parent class
++ public RecipeMapClone() {
++ super(new ItemStack(Items.MAP, 0, -1), java.util.Arrays.asList(new ItemStack(Items.MAP, 0, 0)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ int i = 0;
diff --git a/nms-patches/RecipeRepair.patch b/nms-patches/RecipeRepair.patch
new file mode 100644
index 00000000..51349bb2
--- /dev/null
+++ b/nms-patches/RecipeRepair.patch
@@ -0,0 +1,39 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipeRepair.java 2014-11-27 08:59:46.877421071 +1100
++++ src/main/java/net/minecraft/server/RecipeRepair.java 2014-11-27 08:42:10.148850918 +1100
+@@ -3,9 +3,13 @@
+ import com.google.common.collect.Lists;
+ import java.util.ArrayList;
+
+-public class RecipeRepair implements IRecipe {
++public class RecipeRepair extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- public RecipeRepair() {}
++ // CraftBukkit start - Delegate to new parent class
++ public RecipeRepair() {
++ super(new ItemStack(Items.LEATHER_HELMET), java.util.Arrays.asList(new ItemStack(Items.LEATHER_HELMET)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ ArrayList arraylist = Lists.newArrayList();
+@@ -61,8 +65,18 @@
+ if (i1 < 0) {
+ i1 = 0;
+ }
+-
+- return new ItemStack(itemstack2.getItem(), 1, i1);
++
++ // CraftBukkit start - Construct a dummy repair recipe
++ ItemStack result = new ItemStack(itemstack.getItem(), 1, i1);
++ java.util.List<ItemStack> ingredients = new ArrayList<ItemStack>();
++ ingredients.add(itemstack2.cloneItemStack());
++ ingredients.add(itemstack.cloneItemStack());
++ ShapelessRecipes recipe = new ShapelessRecipes(result.cloneItemStack(), ingredients);
++ inventorycrafting.currentRecipe = recipe;
++ result = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(inventorycrafting, result, CraftingManager.getInstance().lastCraftView, true);
++ return result;
++ // return new ItemStack(itemstack2.getItem(), 1, i1);
++ // CraftBukkit end
+ }
+ }
+
diff --git a/nms-patches/RecipesBannerInnerClass1.patch b/nms-patches/RecipesBannerInnerClass1.patch
new file mode 100644
index 00000000..78471632
--- /dev/null
+++ b/nms-patches/RecipesBannerInnerClass1.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipesBannerInnerClass1.java 2014-11-27 08:59:46.877421071 +1100
++++ src/main/java/net/minecraft/server/RecipesBannerInnerClass1.java 2014-11-27 08:42:10.084851043 +1100
+@@ -1,8 +1,12 @@
+ package net.minecraft.server;
+
+-class RecipesBannerInnerClass1 implements IRecipe {
++class RecipesBannerInnerClass1 extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- private RecipesBannerInnerClass1() {}
++ // CraftBukkit start - Delegate to new parent class with bogus info
++ private RecipesBannerInnerClass1() {
++ super(new ItemStack(Items.BANNER, 0, 0), java.util.Arrays.asList(new ItemStack(Items.BANNER)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ ItemStack itemstack = null;
diff --git a/nms-patches/RecipesBannerInnerClass2.patch b/nms-patches/RecipesBannerInnerClass2.patch
new file mode 100644
index 00000000..3fa03250
--- /dev/null
+++ b/nms-patches/RecipesBannerInnerClass2.patch
@@ -0,0 +1,17 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipesBannerInnerClass2.java 2014-11-27 08:59:46.881421054 +1100
++++ src/main/java/net/minecraft/server/RecipesBannerInnerClass2.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,8 +1,12 @@
+ package net.minecraft.server;
+
+-class RecipesBannerInnerClass2 implements IRecipe {
++class RecipesBannerInnerClass2 extends ShapelessRecipes implements IRecipe { // CraftBukkit - added extends
+
+- private RecipesBannerInnerClass2() {}
++ // CraftBukkit start - Delegate to new parent class with bogus info
++ private RecipesBannerInnerClass2() {
++ super(new ItemStack(Items.BANNER, 0, 0), java.util.Arrays.asList(new ItemStack(Items.DYE, 0, 5)));
++ }
++ // CraftBukkit end
+
+ public boolean a(InventoryCrafting inventorycrafting, World world) {
+ boolean flag = false;
diff --git a/nms-patches/RecipesFurnace.patch b/nms-patches/RecipesFurnace.patch
new file mode 100644
index 00000000..be4d28fb
--- /dev/null
+++ b/nms-patches/RecipesFurnace.patch
@@ -0,0 +1,49 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RecipesFurnace.java 2014-11-27 08:59:46.881421054 +1100
++++ src/main/java/net/minecraft/server/RecipesFurnace.java 2014-11-27 08:42:10.132850949 +1100
+@@ -10,6 +10,7 @@
+ private static final RecipesFurnace a = new RecipesFurnace();
+ public Map recipes = Maps.newHashMap();
+ private Map c = Maps.newHashMap();
++ public Map customRecipes = Maps.newHashMap(); // CraftBukkit - add field
+
+ public static RecipesFurnace getInstance() {
+ return RecipesFurnace.a;
+@@ -52,6 +53,12 @@
+ this.registerRecipe(Blocks.LAPIS_ORE, new ItemStack(Items.DYE, 1, EnumColor.BLUE.getInvColorIndex()), 0.2F);
+ this.registerRecipe(Blocks.QUARTZ_ORE, new ItemStack(Items.QUARTZ), 0.2F);
+ }
++
++ // CraftBukkit start - add method
++ public void registerRecipe(ItemStack itemstack, ItemStack itemstack1) {
++ this.customRecipes.put(itemstack, itemstack1);
++ }
++ // CraftBukkit end
+
+ public void registerRecipe(Block block, ItemStack itemstack, float f) {
+ this.a(Item.getItemOf(block), itemstack, f);
+@@ -67,13 +74,23 @@
+ }
+
+ public ItemStack getResult(ItemStack itemstack) {
+- Iterator iterator = this.recipes.entrySet().iterator();
++ // CraftBukkit start - initialize to customRecipes
++ boolean vanilla = false;
++ Iterator iterator = this.customRecipes.entrySet().iterator();
++ // CraftBukkit end
+
+ Entry entry;
+
+ do {
+ if (!iterator.hasNext()) {
+- return null;
++ // CraftBukkit start - fall back to vanilla recipes
++ if (!vanilla && !recipes.isEmpty()) {
++ iterator = this.recipes.entrySet().iterator();
++ vanilla = true;
++ } else {
++ return null;
++ }
++ // CraftBukkit end
+ }
+
+ entry = (Entry) iterator.next();
diff --git a/nms-patches/RegionFile.patch b/nms-patches/RegionFile.patch
new file mode 100644
index 00000000..80999d9c
--- /dev/null
+++ b/nms-patches/RegionFile.patch
@@ -0,0 +1,81 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RegionFile.java 2014-11-27 08:59:46.881421054 +1100
++++ src/main/java/net/minecraft/server/RegionFile.java 2014-11-27 08:42:10.160850895 +1100
+@@ -86,8 +86,46 @@
+ } catch (IOException ioexception) {
+ ioexception.printStackTrace();
+ }
++ }
++
++ // CraftBukkit start - This is a copy (sort of) of the method below it, make sure they stay in sync
++ public synchronized boolean chunkExists(int i, int j) {
++ if (this.d(i, j)) {
++ return false;
++ } else {
++ try {
++ int k = this.e(i, j);
++
++ if (k == 0) {
++ return false;
++ } else {
++ int l = k >> 8;
++ int i1 = k & 255;
++
++ if (l + i1 > this.f.size()) {
++ return false;
++ }
++
++ this.c.seek((long) (l * 4096));
++ int j1 = this.c.readInt();
++
++ if (j1 > 4096 * i1 || j1 <= 0) {
++ return false;
++ }
++
++ byte b0 = this.c.readByte();
++ if (b0 == 1 || b0 == 2) {
++ return true;
++ }
++ }
++ } catch (IOException ioexception) {
++ return false;
++ }
++ }
+
++ return false;
+ }
++ // CraftBukkit end
+
+ public synchronized DataInputStream a(int i, int j) {
+ if (this.d(i, j)) {
+@@ -214,7 +252,7 @@
+
+ }
+
+- private void a(int i, byte[] abyte, int j) {
++ private void a(int i, byte[] abyte, int j) throws IOException { // CraftBukkit - added throws
+ this.c.seek((long) (i * 4096));
+ this.c.writeInt(j + 1);
+ this.c.writeByte(2);
+@@ -233,19 +271,19 @@
+ return this.e(i, j) != 0;
+ }
+
+- private void a(int i, int j, int k) {
++ private void a(int i, int j, int k) throws IOException { // CraftBukkit - added throws
+ this.d[i + j * 32] = k;
+ this.c.seek((long) ((i + j * 32) * 4));
+ this.c.writeInt(k);
+ }
+
+- private void b(int i, int j, int k) {
++ private void b(int i, int j, int k) throws IOException { // CraftBukkit - added throws
+ this.e[i + j * 32] = k;
+ this.c.seek((long) (4096 + (i + j * 32) * 4));
+ this.c.writeInt(k);
+ }
+
+- public void c() {
++ public void c() throws IOException { // CraftBukkit - added throws
+ if (this.c != null) {
+ this.c.close();
+ }
diff --git a/nms-patches/RemoteControlCommandListener.patch b/nms-patches/RemoteControlCommandListener.patch
new file mode 100644
index 00000000..9cf48bf6
--- /dev/null
+++ b/nms-patches/RemoteControlCommandListener.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/RemoteControlCommandListener.java 2014-11-27 08:59:46.885421036 +1100
++++ src/main/java/net/minecraft/server/RemoteControlCommandListener.java 2014-11-27 08:42:10.152850911 +1100
+@@ -26,6 +26,12 @@
+ public IChatBaseComponent getScoreboardDisplayName() {
+ return new ChatComponentText(this.getName());
+ }
++
++ // CraftBukkit start - Send a String
++ public void sendMessage(String message) {
++ this.b.append(message);
++ }
++ // CraftBukkit end
+
+ public void sendMessage(IChatBaseComponent ichatbasecomponent) {
+ this.b.append(ichatbasecomponent.c());
diff --git a/nms-patches/ScoreboardServer.patch b/nms-patches/ScoreboardServer.patch
new file mode 100644
index 00000000..4b480d13
--- /dev/null
+++ b/nms-patches/ScoreboardServer.patch
@@ -0,0 +1,126 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ScoreboardServer.java 2014-11-27 08:59:46.885421036 +1100
++++ src/main/java/net/minecraft/server/ScoreboardServer.java 2014-11-27 08:42:10.164850887 +1100
+@@ -21,7 +21,7 @@
+ public void handleScoreChanged(ScoreboardScore scoreboardscore) {
+ super.handleScoreChanged(scoreboardscore);
+ if (this.b.contains(scoreboardscore.getObjective())) {
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardScore(scoreboardscore));
++ this.sendAll(new PacketPlayOutScoreboardScore(scoreboardscore)); // CraftBukkit - Internal packet method
+ }
+
+ this.b();
+@@ -29,13 +29,13 @@
+
+ public void handlePlayerRemoved(String s) {
+ super.handlePlayerRemoved(s);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardScore(s));
++ this.sendAll(new PacketPlayOutScoreboardScore(s)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+ public void a(String s, ScoreboardObjective scoreboardobjective) {
+ super.a(s, scoreboardobjective);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardScore(s, scoreboardobjective));
++ this.sendAll(new PacketPlayOutScoreboardScore(s, scoreboardobjective)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+@@ -45,7 +45,7 @@
+ super.setDisplaySlot(i, scoreboardobjective);
+ if (scoreboardobjective1 != scoreboardobjective && scoreboardobjective1 != null) {
+ if (this.h(scoreboardobjective1) > 0) {
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardDisplayObjective(i, scoreboardobjective));
++ this.sendAll(new PacketPlayOutScoreboardDisplayObjective(i, scoreboardobjective)); // CraftBukkit - Internal packet method
+ } else {
+ this.g(scoreboardobjective1);
+ }
+@@ -53,7 +53,7 @@
+
+ if (scoreboardobjective != null) {
+ if (this.b.contains(scoreboardobjective)) {
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardDisplayObjective(i, scoreboardobjective));
++ this.sendAll(new PacketPlayOutScoreboardDisplayObjective(i, scoreboardobjective)); // CraftBukkit - Internal packet method
+ } else {
+ this.e(scoreboardobjective);
+ }
+@@ -66,7 +66,7 @@
+ if (super.addPlayerToTeam(s, s1)) {
+ ScoreboardTeam scoreboardteam = this.getTeam(s1);
+
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, Arrays.asList(new String[] { s}), 3));
++ this.sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, Arrays.asList(new String[] { s}), 3)); // CraftBukkit - Internal packet method
+ this.b();
+ return true;
+ } else {
+@@ -76,7 +76,7 @@
+
+ public void removePlayerFromTeam(String s, ScoreboardTeam scoreboardteam) {
+ super.removePlayerFromTeam(s, scoreboardteam);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, Arrays.asList(new String[] { s}), 4));
++ this.sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, Arrays.asList(new String[] { s}), 4)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+@@ -88,7 +88,7 @@
+ public void handleObjectiveChanged(ScoreboardObjective scoreboardobjective) {
+ super.handleObjectiveChanged(scoreboardobjective);
+ if (this.b.contains(scoreboardobjective)) {
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardObjective(scoreboardobjective, 2));
++ this.sendAll(new PacketPlayOutScoreboardObjective(scoreboardobjective, 2)); // CraftBukkit - Internal packet method
+ }
+
+ this.b();
+@@ -105,19 +105,19 @@
+
+ public void handleTeamAdded(ScoreboardTeam scoreboardteam) {
+ super.handleTeamAdded(scoreboardteam);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 0));
++ this.sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 0)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+ public void handleTeamChanged(ScoreboardTeam scoreboardteam) {
+ super.handleTeamChanged(scoreboardteam);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 2));
++ this.sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 2)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+ public void handleTeamRemoved(ScoreboardTeam scoreboardteam) {
+ super.handleTeamRemoved(scoreboardteam);
+- this.a.getPlayerList().sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 1));
++ this.sendAll(new PacketPlayOutScoreboardTeam(scoreboardteam, 1)); // CraftBukkit - Internal packet method
+ this.b();
+ }
+
+@@ -160,6 +160,7 @@
+
+ while (iterator.hasNext()) {
+ EntityPlayer entityplayer = (EntityPlayer) iterator.next();
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -192,6 +193,7 @@
+
+ while (iterator.hasNext()) {
+ EntityPlayer entityplayer = (EntityPlayer) iterator.next();
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -215,4 +217,14 @@
+
+ return i;
+ }
++
++ // CraftBukkit start - Send to players
++ private void sendAll(Packet packet) {
++ for (EntityPlayer entityplayer : (List<EntityPlayer>) this.a.getPlayerList().players) {
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) {
++ entityplayer.playerConnection.sendPacket(packet);
++ }
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/SecondaryWorldServer.patch b/nms-patches/SecondaryWorldServer.patch
new file mode 100644
index 00000000..935246b6
--- /dev/null
+++ b/nms-patches/SecondaryWorldServer.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/SecondaryWorldServer.java 2014-11-27 08:59:46.889421019 +1100
++++ src/main/java/net/minecraft/server/SecondaryWorldServer.java 2014-11-27 08:42:10.088851036 +1100
+@@ -4,8 +4,10 @@
+
+ private WorldServer a;
+
+- public SecondaryWorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, int i, WorldServer worldserver, MethodProfiler methodprofiler) {
+- super(minecraftserver, idatamanager, new SecondaryWorldData(worldserver.getWorldData()), i, methodprofiler);
++ // CraftBukkit start - Add WorldData, Environment and ChunkGenerator arguments
++ public SecondaryWorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, int i, WorldServer worldserver, MethodProfiler methodprofiler, WorldData worldData, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
++ super(minecraftserver, idatamanager, worldData, i, methodprofiler, env, gen);
++ // CraftBukkit end
+ this.a = worldserver;
+ worldserver.af().a((IWorldBorderListener) (new SecondaryWorldServerInnerClass1(this)));
+ }
diff --git a/nms-patches/ShapedRecipes.patch b/nms-patches/ShapedRecipes.patch
new file mode 100644
index 00000000..5b3908ce
--- /dev/null
+++ b/nms-patches/ShapedRecipes.patch
@@ -0,0 +1,77 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ShapedRecipes.java 2014-11-27 08:59:46.889421019 +1100
++++ src/main/java/net/minecraft/server/ShapedRecipes.java 2014-11-27 08:42:10.164850887 +1100
+@@ -1,5 +1,11 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
++// CraftBukkit end
++
++
+ public class ShapedRecipes implements IRecipe {
+
+ private final int width;
+@@ -14,6 +20,62 @@
+ this.items = aitemstack;
+ this.result = itemstack;
+ }
++
++ // CraftBukkit start
++ public org.bukkit.inventory.ShapedRecipe toBukkitRecipe() {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++ CraftShapedRecipe recipe = new CraftShapedRecipe(result, this);
++ switch (this.height) {
++ case 1:
++ switch (this.width) {
++ case 1:
++ recipe.shape("a");
++ break;
++ case 2:
++ recipe.shape("ab");
++ break;
++ case 3:
++ recipe.shape("abc");
++ break;
++ }
++ break;
++ case 2:
++ switch (this.width) {
++ case 1:
++ recipe.shape("a","b");
++ break;
++ case 2:
++ recipe.shape("ab","cd");
++ break;
++ case 3:
++ recipe.shape("abc","def");
++ break;
++ }
++ break;
++ case 3:
++ switch (this.width) {
++ case 1:
++ recipe.shape("a","b","c");
++ break;
++ case 2:
++ recipe.shape("ab","cd","ef");
++ break;
++ case 3:
++ recipe.shape("abc","def","ghi");
++ break;
++ }
++ break;
++ }
++ char c = 'a';
++ for (ItemStack stack : this.items) {
++ if (stack != null) {
++ recipe.setIngredient(c, org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(stack.getItem()), stack.getData());
++ }
++ c++;
++ }
++ return recipe;
++ }
++ // CraftBukkit end
+
+ public ItemStack b() {
+ return this.result;
diff --git a/nms-patches/ShapelessRecipes.patch b/nms-patches/ShapelessRecipes.patch
new file mode 100644
index 00000000..7e4230c9
--- /dev/null
+++ b/nms-patches/ShapelessRecipes.patch
@@ -0,0 +1,35 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ShapelessRecipes.java 2014-11-27 08:59:46.889421019 +1100
++++ src/main/java/net/minecraft/server/ShapelessRecipes.java 2014-11-27 08:42:10.128850958 +1100
+@@ -5,6 +5,11 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
++// CraftBukkit end
++
+ public class ShapelessRecipes implements IRecipe {
+
+ private final ItemStack result;
+@@ -14,6 +19,20 @@
+ this.result = itemstack;
+ this.ingredients = list;
+ }
++
++ // CraftBukkit start
++ @SuppressWarnings("unchecked")
++ public org.bukkit.inventory.ShapelessRecipe toBukkitRecipe() {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++ CraftShapelessRecipe recipe = new CraftShapelessRecipe(result, this);
++ for (ItemStack stack : (List<ItemStack>) this.ingredients) {
++ if (stack != null) {
++ recipe.addIngredient(org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(stack.getItem()), stack.getData());
++ }
++ }
++ return recipe;
++ }
++ // CraftBukkit end
+
+ public ItemStack b() {
+ return this.result;
diff --git a/nms-patches/SlotFurnaceResult.patch b/nms-patches/SlotFurnaceResult.patch
new file mode 100644
index 00000000..626529de
--- /dev/null
+++ b/nms-patches/SlotFurnaceResult.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/SlotFurnaceResult.java 2014-11-27 08:59:46.893421001 +1100
++++ src/main/java/net/minecraft/server/SlotFurnaceResult.java 2014-11-27 08:42:10.128850958 +1100
+@@ -1,5 +1,10 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.event.inventory.FurnaceExtractEvent;
++// CraftBukkit end
++
+ public class SlotFurnaceResult extends Slot {
+
+ private EntityHuman a;
+@@ -49,6 +54,17 @@
+
+ i = j;
+ }
++
++ // CraftBukkit start - fire FurnaceExtractEvent
++ Player player = (Player) a.getBukkitEntity();
++ TileEntityFurnace furnace = ((TileEntityFurnace) this.inventory);
++ org.bukkit.block.Block block = a.world.getWorld().getBlockAt(furnace.position.getX(), furnace.position.getY(), furnace.position.getZ());
++
++ FurnaceExtractEvent event = new FurnaceExtractEvent(player, block, org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(itemstack.getItem()), itemstack.count, i);
++ a.world.getServer().getPluginManager().callEvent(event);
++
++ i = event.getExpToDrop();
++ // CraftBukkit end
+
+ while (i > 0) {
+ j = EntityExperienceOrb.getOrbValue(i);
diff --git a/nms-patches/SpawnerCreature.patch b/nms-patches/SpawnerCreature.patch
new file mode 100644
index 00000000..96828173
--- /dev/null
+++ b/nms-patches/SpawnerCreature.patch
@@ -0,0 +1,109 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/SpawnerCreature.java 2014-11-27 08:59:46.893421001 +1100
++++ src/main/java/net/minecraft/server/SpawnerCreature.java 2014-11-27 08:42:10.164850887 +1100
+@@ -6,10 +6,16 @@
+ import java.util.Random;
+ import java.util.Set;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.LongHash;
++import org.bukkit.craftbukkit.util.LongHashSet;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
++
+ public final class SpawnerCreature {
+
+ private static final int a = (int) Math.pow(17.0D, 2.0D);
+- private final Set b = Sets.newHashSet();
++ private final LongHashSet b = new LongHashSet();
+
+ public SpawnerCreature() {}
+
+@@ -36,14 +42,18 @@
+ for (int i1 = -b0; i1 <= b0; ++i1) {
+ for (k = -b0; k <= b0; ++k) {
+ boolean flag3 = i1 == -b0 || i1 == b0 || k == -b0 || k == b0;
+- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i1 + l, k + j);
++ // ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i1 + l, k + j);
+
+- if (!this.b.contains(chunkcoordintpair)) {
++ // CraftBukkit start - use LongHash and LongHashSet
++ long chunkCoords = LongHash.toLong(i1 + l, k + j);
++
++ if (!this.b.contains(chunkCoords)) {
+ ++i;
+- if (!flag3 && worldserver.af().isInBounds(chunkcoordintpair)) {
+- this.b.add(chunkcoordintpair);
++ if (!flag3 && worldserver.af().isInBounds(chunkCoords)) {
++ this.b.add(chunkCoords);
+ }
+ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -57,18 +67,41 @@
+
+ for (int k1 = 0; k1 < j; ++k1) {
+ EnumCreatureType enumcreaturetype = aenumcreaturetype[k1];
++
++ // CraftBukkit start - Use per-world spawn limits
++ int limit = enumcreaturetype.b();
++ switch (enumcreaturetype) {
++ case MONSTER:
++ limit = worldserver.getWorld().getMonsterSpawnLimit();
++ break;
++ case CREATURE:
++ limit = worldserver.getWorld().getAnimalSpawnLimit();
++ break;
++ case WATER_CREATURE:
++ limit = worldserver.getWorld().getWaterAnimalSpawnLimit();
++ break;
++ case AMBIENT:
++ limit = worldserver.getWorld().getAmbientSpawnLimit();
++ break;
++ }
++
++ if (limit == 0) {
++ continue;
++ }
++ // CraftBukkit end
+
+ if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2)) {
+ k = worldserver.a(enumcreaturetype.a());
+- int l1 = enumcreaturetype.b() * i / SpawnerCreature.a;
++ int l1 = limit * i / a; // CraftBukkit - use per-world limits
+
+ if (k <= l1) {
+ Iterator iterator1 = this.b.iterator();
+
+ label115:
+ while (iterator1.hasNext()) {
+- ChunkCoordIntPair chunkcoordintpair1 = (ChunkCoordIntPair) iterator1.next();
+- BlockPosition blockposition1 = getRandomPosition(worldserver, chunkcoordintpair1.x, chunkcoordintpair1.z);
++ // CraftBukkit start = use LongHash and LongObjectHashMap
++ long key = ((Long) iterator1.next()).longValue();
++ BlockPosition blockposition1 = getRandomPosition(worldserver, LongHash.msw(key), LongHash.lsw(key));
+ int i2 = blockposition1.getX();
+ int j2 = blockposition1.getY();
+ int k2 = blockposition1.getZ();
+@@ -120,7 +153,7 @@
+ groupdataentity = entityinsentient.prepare(worldserver.E(new BlockPosition(entityinsentient)), groupdataentity);
+ if (entityinsentient.canSpawn()) {
+ ++l2;
+- worldserver.addEntity(entityinsentient);
++ worldserver.addEntity(entityinsentient, SpawnReason.NATURAL); // CraftBukkit - Added a reason for spawning this creature
+ }
+
+ if (l2 >= entityinsentient.bU()) {
+@@ -214,8 +247,10 @@
+ }
+
+ entityinsentient.setPositionRotation((double) ((float) j1 + 0.5F), (double) blockposition.getY(), (double) ((float) k1 + 0.5F), random.nextFloat() * 360.0F, 0.0F);
+- world.addEntity(entityinsentient);
++ // CraftBukkit start - Added a reason for spawning this creature, moved entityinsentient.prepare(groupdataentity) up
+ groupdataentity = entityinsentient.prepare(world.E(new BlockPosition(entityinsentient)), groupdataentity);
++ world.addEntity(entityinsentient, SpawnReason.CHUNK_GEN);
++ // CraftBukkit end
+ flag = true;
+ }
+
diff --git a/nms-patches/StatisticManager.patch b/nms-patches/StatisticManager.patch
new file mode 100644
index 00000000..5b9486b3
--- /dev/null
+++ b/nms-patches/StatisticManager.patch
@@ -0,0 +1,15 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/StatisticManager.java 2014-11-27 08:59:46.893421001 +1100
++++ src/main/java/net/minecraft/server/StatisticManager.java 2014-11-27 08:42:10.172850872 +1100
+@@ -19,6 +19,12 @@
+
+ public void b(EntityHuman entityhuman, Statistic statistic, int i) {
+ if (!statistic.d() || this.b((Achievement) statistic)) {
++ // CraftBukkit start - fire Statistic events
++ org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(entityhuman, statistic, this.getStatisticValue(statistic), i);
++ if (cancellable != null && cancellable.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.setStatistic(entityhuman, statistic, this.getStatisticValue(statistic) + i);
+ }
+ }
diff --git a/nms-patches/SwitchHelperLogVariant.patch b/nms-patches/SwitchHelperLogVariant.patch
new file mode 100644
index 00000000..e6b38cc5
--- /dev/null
+++ b/nms-patches/SwitchHelperLogVariant.patch
@@ -0,0 +1,9 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/SwitchHelperLogVariant.java 2014-11-27 08:59:46.897420984 +1100
++++ src/main/java/net/minecraft/server/SwitchHelperLogVariant.java 2014-11-27 08:42:10.084851043 +1100
+@@ -1,5 +1,6 @@
+ package net.minecraft.server;
+
++// CraftBukkit - imported for visibility
+ class SwitchHelperLogVariant {
+
+ static final int[] a = new int[EnumLogVariant.values().length];
diff --git a/nms-patches/ThreadCommandReader.patch b/nms-patches/ThreadCommandReader.patch
new file mode 100644
index 00000000..87bf341a
--- /dev/null
+++ b/nms-patches/ThreadCommandReader.patch
@@ -0,0 +1,43 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ThreadCommandReader.java 2014-11-27 08:59:46.897420984 +1100
++++ src/main/java/net/minecraft/server/ThreadCommandReader.java 2014-11-27 08:42:10.084851043 +1100
+@@ -4,6 +4,8 @@
+ import java.io.IOException;
+ import java.io.InputStreamReader;
+
++import static org.bukkit.craftbukkit.Main.*; // CraftBukkit
++
+ class ThreadCommandReader extends Thread {
+
+ final DedicatedServer server;
+@@ -14,13 +16,28 @@
+ }
+
+ public void run() {
+- BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in));
++ // CraftBukkit start
++ if (!useConsole) {
++ return;
++ }
++ // CraftBukkit end
++
++ jline.console.ConsoleReader bufferedreader = this.server.reader; // CraftBukkit
+
+ String s;
+
+ try {
+- while (!this.server.isStopped() && this.server.isRunning() && (s = bufferedreader.readLine()) != null) {
+- this.server.issueCommand(s, this.server);
++ // CraftBukkit start - JLine disabling compatibility
++ while (!this.server.isStopped() && this.server.isRunning()) {
++ if (useJline) {
++ s = bufferedreader.readLine(">", null);
++ } else {
++ s = bufferedreader.readLine();
++ }
++ if (s != null && s.trim().length() > 0) { // Trim to filter lines which are just spaces
++ this.server.issueCommand(s, this.server);
++ }
++ // CraftBukkit end
+ }
+ } catch (IOException ioexception) {
+ DedicatedServer.aR().error("Exception handling console input", ioexception);
diff --git a/nms-patches/ThreadPlayerLookupUUID.patch b/nms-patches/ThreadPlayerLookupUUID.patch
new file mode 100644
index 00000000..d0732690
--- /dev/null
+++ b/nms-patches/ThreadPlayerLookupUUID.patch
@@ -0,0 +1,72 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/ThreadPlayerLookupUUID.java 2014-11-27 08:59:46.901420966 +1100
++++ src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java 2014-11-27 08:42:10.164850887 +1100
+@@ -5,6 +5,12 @@
+ import java.math.BigInteger;
+ import java.util.UUID;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.Waitable;
++import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
++import org.bukkit.event.player.PlayerPreLoginEvent;
++// CraftBukkit end
++
+ class ThreadPlayerLookupUUID extends Thread {
+
+ final LoginListener a;
+@@ -22,6 +28,44 @@
+
+ LoginListener.a(this.a, LoginListener.a(this.a).aB().hasJoinedServer(new GameProfile((UUID) null, gameprofile.getName()), s));
+ if (LoginListener.b(this.a) != null) {
++ // CraftBukkit start - fire PlayerPreLoginEvent
++ if (!this.a.networkManager.g()) {
++ return;
++ }
++
++ String playerName = LoginListener.a(this.a).getName();
++ java.net.InetAddress address = ((java.net.InetSocketAddress) a.networkManager.getSocketAddress()).getAddress();
++ java.util.UUID uniqueId = LoginListener.b(this.a).getId();
++ final org.bukkit.craftbukkit.CraftServer server = LoginListener.a(this.a).server;
++
++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId);
++ server.getPluginManager().callEvent(asyncEvent);
++
++ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
++ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
++ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
++ event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage());
++ }
++ Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() {
++ @Override
++ protected PlayerPreLoginEvent.Result evaluate() {
++ server.getPluginManager().callEvent(event);
++ return event.getResult();
++ }};
++
++ LoginListener.a(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
++
+ LoginListener.e().info("UUID of player " + LoginListener.b(this.a).getName() + " is " + LoginListener.b(this.a).getId());
+ LoginListener.a(this.a, EnumProtocolState.READY_TO_ACCEPT);
+ } else if (LoginListener.a(this.a).S()) {
+@@ -41,6 +85,11 @@
+ this.a.disconnect("Authentication servers are down. Please try again later, sorry!");
+ LoginListener.e().error("Couldn\'t verify username because servers are unavailable");
+ }
++ // CraftBukkit start - catch all exceptions
++ } catch (Exception exception) {
++ this.a.disconnect("Failed to verify username!");
++ LoginListener.a(this.a).server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + LoginListener.a(this.a).getName(), exception);
++ // CraftBukkit end
+ }
+
+ }
diff --git a/nms-patches/TileEntity.patch b/nms-patches/TileEntity.patch
new file mode 100644
index 00000000..dd76aea1
--- /dev/null
+++ b/nms-patches/TileEntity.patch
@@ -0,0 +1,24 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntity.java 2014-11-27 08:59:46.913420913 +1100
++++ src/main/java/net/minecraft/server/TileEntity.java 2014-11-27 08:42:10.148850918 +1100
+@@ -6,6 +6,8 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import org.bukkit.inventory.InventoryHolder; // CraftBukkit
++
+ public abstract class TileEntity {
+
+ private static final Logger a = LogManager.getLogger();
+@@ -182,4 +184,12 @@
+ a(TileEntityFlowerPot.class, "FlowerPot");
+ a(TileEntityBanner.class, "Banner");
+ }
++
++ // CraftBukkit start - add method
++ public InventoryHolder getOwner() {
++ org.bukkit.block.BlockState state = world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState();
++ if (state instanceof InventoryHolder) return (InventoryHolder) state;
++ return null;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/TileEntityBeacon.patch b/nms-patches/TileEntityBeacon.patch
new file mode 100644
index 00000000..a5179536
--- /dev/null
+++ b/nms-patches/TileEntityBeacon.patch
@@ -0,0 +1,54 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityBeacon.java 2014-11-27 08:59:46.901420966 +1100
++++ src/main/java/net/minecraft/server/TileEntityBeacon.java 2014-11-27 08:42:10.152850911 +1100
+@@ -5,6 +5,11 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class TileEntityBeacon extends TileEntityContainer implements IUpdatePlayerListBox, IInventory {
+
+ public static final MobEffectList[][] a = new MobEffectList[][] { { MobEffectList.FASTER_MOVEMENT, MobEffectList.FASTER_DIG}, { MobEffectList.RESISTANCE, MobEffectList.JUMP}, { MobEffectList.INCREASE_DAMAGE}, { MobEffectList.REGENERATION}};
+@@ -15,6 +20,30 @@
+ private int l;
+ private ItemStack inventorySlot;
+ private String n;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return new ItemStack[] { this.inventorySlot };
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public TileEntityBeacon() {}
+
+@@ -182,7 +211,7 @@
+ }
+
+ public int getSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ public ItemStack getItem(int i) {
diff --git a/nms-patches/TileEntityBrewingStand.patch b/nms-patches/TileEntityBrewingStand.patch
new file mode 100644
index 00000000..80e791ad
--- /dev/null
+++ b/nms-patches/TileEntityBrewingStand.patch
@@ -0,0 +1,94 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityBrewingStand.java 2014-11-27 08:59:46.901420966 +1100
++++ src/main/java/net/minecraft/server/TileEntityBrewingStand.java 2014-11-27 08:42:10.132850949 +1100
+@@ -3,6 +3,12 @@
+ import java.util.Arrays;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.BrewEvent;
++// CraftBukkit end
++
+ public class TileEntityBrewingStand extends TileEntityContainer implements IUpdatePlayerListBox, IWorldInventory {
+
+ private static final int[] a = new int[] { 3};
+@@ -12,8 +18,35 @@
+ private boolean[] i;
+ private Item j;
+ private String k;
++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
+
+ public TileEntityBrewingStand() {}
++
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = 64;
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public String getName() {
+ return this.hasCustomName() ? this.k : "container.brewing";
+@@ -32,9 +65,14 @@
+ }
+
+ public void c() {
++ // CraftBukkit start - Use wall time instead of ticks for brewing
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ this.lastTick = MinecraftServer.currentTick;
++
+ if (this.brewTime > 0) {
+- --this.brewTime;
+- if (this.brewTime == 0) {
++ this.brewTime -= elapsedTicks;
++ if (this.brewTime <= 0) { // == -> <=
++ // CraftBukkit end
+ this.o();
+ this.update();
+ } else if (!this.n()) {
+@@ -109,6 +147,16 @@
+ private void o() {
+ if (this.n()) {
+ ItemStack itemstack = this.items[3];
++
++ // CraftBukkit start
++ if (getOwner() != null) {
++ BrewEvent event = new BrewEvent(world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), (org.bukkit.inventory.BrewerInventory) this.getOwner().getInventory());
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
+
+ for (int i = 0; i < 3; ++i) {
+ if (this.items[i] != null && this.items[i].getItem() == Items.POTION) {
+@@ -221,7 +269,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return this.maxStack; // CraftBukkit
+ }
+
+ public boolean a(EntityHuman entityhuman) {
diff --git a/nms-patches/TileEntityChest.patch b/nms-patches/TileEntityChest.patch
new file mode 100644
index 00000000..52d98548
--- /dev/null
+++ b/nms-patches/TileEntityChest.patch
@@ -0,0 +1,104 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityChest.java 2014-11-27 08:59:46.905420949 +1100
++++ src/main/java/net/minecraft/server/TileEntityChest.java 2014-11-27 08:42:10.168850880 +1100
+@@ -3,6 +3,11 @@
+ import java.util.Iterator;
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class TileEntityChest extends TileEntityContainer implements IUpdatePlayerListBox, IInventory {
+
+ private ItemStack[] items = new ItemStack[27];
+@@ -19,6 +24,31 @@
+ private String p;
+
+ public TileEntityChest() {}
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public int getSize() {
+ return 27;
+@@ -125,10 +155,11 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean a(EntityHuman entityhuman) {
++ if (this.world == null) return true; // CraftBukkit
+ return this.world.getTileEntity(this.position) != this ? false : entityhuman.e((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D) <= 64.0D;
+ }
+
+@@ -304,9 +335,22 @@
+ if (this.l < 0) {
+ this.l = 0;
+ }
++
++ int oldPower = Math.max(0, Math.min(15, this.l)); // CraftBukkit - Get power before new viewer is added
+
+ ++this.l;
++ if (this.world == null) return; // CraftBukkit
+ this.world.playBlockAction(this.position, this.w(), 1, this.l);
++
++ // CraftBukkit start - Call redstone event
++ if (this.w() == Blocks.TRAPPED_CHEST) {
++ int newPower = Math.max(0, Math.min(15, this.l));
++
++ if (oldPower != newPower) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, position.getX(), position.getY(), position.getZ(), oldPower, newPower);
++ }
++ }
++ // CraftBukkit end
+ this.world.applyPhysics(this.position, this.w());
+ this.world.applyPhysics(this.position.down(), this.w());
+ }
+@@ -315,8 +359,21 @@
+
+ public void closeContainer(EntityHuman entityhuman) {
+ if (!entityhuman.v() && this.w() instanceof BlockChest) {
++ int oldPower = Math.max(0, Math.min(15, this.l)); // CraftBukkit - Get power before new viewer is added
++
+ --this.l;
++ if (this.world == null) return; // CraftBukkit
+ this.world.playBlockAction(this.position, this.w(), 1, this.l);
++
++ // CraftBukkit start - Call redstone event
++ if (this.w() == Blocks.TRAPPED_CHEST) {
++ int newPower = Math.max(0, Math.min(15, this.l));
++
++ if (oldPower != newPower) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, position.getX(), position.getY(), position.getZ(), oldPower, newPower);
++ }
++ }
++ // CraftBukkit end
+ this.world.applyPhysics(this.position, this.w());
+ this.world.applyPhysics(this.position.down(), this.w());
+ }
diff --git a/nms-patches/TileEntityCommandListener.patch b/nms-patches/TileEntityCommandListener.patch
new file mode 100644
index 00000000..92b43a41
--- /dev/null
+++ b/nms-patches/TileEntityCommandListener.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityCommandListener.java 2014-11-27 08:59:46.905420949 +1100
++++ src/main/java/net/minecraft/server/TileEntityCommandListener.java 2014-11-27 08:42:10.132850949 +1100
+@@ -6,6 +6,7 @@
+
+ TileEntityCommandListener(TileEntityCommand tileentitycommand) {
+ this.a = tileentitycommand;
++ sender = new org.bukkit.craftbukkit.command.CraftBlockCommandSender(this); // CraftBukkit - add sender
+ }
+
+ public BlockPosition getChunkCoordinates() {
diff --git a/nms-patches/TileEntityDispenser.patch b/nms-patches/TileEntityDispenser.patch
new file mode 100644
index 00000000..918eb349
--- /dev/null
+++ b/nms-patches/TileEntityDispenser.patch
@@ -0,0 +1,63 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityDispenser.java 2014-11-27 08:59:46.905420949 +1100
++++ src/main/java/net/minecraft/server/TileEntityDispenser.java 2014-11-27 08:42:10.116850981 +1100
+@@ -2,11 +2,43 @@
+
+ import java.util.Random;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class TileEntityDispenser extends TileEntityContainer implements IInventory {
+
+ private static final Random f = new Random();
+ private ItemStack[] items = new ItemStack[9];
+ protected String a;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public TileEntityDispenser() {}
+
+@@ -58,6 +90,7 @@
+
+ for (int k = 0; k < this.items.length; ++k) {
+ if (this.items[k] != null && TileEntityDispenser.f.nextInt(j++) == 0) {
++ if (this.items[k].count == 0) continue; // CraftBukkit
+ i = k;
+ }
+ }
+@@ -140,7 +173,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean a(EntityHuman entityhuman) {
diff --git a/nms-patches/TileEntityFurnace.patch b/nms-patches/TileEntityFurnace.patch
new file mode 100644
index 00000000..911664c5
--- /dev/null
+++ b/nms-patches/TileEntityFurnace.patch
@@ -0,0 +1,183 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityFurnace.java 2014-11-27 08:59:46.909420931 +1100
++++ src/main/java/net/minecraft/server/TileEntityFurnace.java 2014-11-27 08:42:10.156850903 +1100
+@@ -1,5 +1,15 @@
+ package net.minecraft.server;
+
++// CraftBukkit start
++import java.util.List;
++
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.FurnaceBurnEvent;
++import org.bukkit.event.inventory.FurnaceSmeltEvent;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++// CraftBukkit end
++
+ public class TileEntityFurnace extends TileEntityContainer implements IUpdatePlayerListBox, IWorldInventory {
+
+ private static final int[] a = new int[] { 0};
+@@ -11,6 +21,32 @@
+ public int cookTime;
+ private int cookTimeTotal;
+ private String m;
++
++ // CraftBukkit start - add fields and methods
++ private int lastTick = MinecraftServer.currentTick;
++ private int maxStack = MAX_STACK;
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public TileEntityFurnace() {}
+
+@@ -132,7 +168,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean isBurning() {
+@@ -142,9 +178,27 @@
+ public void c() {
+ boolean flag = this.isBurning();
+ boolean flag1 = false;
++
++ // CraftBukkit start - Use wall time instead of ticks for cooking
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ this.lastTick = MinecraftServer.currentTick;
++
++ // CraftBukkit - moved from below
++ if (this.isBurning() && this.canBurn()) {
++ this.cookTime += elapsedTicks;
++ if (this.cookTime >= this.cookTimeTotal) {
++ this.cookTime = 0;
++ this.cookTimeTotal = this.a(this.items[0]);
++ this.burn();
++ flag1 = true;
++ }
++ } else {
++ this.cookTime = 0;
++ }
++ // CraftBukkit end
+
+ if (this.isBurning()) {
+- --this.burnTime;
++ this.burnTime -= elapsedTicks; // CraftBukkit - use elapsedTicks in place of constant
+ }
+
+ if (!this.world.isStatic) {
+@@ -153,9 +207,21 @@
+ this.cookTime = MathHelper.clamp(this.cookTime - 2, 0, this.cookTimeTotal);
+ }
+ } else {
+- if (!this.isBurning() && this.canBurn()) {
+- this.ticksForCurrentFuel = this.burnTime = fuelTime(this.items[1]);
+- if (this.isBurning()) {
++ // CraftBukkit start - Handle multiple elapsed ticks
++ if (this.burnTime <= 0 && this.canBurn()) { // CraftBukkit - == to <=
++ CraftItemStack fuel = CraftItemStack.asCraftMirror(this.items[1]);
++
++ FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), fuel, fuelTime(this.items[1]));
++ this.world.getServer().getPluginManager().callEvent(furnaceBurnEvent);
++
++ if (furnaceBurnEvent.isCancelled()) {
++ return;
++ }
++
++ this.ticksForCurrentFuel = furnaceBurnEvent.getBurnTime();
++ this.burnTime += this.ticksForCurrentFuel;
++ if (this.burnTime > 0 && furnaceBurnEvent.isBurning()) {
++ // CraftBukkit end
+ flag1 = true;
+ if (this.items[1] != null) {
+ --this.items[1].count;
+@@ -167,7 +233,8 @@
+ }
+ }
+ }
+-
++
++ /* CraftBukkit start - Moved up
+ if (this.isBurning() && this.canBurn()) {
+ ++this.cookTime;
+ if (this.cookTime == this.cookTimeTotal) {
+@@ -179,6 +246,7 @@
+ } else {
+ this.cookTime = 0;
+ }
++ */
+ }
+
+ if (flag != this.isBurning()) {
+@@ -202,20 +270,48 @@
+ return false;
+ } else {
+ ItemStack itemstack = RecipesFurnace.getInstance().getResult(this.items[0]);
+-
+- return itemstack == null ? false : (this.items[2] == null ? true : (!this.items[2].doMaterialsMatch(itemstack) ? false : (this.items[2].count < this.getMaxStackSize() && this.items[2].count < this.items[2].getMaxStackSize() ? true : this.items[2].count < itemstack.getMaxStackSize())));
++ // CraftBukkit - consider resultant count instead of current count
++ return itemstack == null ? false : (this.items[2] == null ? true : (!this.items[2].doMaterialsMatch(itemstack) ? false : (this.items[2].count + itemstack.count <= this.getMaxStackSize() && this.items[2].count < this.items[2].getMaxStackSize() ? true : this.items[2].count + itemstack.count <= itemstack.getMaxStackSize())));
++
+ }
+ }
+
+ public void burn() {
+ if (this.canBurn()) {
+ ItemStack itemstack = RecipesFurnace.getInstance().getResult(this.items[0]);
++
++ // CraftBukkit start - fire FurnaceSmeltEvent
++ CraftItemStack source = CraftItemStack.asCraftMirror(this.items[0]);
++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack);
+
++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), source, result);
++ this.world.getServer().getPluginManager().callEvent(furnaceSmeltEvent);
++
++ if (furnaceSmeltEvent.isCancelled()) {
++ return;
++ }
++
++ result = furnaceSmeltEvent.getResult();
++ itemstack = CraftItemStack.asNMSCopy(result);
++
++ if (itemstack != null) {
++ if (this.items[2] == null) {
++ this.items[2] = itemstack;
++ } else if (CraftItemStack.asCraftMirror(this.items[2]).isSimilar(result)) {
++ this.items[2].count += itemstack.count;
++ } else {
++ return;
++ }
++ }
++
++ /*
+ if (this.items[2] == null) {
+ this.items[2] = itemstack.cloneItemStack();
+ } else if (this.items[2].getItem() == itemstack.getItem()) {
+ ++this.items[2].count;
+ }
++ */
++ // CraftBukkit end
+
+ if (this.items[0].getItem() == Item.getItemOf(Blocks.SPONGE) && this.items[0].getData() == 1 && this.items[1] != null && this.items[1].getItem() == Items.BUCKET) {
+ this.items[1] = new ItemStack(Items.WATER_BUCKET);
diff --git a/nms-patches/TileEntityHopper.patch b/nms-patches/TileEntityHopper.patch
new file mode 100644
index 00000000..a8f1dc57
--- /dev/null
+++ b/nms-patches/TileEntityHopper.patch
@@ -0,0 +1,154 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityHopper.java 2014-11-27 08:59:46.909420931 +1100
++++ src/main/java/net/minecraft/server/TileEntityHopper.java 2014-11-27 08:42:10.132850949 +1100
+@@ -2,11 +2,45 @@
+
+ import java.util.List;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.InventoryMoveItemEvent;
++import org.bukkit.event.inventory.InventoryPickupItemEvent;
++import org.bukkit.inventory.Inventory;
++// CraftBukkit end
++
+ public class TileEntityHopper extends TileEntityContainer implements IHopper, IUpdatePlayerListBox {
+
+ private ItemStack[] items = new ItemStack[5];
+ private String f;
+ private int g = -1;
++
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public ItemStack[] getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
+
+ public TileEntityHopper() {}
+
+@@ -119,7 +153,7 @@
+ }
+
+ public int getMaxStackSize() {
+- return 64;
++ return maxStack; // CraftBukkit
+ }
+
+ public boolean a(EntityHuman entityhuman) {
+@@ -215,10 +249,35 @@
+ for (int i = 0; i < this.getSize(); ++i) {
+ if (this.getItem(i) != null) {
+ ItemStack itemstack = this.getItem(i).cloneItemStack();
+- ItemStack itemstack1 = addItem(iinventory, this.splitStack(i, 1), enumdirection);
++ // ItemStack itemstack1 = addItem(iinventory, this.splitStack(i, 1), enumdirection);
++
++ // CraftBukkit start - Call event when pushing items into other inventories
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.splitStack(i, 1));
++
++ Inventory destinationInventory;
++ // Have to special case large chests as they work oddly
++ if (iinventory instanceof InventoryLargeChest) {
++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
++ } else {
++ destinationInventory = iinventory.getOwner().getInventory();
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
++ this.getWorld().getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ this.setItem(i, itemstack);
++ this.d(8); // Delay hopper checks
++ return false;
++ }
++ ItemStack itemstack1 = addItem(iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection);
+
+ if (itemstack1 == null || itemstack1.count == 0) {
+- iinventory.update();
++ if (event.getItem().equals(oitemstack)) {
++ iinventory.update();
++ } else {
++ this.setItem(i, itemstack);
++ }
++ // CraftBukkit end
+ return true;
+ }
+
+@@ -325,10 +384,41 @@
+
+ if (itemstack != null && b(iinventory, itemstack, i, enumdirection)) {
+ ItemStack itemstack1 = itemstack.cloneItemStack();
+- ItemStack itemstack2 = addItem(ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
++ // ItemStack itemstack2 = addItem(ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
++ // CraftBukkit start - Call event on collection of items from inventories into the hopper
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.splitStack(i, 1));
++
++ Inventory sourceInventory;
++ // Have to special case large chests as they work oddly
++ if (iinventory instanceof InventoryLargeChest) {
++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
++ } else {
++ sourceInventory = iinventory.getOwner().getInventory();
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false);
++
++ ihopper.getWorld().getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ iinventory.setItem(i, itemstack1);
++
++ if (ihopper instanceof TileEntityHopper) {
++ ((TileEntityHopper) ihopper).d(8); // Delay hopper checks
++ } else if (ihopper instanceof EntityMinecartHopper) {
++ ((EntityMinecartHopper) ihopper).l(4); // Delay hopper minecart checks
++ }
++
++ return false;
++ }
++ ItemStack itemstack2 = addItem(ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
+
+ if (itemstack2 == null || itemstack2.count == 0) {
+- iinventory.update();
++ if (event.getItem().equals(oitemstack)) {
++ iinventory.update();
++ } else {
++ iinventory.setItem(i, itemstack1);
++ }
++ // CraftBukkit end
+ return true;
+ }
+
+@@ -344,6 +434,14 @@
+ if (entityitem == null) {
+ return false;
+ } else {
++ // CraftBukkit start
++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(iinventory.getOwner().getInventory(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ entityitem.world.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++
+ ItemStack itemstack = entityitem.getItemStack().cloneItemStack();
+ ItemStack itemstack1 = addItem(iinventory, itemstack, (EnumDirection) null);
+
diff --git a/nms-patches/TileEntityNote.patch b/nms-patches/TileEntityNote.patch
new file mode 100644
index 00000000..fd9a329b
--- /dev/null
+++ b/nms-patches/TileEntityNote.patch
@@ -0,0 +1,16 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityNote.java 2014-11-27 08:59:46.913420913 +1100
++++ src/main/java/net/minecraft/server/TileEntityNote.java 2014-11-27 08:42:10.172850872 +1100
+@@ -44,7 +44,12 @@
+ b0 = 4;
+ }
+
+- world.playBlockAction(blockposition, Blocks.NOTEBLOCK, b0, this.note);
++ // CraftBukkit start
++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(this.world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), b0, this.note);
++ if (!event.isCancelled()) {
++ world.playBlockAction(blockposition, Blocks.NOTEBLOCK, event.getInstrument().getType(), event.getNote().getId());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/nms-patches/TileEntityPiston.patch b/nms-patches/TileEntityPiston.patch
new file mode 100644
index 00000000..0c1fa1d0
--- /dev/null
+++ b/nms-patches/TileEntityPiston.patch
@@ -0,0 +1,10 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityPiston.java 2014-11-27 08:59:46.917420896 +1100
++++ src/main/java/net/minecraft/server/TileEntityPiston.java 2014-11-27 08:42:10.152850911 +1100
+@@ -104,6 +104,7 @@
+ }
+
+ public void c() {
++ if (this.world == null) return; // CraftBukkit
+ this.j = this.i;
+ if (this.j >= 1.0F) {
+ this.a(1.0F, 0.25F);
diff --git a/nms-patches/TileEntityRecordPlayer.patch b/nms-patches/TileEntityRecordPlayer.patch
new file mode 100644
index 00000000..068fc0b2
--- /dev/null
+++ b/nms-patches/TileEntityRecordPlayer.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntityRecordPlayer.java 2014-11-27 08:59:46.917420896 +1100
++++ src/main/java/net/minecraft/server/TileEntityRecordPlayer.java 2014-11-27 08:42:10.164850887 +1100
+@@ -29,6 +29,11 @@
+ }
+
+ public void setRecord(ItemStack itemstack) {
++ // CraftBukkit start - There can only be one
++ if (itemstack != null) {
++ itemstack.count = 1;
++ }
++ // CraftBukkit end
+ this.record = itemstack;
+ this.update();
+ }
diff --git a/nms-patches/TileEntitySkull.patch b/nms-patches/TileEntitySkull.patch
new file mode 100644
index 00000000..1504054f
--- /dev/null
+++ b/nms-patches/TileEntitySkull.patch
@@ -0,0 +1,13 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/TileEntitySkull.java 2014-11-27 08:59:46.917420896 +1100
++++ src/main/java/net/minecraft/server/TileEntitySkull.java 2014-11-27 08:42:10.168850880 +1100
+@@ -105,4 +105,10 @@
+ public void setRotation(int i) {
+ this.rotation = i;
+ }
++
++ // CraftBukkit start - add method
++ public int getRotation() {
++ return this.rotation;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/Village.patch b/nms-patches/Village.patch
new file mode 100644
index 00000000..9b58e724
--- /dev/null
+++ b/nms-patches/Village.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/Village.java 2014-11-27 08:59:46.921420877 +1100
++++ src/main/java/net/minecraft/server/Village.java 2014-11-27 08:42:10.104851005 +1100
+@@ -60,7 +60,7 @@
+ EntityIronGolem entityirongolem = new EntityIronGolem(this.a);
+
+ entityirongolem.setPosition(vec3d.a, vec3d.b, vec3d.c);
+- this.a.addEntity(entityirongolem);
++ this.a.addEntity(entityirongolem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE); // CraftBukkit
+ ++this.l;
+ }
+ }
diff --git a/nms-patches/VillageSiege.patch b/nms-patches/VillageSiege.patch
new file mode 100644
index 00000000..f6da3f10
--- /dev/null
+++ b/nms-patches/VillageSiege.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/VillageSiege.java 2014-11-27 08:59:46.921420877 +1100
++++ src/main/java/net/minecraft/server/VillageSiege.java 2014-11-27 08:42:10.100851012 +1100
+@@ -140,7 +140,7 @@
+ }
+
+ entityzombie.setPositionRotation(vec3d.a, vec3d.b, vec3d.c, this.a.random.nextFloat() * 360.0F, 0.0F);
+- this.a.addEntity(entityzombie);
++ this.a.addEntity(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit
+ BlockPosition blockposition = this.f.a();
+
+ entityzombie.a(blockposition, this.f.b());
diff --git a/nms-patches/World.patch b/nms-patches/World.patch
new file mode 100644
index 00000000..03912ea0
--- /dev/null
+++ b/nms-patches/World.patch
@@ -0,0 +1,560 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/World.java 2014-11-27 08:59:46.933420825 +1100
++++ src/main/java/net/minecraft/server/World.java 2014-11-27 08:42:10.132850949 +1100
+@@ -13,6 +13,22 @@
+ import java.util.UUID;
+ import java.util.concurrent.Callable;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.craftbukkit.util.LongHashSet;
++import org.bukkit.generator.ChunkGenerator;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.BlockCanBuildEvent;
++import org.bukkit.event.block.BlockPhysicsEvent;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++import org.bukkit.event.weather.WeatherChangeEvent;
++import org.bukkit.event.weather.ThunderChangeEvent;
++// CraftBukkit end
++
+ public abstract class World implements IBlockAccess {
+
+ protected boolean e;
+@@ -47,7 +63,8 @@
+ private final Calendar J = Calendar.getInstance();
+ public Scoreboard scoreboard = new Scoreboard();
+ public final boolean isStatic;
+- protected Set chunkTickList = Sets.newHashSet();
++ // CraftBukkit - longhashset
++ protected LongHashSet chunkTickList = new LongHashSet();
+ private int K;
+ public boolean allowMonsters;
+ public boolean allowAnimals;
+@@ -55,7 +72,39 @@
+ private final WorldBorder M;
+ int[] H;
+
+- protected World(IDataManager idatamanager, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag) {
++ // CraftBukkit start Added the following
++ private final CraftWorld world;
++ public boolean pvpMode;
++ public boolean keepSpawnInMemory = true;
++ public ChunkGenerator generator;
++
++ public boolean captureBlockStates = false;
++ public boolean captureTreeGeneration = false;
++ public ArrayList<BlockState> capturedBlockStates= new ArrayList<BlockState>();
++ public long ticksPerAnimalSpawns;
++ public long ticksPerMonsterSpawns;
++ public boolean populating;
++ private int tickPosition;
++
++ public CraftWorld getWorld() {
++ return this.world;
++ }
++
++ public CraftServer getServer() {
++ return (CraftServer) Bukkit.getServer();
++ }
++
++ public Chunk getChunkIfLoaded(int x, int z) {
++ return ((ChunkProviderServer) this.chunkProvider).getChunkIfLoaded(x, z);
++ }
++
++ protected World(IDataManager idatamanager, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag, ChunkGenerator gen, org.bukkit.World.Environment env) {
++ 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
++
+ this.K = this.random.nextInt(12000);
+ this.allowMonsters = true;
+ this.allowAnimals = true;
+@@ -66,6 +115,8 @@
+ this.worldProvider = worldprovider;
+ this.isStatic = flag;
+ this.M = worldprovider.getWorldBorder();
++
++ this.getServer().addWorld(this.world); // CraftBukkit
+ }
+
+ public World b() {
+@@ -184,6 +235,27 @@
+ }
+
+ public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) {
++ // CraftBukkit start - tree generation
++ if (this.captureTreeGeneration) {
++ BlockState blockstate = null;
++ Iterator<BlockState> it = capturedBlockStates.iterator();
++ while (it.hasNext()) {
++ BlockState previous = it.next();
++ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) {
++ blockstate = previous;
++ it.remove();
++ break;
++ }
++ }
++ if (blockstate == null) {
++ blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), i);
++ }
++ blockstate.setTypeId(CraftMagicNumbers.getId(iblockdata.getBlock()));
++ blockstate.setRawData((byte) iblockdata.getBlock().toLegacyData(iblockdata));
++ this.capturedBlockStates.add(blockstate);
++ return true;
++ }
++ // CraftBukkit end
+ if (!this.isValidLocation(blockposition)) {
+ return false;
+ } else if (!this.isStatic && this.worldData.getType() == WorldType.DEBUG_ALL_BLOCK_STATES) {
+@@ -191,9 +263,23 @@
+ } else {
+ Chunk chunk = this.getChunkAtWorldCoords(blockposition);
+ Block block = iblockdata.getBlock();
++
++ // CraftBukkit start - capture blockstates
++ BlockState blockstate = null;
++ if (this.captureBlockStates) {
++ blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), i);
++ this.capturedBlockStates.add(blockstate);
++ }
++ // CraftBukkit end
++
+ IBlockData iblockdata1 = chunk.a(blockposition, iblockdata);
+
+ if (iblockdata1 == null) {
++ // CraftBukkit start - remove blockstate if failed
++ if (!this.captureBlockStates) {
++ this.capturedBlockStates.remove(blockstate);
++ }
++ // CraftBukkit end
+ return false;
+ } else {
+ Block block1 = iblockdata1.getBlock();
+@@ -204,6 +290,7 @@
+ this.methodProfiler.b();
+ }
+
++ /*
+ if ((i & 2) != 0 && (!this.isStatic || (i & 4) == 0) && chunk.isReady()) {
+ this.notify(blockposition);
+ }
+@@ -214,12 +301,35 @@
+ this.updateAdjacentComparators(blockposition, block);
+ }
+ }
++ */
++
++ // CraftBukkit start
++ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
++ // Modularize client and physic updates
++ notifyAndUpdatePhysics(blockposition, chunk, block1, block, i);
++ }
++ // CraftBukkit end
+
+ return true;
+ }
+ }
+ }
+
++ // CraftBukkit start - Split off from original setTypeAndData(int i, int j, int k, Block block, int l, int i1) method in order to directly send client and physic updates
++ public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, Block oldBlock, Block newBLock, int flag) {
++ if ((flag & 2) != 0 && (chunk == null || chunk.isReady())) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
++ this.notify(blockposition);
++ }
++
++ if (!this.isStatic && (flag & 1) != 0) {
++ this.update(blockposition, oldBlock);
++ if (newBLock.isComplexRedstone()) {
++ this.updateAdjacentComparators(blockposition, newBLock);
++ }
++ }
++ }
++ // CraftBukkit end
++
+ public boolean setAir(BlockPosition blockposition) {
+ return this.setTypeAndData(blockposition, Blocks.AIR.getBlockData(), 3);
+ }
+@@ -253,6 +363,11 @@
+
+ public void update(BlockPosition blockposition, Block block) {
+ if (this.worldData.getType() != WorldType.DEBUG_ALL_BLOCK_STATES) {
++ // CraftBukkit start
++ if (populating) {
++ return;
++ }
++ // CraftBukkit end
+ this.applyPhysics(blockposition, block);
+ }
+
+@@ -328,6 +443,17 @@
+ IBlockData iblockdata = this.getType(blockposition);
+
+ try {
++ // CraftBukkit start
++ CraftWorld world = ((WorldServer) this).getWorld();
++ if (world != null) {
++ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftMagicNumbers.getId(block));
++ this.getServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ iblockdata.getBlock().doPhysics(this, blockposition, iblockdata, block);
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.a(throwable, "Exception while updating neighbours");
+@@ -497,6 +623,17 @@
+ }
+
+ public IBlockData getType(BlockPosition blockposition) {
++ // CraftBukkit start - tree generation
++ if (captureTreeGeneration) {
++ Iterator<BlockState> it = capturedBlockStates.iterator();
++ while (it.hasNext()) {
++ BlockState previous = it.next();
++ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) {
++ return CraftMagicNumbers.getBlock(previous.getTypeId()).fromLegacyData(previous.getRawData());
++ }
++ }
++ }
++ // CraftBukkit end
+ if (!this.isValidLocation(blockposition)) {
+ return Blocks.AIR.getBlockData();
+ } else {
+@@ -704,6 +841,13 @@
+ }
+
+ public boolean addEntity(Entity entity) {
++ // CraftBukkit start - Used for entities other than creatures
++ return addEntity(entity, SpawnReason.DEFAULT);
++ }
++
++ public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason
++ if (entity == null) return false;
++ // CraftBukkit end
+ int i = MathHelper.floor(entity.locX / 16.0D);
+ int j = MathHelper.floor(entity.locZ / 16.0D);
+ boolean flag = entity.attachedToPlayer;
+@@ -712,7 +856,35 @@
+ flag = true;
+ }
+
++ // CraftBukkit start
++ org.bukkit.event.Cancellable event = null;
++ if (entity instanceof EntityLiving && !(entity instanceof EntityPlayer)) {
++ boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal || entity instanceof EntityGolem;
++ boolean isMonster = entity instanceof EntityMonster || entity instanceof EntityGhast || entity instanceof EntitySlime;
++
++ if (spawnReason != SpawnReason.CUSTOM) {
++ if (isAnimal && !allowAnimals || isMonster && !allowMonsters) {
++ entity.dead = true;
++ return false;
++ }
++ }
++
++ event = CraftEventFactory.callCreatureSpawnEvent((EntityLiving) entity, spawnReason);
++ } else if (entity instanceof EntityItem) {
++ event = CraftEventFactory.callItemSpawnEvent((EntityItem) entity);
++ } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Projectile) {
++ // Not all projectiles extend EntityProjectile, so check for Bukkit interface instead
++ event = CraftEventFactory.callProjectileLaunchEvent(entity);
++ }
++
++ if (event != null && (event.isCancelled() || entity.dead)) {
++ entity.dead = true;
++ return false;
++ }
++ // CraftBukkit end
++
+ if (!flag && !this.isChunkLoaded(i, j, true)) {
++ entity.dead = true;
+ return false;
+ } else {
+ if (entity instanceof EntityHuman) {
+@@ -734,6 +906,7 @@
+ ((IWorldAccess) this.u.get(i)).a(entity);
+ }
+
++ entity.valid = true; // CraftBukkit
+ }
+
+ protected void b(Entity entity) {
+@@ -741,6 +914,7 @@
+ ((IWorldAccess) this.u.get(i)).b(entity);
+ }
+
++ entity.valid = false; // CraftBukkit
+ }
+
+ public void kill(Entity entity) {
+@@ -775,7 +949,15 @@
+ this.getChunkAt(i, j).b(entity);
+ }
+
+- this.entityList.remove(entity);
++ // CraftBukkit start - Decrement loop variable field if we've already ticked this entity
++ int index = this.entityList.indexOf(entity);
++ if (index != -1) {
++ if (index <= this.tickPosition) {
++ this.tickPosition--;
++ }
++ this.entityList.remove(index);
++ }
++ // CraftBukkit end
+ this.b(entity);
+ }
+
+@@ -958,6 +1140,11 @@
+
+ for (i = 0; i < this.k.size(); ++i) {
+ entity = (Entity) this.k.get(i);
++ // CraftBukkit start - Fixed an NPE
++ if (entity == null) {
++ continue;
++ }
++ // CraftBukkit end
+
+ try {
+ ++entity.ticksLived;
+@@ -1001,8 +1188,10 @@
+ this.g.clear();
+ this.methodProfiler.c("regular");
+
+- for (i = 0; i < this.entityList.size(); ++i) {
+- entity = (Entity) this.entityList.get(i);
++ // CraftBukkit start - Use field for loop variable
++ for (this.tickPosition = 0; this.tickPosition < this.entityList.size(); ++this.tickPosition) {
++ entity = (Entity) this.entityList.get(this.tickPosition);
++ // CraftBukkit end
+ if (entity.vehicle != null) {
+ if (!entity.vehicle.dead && entity.vehicle.passenger == entity) {
+ continue;
+@@ -1033,7 +1222,7 @@
+ this.getChunkAt(j, k).b(entity);
+ }
+
+- this.entityList.remove(i--);
++ this.entityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable
+ this.b(entity);
+ }
+
+@@ -1042,6 +1231,14 @@
+
+ this.methodProfiler.c("blockEntities");
+ this.L = true;
++ // CraftBukkit start - From below, clean up tile entities before ticking them
++ if (!this.b.isEmpty()) {
++ this.tileEntityList.removeAll(this.b);
++ this.h.removeAll(this.b);
++ this.b.clear();
++ }
++ // CraftBukkit end
++
+ Iterator iterator = this.tileEntityList.iterator();
+
+ while (iterator.hasNext()) {
+@@ -1073,11 +1270,13 @@
+ }
+
+ this.L = false;
++ /* CraftBukkit start - Moved up
+ if (!this.b.isEmpty()) {
+ this.tileEntityList.removeAll(this.b);
+ this.h.removeAll(this.b);
+ this.b.clear();
+ }
++ */ // CraftBukkit end
+
+ this.methodProfiler.c("pendingBlockEntities");
+ if (!this.a.isEmpty()) {
+@@ -1085,9 +1284,11 @@
+ TileEntity tileentity1 = (TileEntity) this.a.get(l);
+
+ if (!tileentity1.x()) {
++ /* CraftBukkit start - Order matters, moved down
+ if (!this.h.contains(tileentity1)) {
+ this.a(tileentity1);
+ }
++ // CraftBukkit end */
+
+ if (this.isLoaded(tileentity1.getPosition())) {
+ this.getChunkAtWorldCoords(tileentity1.getPosition()).a(tileentity1.getPosition(), tileentity1);
+@@ -1141,7 +1342,10 @@
+ int j = MathHelper.floor(entity.locZ);
+ byte b0 = 32;
+
+- if (!flag || this.isAreaLoaded(i - b0, 0, j - b0, i + b0, 0, j + b0, true)) {
++ // CraftBukkit start - Use neighbor cache instead of looking up
++ Chunk startingChunk = this.getChunkIfLoaded(i >> 4, j >> 4);
++ if (!flag || (startingChunk != null && startingChunk.areNeighborsLoaded(2)) /* this.isAreaLoaded(i - b0, 0, j - b0, i + b0, 0, j + b0) */) {
++ // CraftBukkit end
+ entity.P = entity.locX;
+ entity.Q = entity.locY;
+ entity.R = entity.locZ;
+@@ -1615,7 +1819,13 @@
+ --j;
+ this.worldData.setThunderDuration(j);
+ if (j <= 0) {
+- this.worldData.setThundering(!this.worldData.isThundering());
++ // CraftBukkit start
++ ThunderChangeEvent thunder = new ThunderChangeEvent(this.getWorld(), !this.worldData.isThundering());
++ this.getServer().getPluginManager().callEvent(thunder);
++ if (!thunder.isCancelled()) {
++ this.worldData.setThundering(!this.worldData.isThundering());
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -1639,7 +1849,14 @@
+ --k;
+ this.worldData.setWeatherDuration(k);
+ if (k <= 0) {
+- this.worldData.setStorm(!this.worldData.hasStorm());
++ // CraftBukkit start
++ WeatherChangeEvent weather = new WeatherChangeEvent(this.getWorld(), !this.worldData.hasStorm());
++ this.getServer().getPluginManager().callEvent(weather);
++
++ if (!weather.isCancelled()) {
++ this.worldData.setStorm(!this.worldData.hasStorm());
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -1656,7 +1873,7 @@
+ }
+
+ protected void D() {
+- this.chunkTickList.clear();
++ // this.chunkTickList.clear(); // CraftBukkit - removed
+ this.methodProfiler.a("buildList");
+
+ int i;
+@@ -1673,7 +1890,7 @@
+
+ for (int i1 = -l; i1 <= l; ++i1) {
+ for (int j1 = -l; j1 <= l; ++j1) {
+- this.chunkTickList.add(new ChunkCoordIntPair(i1 + j, j1 + k));
++ this.chunkTickList.add(org.bukkit.craftbukkit.util.LongHash.toLong(i1 + j, j1 + k));
+ }
+ }
+ }
+@@ -1851,7 +2068,10 @@
+ }
+
+ public boolean c(EnumSkyBlock enumskyblock, BlockPosition blockposition) {
+- if (!this.areChunksLoaded(blockposition, 17, false)) {
++ // CraftBukkit start - Use neighbor cache instead of looking up
++ Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
++ if (chunk == null || !chunk.areNeighborsLoaded(1) /*!this.areChunksLoaded(blockposition, 17, false)*/) {
++ // CraftBukkit end
+ return false;
+ } else {
+ int i = 0;
+@@ -2095,8 +2315,17 @@
+
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
++ // CraftBukkit start - Split out persistent check, don't apply it to special persistent mobs
++ if (entity instanceof EntityInsentient) {
++ EntityInsentient entityinsentient = (EntityInsentient) entity;
++ if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) {
++ continue;
++ }
++ }
+
+- if ((!(entity instanceof EntityInsentient) || !((EntityInsentient) entity).isPersistent()) && oclass.isAssignableFrom(entity.getClass())) {
++ if (oclass.isAssignableFrom(entity.getClass())) {
++ // if ((!(entity instanceof EntityInsentient) || !((EntityInsentient) entity).isPersistent()) && oclass.isAssignableFrom(entity.getClass())) {
++ // CraftBukkit end
+ ++i;
+ }
+ }
+@@ -2105,12 +2334,17 @@
+ }
+
+ public void b(Collection collection) {
+- this.entityList.addAll(collection);
++ // CraftBukkit start
++ // this.entityList.addAll(collection);
+ Iterator iterator = collection.iterator();
+
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
+-
++ if (entity == null) {
++ continue;
++ }
++ this.entityList.add(entity);
++ // CraftBukkit end
+ this.a(entity);
+ }
+
+@@ -2124,7 +2358,13 @@
+ Block block1 = this.getType(blockposition).getBlock();
+ AxisAlignedBB axisalignedbb = flag ? null : block.a(this, blockposition, block.getBlockData());
+
+- return axisalignedbb != null && !this.a(axisalignedbb, entity) ? false : (block1.getMaterial() == Material.ORIENTABLE && block == Blocks.ANVIL ? true : block1.getMaterial().isReplaceable() && block.canPlace(this, blockposition, enumdirection, itemstack));
++ // CraftBukkit start - store default return
++ boolean defaultReturn = axisalignedbb != null && !this.a(axisalignedbb, entity) ? false : (block1.getMaterial() == Material.ORIENTABLE && block == Blocks.ANVIL ? true : block1.getMaterial().isReplaceable() && block.canPlace(this, blockposition, enumdirection, itemstack));
++ BlockCanBuildEvent event = new BlockCanBuildEvent(this.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftMagicNumbers.getId(block), defaultReturn);
++ this.getServer().getPluginManager().callEvent(event);
++
++ return event.isBuildable();
++ // CraftBukkit end
+ }
+
+ public int getBlockPower(BlockPosition blockposition, EnumDirection enumdirection) {
+@@ -2215,6 +2455,11 @@
+
+ for (int i = 0; i < this.players.size(); ++i) {
+ EntityHuman entityhuman1 = (EntityHuman) this.players.get(i);
++ // CraftBukkit start - Fixed an NPE
++ if (entityhuman1 == null || entityhuman1.dead) {
++ continue;
++ }
++ // CraftBukkit end
+
+ if (IEntitySelector.d.apply(entityhuman1)) {
+ double d5 = entityhuman1.e(d0, d1, d2);
+@@ -2269,7 +2514,7 @@
+ return null;
+ }
+
+- public void checkSession() {
++ public void checkSession() throws ExceptionWorldConflict { // CraftBukkit - added throws
+ this.dataManager.checkSession();
+ }
+
+@@ -2331,6 +2576,16 @@
+
+ public void everyoneSleeping() {}
+
++ // CraftBukkit start
++ // Calls the method that checks to see if players are sleeping
++ // Called by CraftPlayer.setPermanentSleeping()
++ public void checkSleepStatus() {
++ if (!this.isStatic) {
++ this.everyoneSleeping();
++ }
++ }
++ // CraftBukkit end
++
+ public float h(float f) {
+ return (this.q + (this.r - this.q) * f) * this.j(f);
+ }
+@@ -2538,6 +2793,6 @@
+ int l = j * 16 + 8 - blockposition.getZ();
+ short short0 = 128;
+
+- return k >= -short0 && k <= short0 && l >= -short0 && l <= short0;
++ return k >= -short0 && k <= short0 && l >= -short0 && l <= short0 || !this.keepSpawnInMemory; // CraftBukkit - Added 'this.world.keepSpawnInMemory'
+ }
+ }
diff --git a/nms-patches/WorldBorder.patch b/nms-patches/WorldBorder.patch
new file mode 100644
index 00000000..37416ef7
--- /dev/null
+++ b/nms-patches/WorldBorder.patch
@@ -0,0 +1,25 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldBorder.java 2014-11-27 08:59:46.925420860 +1100
++++ src/main/java/net/minecraft/server/WorldBorder.java 2014-11-27 08:42:10.168850880 +1100
+@@ -32,9 +32,21 @@
+ return (double) (blockposition.getX() + 1) > this.b() && (double) blockposition.getX() < this.d() && (double) (blockposition.getZ() + 1) > this.c() && (double) blockposition.getZ() < this.e();
+ }
+
++ // CraftBukkit start - split method
+ public boolean isInBounds(ChunkCoordIntPair chunkcoordintpair) {
+- return (double) chunkcoordintpair.e() > this.b() && (double) chunkcoordintpair.c() < this.d() && (double) chunkcoordintpair.f() > this.c() && (double) chunkcoordintpair.d() < this.e();
++ return isInBounds(chunkcoordintpair.x, chunkcoordintpair.z);
+ }
++
++ // Inlined the getters from ChunkCoordIntPair
++ public boolean isInBounds(long chunkcoords) {
++ return isInBounds(org.bukkit.craftbukkit.util.LongHash.msw(chunkcoords), org.bukkit.craftbukkit.util.LongHash.lsw(chunkcoords));
++ }
++
++ // Inlined the getters from ChunkCoordIntPair
++ public boolean isInBounds(int x, int z) {
++ return (double) ((x << 4) + 15) > this.b() && (double) (x << 4) < this.d() && (double) ((z << 4) + 15) > this.c() && (double) (x << 4) < this.e();
++ }
++ // CraftBukkit end
+
+ public boolean a(AxisAlignedBB axisalignedbb) {
+ return axisalignedbb.d > this.b() && axisalignedbb.a < this.d() && axisalignedbb.f > this.c() && axisalignedbb.c < this.e();
diff --git a/nms-patches/WorldGenGroundBush.patch b/nms-patches/WorldGenGroundBush.patch
new file mode 100644
index 00000000..9a633c3b
--- /dev/null
+++ b/nms-patches/WorldGenGroundBush.patch
@@ -0,0 +1,14 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldGenGroundBush.java 2014-11-27 08:59:46.925420860 +1100
++++ src/main/java/net/minecraft/server/WorldGenGroundBush.java 2014-11-27 08:42:10.144850927 +1100
+@@ -46,7 +46,11 @@
+ }
+ }
+ }
++ // CraftBukkit start - Return false if gen was unsuccessful
++ } else {
++ return false;
+ }
++ // CraftBukkit end
+
+ return true;
+ }
diff --git a/nms-patches/WorldGenMegaTreeAbstract.patch b/nms-patches/WorldGenMegaTreeAbstract.patch
new file mode 100644
index 00000000..4dcc8625
--- /dev/null
+++ b/nms-patches/WorldGenMegaTreeAbstract.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldGenMegaTreeAbstract.java 2014-11-27 08:59:46.925420860 +1100
++++ src/main/java/net/minecraft/server/WorldGenMegaTreeAbstract.java 2014-11-27 08:42:10.140850934 +1100
+@@ -42,7 +42,7 @@
+
+ for (int k = -b0; k <= b0 && flag; ++k) {
+ for (int l = -b0; l <= b0 && flag; ++l) {
+- if (blockposition.getY() + j < 0 || blockposition.getY() + j >= 256 || !this.a(world.getType(blockposition.a(k, j, l)).getBlock())) {
++ if (blockposition.getY() + j < 0 || blockposition.getY() + j >= 256 || (!this.a(world.getType(blockposition.a(k, j, l)).getBlock()) && world.getType(blockposition.a(k, j, l)).getBlock() != Blocks.SAPLING)) { // CraftBukkit - ignore our own saplings
+ flag = false;
+ }
+ }
diff --git a/nms-patches/WorldGenVillagePiece.patch b/nms-patches/WorldGenVillagePiece.patch
new file mode 100644
index 00000000..ff11ea6a
--- /dev/null
+++ b/nms-patches/WorldGenVillagePiece.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldGenVillagePiece.java 2014-11-27 08:59:46.929420842 +1100
++++ src/main/java/net/minecraft/server/WorldGenVillagePiece.java 2014-11-27 08:42:10.144850927 +1100
+@@ -114,7 +114,7 @@
+ entityvillager.setPositionRotation((double) j1 + 0.5D, (double) k1, (double) l1 + 0.5D, 0.0F, 0.0F);
+ entityvillager.prepare(world.E(new BlockPosition(entityvillager)), (GroupDataEntity) null);
+ entityvillager.setProfession(this.c(i1, entityvillager.getProfession()));
+- world.addEntity(entityvillager);
++ world.addEntity(entityvillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+ }
+
+ }
diff --git a/nms-patches/WorldGenVillagePieces.patch b/nms-patches/WorldGenVillagePieces.patch
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/nms-patches/WorldGenVillagePieces.patch
diff --git a/nms-patches/WorldGenWitchHut.patch b/nms-patches/WorldGenWitchHut.patch
new file mode 100644
index 00000000..cfd76bed
--- /dev/null
+++ b/nms-patches/WorldGenWitchHut.patch
@@ -0,0 +1,11 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldGenWitchHut.java 2014-11-27 08:59:46.933420825 +1100
++++ src/main/java/net/minecraft/server/WorldGenWitchHut.java 2014-11-27 08:42:10.152850911 +1100
+@@ -77,7 +77,7 @@
+
+ entitywitch.setPositionRotation((double) i1 + 0.5D, (double) j1, (double) k1 + 0.5D, 0.0F, 0.0F);
+ entitywitch.prepare(world.E(new BlockPosition(i1, j1, k1)), (GroupDataEntity) null);
+- world.addEntity(entitywitch);
++ world.addEntity(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+ }
+ }
+
diff --git a/nms-patches/WorldManager.patch b/nms-patches/WorldManager.patch
new file mode 100644
index 00000000..a7f97b03
--- /dev/null
+++ b/nms-patches/WorldManager.patch
@@ -0,0 +1,28 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldManager.java 2014-11-27 08:59:46.937420807 +1100
++++ src/main/java/net/minecraft/server/WorldManager.java 2014-11-27 08:42:10.132850949 +1100
+@@ -23,11 +23,13 @@
+ }
+
+ public void a(String s, double d0, double d1, double d2, float f, float f1) {
+- this.a.getPlayerList().sendPacketNearby(d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world.worldProvider.getDimension(), new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1));
++ // CraftBukkit - this.world.dimension
++ this.a.getPlayerList().sendPacketNearby(d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world.dimension, new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1));
+ }
+
+ public void a(EntityHuman entityhuman, String s, double d0, double d1, double d2, float f, float f1) {
+- this.a.getPlayerList().sendPacketNearby(entityhuman, d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world.worldProvider.getDimension(), new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1));
++ // CraftBukkit - this.world.dimension
++ this.a.getPlayerList().sendPacketNearby(entityhuman, d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world.dimension, new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1));
+ }
+
+ public void a(int i, int j, int k, int l, int i1, int j1) {}
+@@ -41,7 +43,8 @@
+ public void a(String s, BlockPosition blockposition) {}
+
+ public void a(EntityHuman entityhuman, int i, BlockPosition blockposition, int j) {
+- this.a.getPlayerList().sendPacketNearby(entityhuman, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 64.0D, this.world.worldProvider.getDimension(), new PacketPlayOutWorldEvent(i, blockposition, j, false));
++ // CraftBukkit - this.world.dimension
++ this.a.getPlayerList().sendPacketNearby(entityhuman, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 64.0D, this.world.dimension, new PacketPlayOutWorldEvent(i, blockposition, j, false));
+ }
+
+ public void a(int i, BlockPosition blockposition, int j) {
diff --git a/nms-patches/WorldMap.patch b/nms-patches/WorldMap.patch
new file mode 100644
index 00000000..86ba2aa6
--- /dev/null
+++ b/nms-patches/WorldMap.patch
@@ -0,0 +1,95 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldMap.java 2014-11-27 08:59:46.941420790 +1100
++++ src/main/java/net/minecraft/server/WorldMap.java 2014-11-27 08:42:10.152850911 +1100
+@@ -6,6 +6,14 @@
+ import java.util.List;
+ import java.util.Map;
+
++// CraftBukkit start
++import java.util.UUID;
++
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.map.CraftMapView;
++// CraftBukkit end
++
+ public class WorldMap extends PersistentBase {
+
+ public int centerX;
+@@ -16,9 +24,19 @@
+ public List g = Lists.newArrayList();
+ private Map i = Maps.newHashMap();
+ public Map decorations = Maps.newLinkedHashMap();
++
++ // CraftBukkit start
++ public final CraftMapView mapView;
++ private CraftServer server;
++ private UUID uniqueId = null;
++ // CraftBukkit end
+
+ public WorldMap(String s) {
+ super(s);
++ // CraftBukkit start
++ mapView = new CraftMapView(this);
++ server = (CraftServer) org.bukkit.Bukkit.getServer();
++ // CraftBukkit end
+ }
+
+ public void a(double d0, double d1, int i) {
+@@ -31,7 +49,30 @@
+ }
+
+ public void a(NBTTagCompound nbttagcompound) {
+- this.map = nbttagcompound.getByte("dimension");
++ // CraftBukkit start
++ byte dimension = nbttagcompound.getByte("dimension");
++
++ if (dimension >= 10) {
++ long least = nbttagcompound.getLong("UUIDLeast");
++ long most = nbttagcompound.getLong("UUIDMost");
++
++ if (least != 0L && most != 0L) {
++ this.uniqueId = new UUID(most, least);
++
++ CraftWorld world = (CraftWorld) server.getWorld(this.uniqueId);
++ // Check if the stored world details are correct.
++ if (world == null) {
++ /* All Maps which do not have their valid world loaded are set to a dimension which hopefully won't be reached.
++ This is to prevent them being corrupted with the wrong map data. */
++ dimension = 127;
++ } else {
++ dimension = (byte) world.getHandle().dimension;
++ }
++ }
++ }
++
++ this.map = dimension;
++ // CraftBukkit end
+ this.centerX = nbttagcompound.getInt("xCenter");
+ this.centerZ = nbttagcompound.getInt("zCenter");
+ this.scale = nbttagcompound.getByte("scale");
+@@ -66,6 +107,25 @@
+ }
+
+ public void b(NBTTagCompound nbttagcompound) {
++ // CraftBukkit start
++ if (this.map >= 10) {
++ if (this.uniqueId == null) {
++ for (org.bukkit.World world : server.getWorlds()) {
++ CraftWorld cWorld = (CraftWorld) world;
++ if (cWorld.getHandle().dimension == this.map) {
++ this.uniqueId = cWorld.getUID();
++ break;
++ }
++ }
++ }
++ /* Perform a second check to see if a matching world was found, this is a necessary
++ change incase Maps are forcefully unlinked from a World and lack a UID.*/
++ if (this.uniqueId != null) {
++ nbttagcompound.setLong("UUIDLeast", this.uniqueId.getLeastSignificantBits());
++ nbttagcompound.setLong("UUIDMost", this.uniqueId.getMostSignificantBits());
++ }
++ }
++ // CraftBukkit end
+ nbttagcompound.setByte("dimension", this.map);
+ nbttagcompound.setInt("xCenter", this.centerX);
+ nbttagcompound.setInt("zCenter", this.centerZ);
diff --git a/nms-patches/WorldMapHumanTracker.patch b/nms-patches/WorldMapHumanTracker.patch
new file mode 100644
index 00000000..627bbbfd
--- /dev/null
+++ b/nms-patches/WorldMapHumanTracker.patch
@@ -0,0 +1,31 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldMapHumanTracker.java 2014-11-27 08:59:46.937420807 +1100
++++ src/main/java/net/minecraft/server/WorldMapHumanTracker.java 2014-11-27 08:42:10.168850880 +1100
+@@ -23,12 +23,26 @@
+ }
+
+ public Packet a(ItemStack itemstack) {
++ // CraftBukkit start
++ org.bukkit.craftbukkit.map.RenderData render = this.worldMap.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.trackee.getBukkitEntity()); // CraftBukkit
++
++ java.util.Collection<MapIcon> icons = new java.util.ArrayList<MapIcon>();
++
++ for ( org.bukkit.map.MapCursor cursor : render.cursors) {
++
++ if (cursor.isVisible()) {
++ icons.add(new MapIcon(cursor.getRawType(), cursor.getX(), cursor.getY(), cursor.getDirection()));
++ }
++ }
++
++
+ if (this.d) {
+ this.d = false;
+- return new PacketPlayOutMap(itemstack.getData(), this.worldMap.scale, this.worldMap.decorations.values(), this.worldMap.colors, this.e, this.f, this.g + 1 - this.e, this.h + 1 - this.f);
++ return new PacketPlayOutMap(itemstack.getData(), this.worldMap.scale, icons, render.buffer, this.e, this.f, this.g + 1 - this.e, this.h + 1 - this.f);
+ } else {
+- return this.i++ % 5 == 0 ? new PacketPlayOutMap(itemstack.getData(), this.worldMap.scale, this.worldMap.decorations.values(), this.worldMap.colors, 0, 0, 0, 0) : null;
++ return this.i++ % 5 == 0 ? new PacketPlayOutMap(itemstack.getData(), this.worldMap.scale, icons, render.buffer, 0, 0, 0, 0) : null;
+ }
++ // CraftBukkit end
+ }
+
+ public void a(int i, int j) {
diff --git a/nms-patches/WorldNBTStorage.patch b/nms-patches/WorldNBTStorage.patch
new file mode 100644
index 00000000..503af702
--- /dev/null
+++ b/nms-patches/WorldNBTStorage.patch
@@ -0,0 +1,123 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldNBTStorage.java 2014-11-27 08:59:46.941420790 +1100
++++ src/main/java/net/minecraft/server/WorldNBTStorage.java 2014-11-27 08:42:10.156850903 +1100
+@@ -11,6 +11,12 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.util.UUID;
++
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++// CraftBukkit end
++
+ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
+
+ private static final Logger a = LogManager.getLogger();
+@@ -19,6 +25,7 @@
+ private final File dataDir;
+ private final long sessionId = MinecraftServer.ax();
+ private final String f;
++ private UUID uuid = null; // CraftBukkit
+
+ public WorldNBTStorage(File file, String s, boolean flag) {
+ this.baseDir = new File(file, s);
+@@ -55,7 +62,7 @@
+ return this.baseDir;
+ }
+
+- public void checkSession() {
++ public void checkSession() throws ExceptionWorldConflict { // CraftBukkit - throws ExceptionWorldConflict
+ try {
+ File file = new File(this.baseDir, "session.lock");
+ DataInputStream datainputstream = new DataInputStream(new FileInputStream(file));
+@@ -202,12 +209,39 @@
+ }
+
+ if (nbttagcompound != null) {
++ // CraftBukkit start
++ if (entityhuman instanceof EntityPlayer) {
++ CraftPlayer player = (CraftPlayer) entityhuman.getBukkitEntity();
++ // Only update first played if it is older than the one we have
++ long modified = new File(this.playerDir, entityhuman.getUniqueID().toString() + ".dat").lastModified();
++ if (modified < player.getFirstPlayed()) {
++ player.setFirstPlayed(modified);
++ }
++ }
++ // CraftBukkit end
++
+ entityhuman.f(nbttagcompound);
+ }
+
+ return nbttagcompound;
+ }
+
++ // CraftBukkit start
++ public NBTTagCompound getPlayerData(String s) {
++ try {
++ File file1 = new File(this.playerDir, s + ".dat");
++
++ if (file1.exists()) {
++ return NBTCompressedStreamTools.a((InputStream) (new FileInputStream(file1)));
++ }
++ } catch (Exception exception) {
++ a.warn("Failed to load player data for " + s);
++ }
++
++ return null;
++ }
++ // CraftBukkit end
++
+ public IPlayerFileData getPlayerFileData() {
+ return this;
+ }
+@@ -237,4 +271,50 @@
+ public String g() {
+ return this.f;
+ }
++
++ // CraftBukkit start
++ public UUID getUUID() {
++ if (uuid != null) return uuid;
++ File file1 = new File(this.baseDir, "uid.dat");
++ if (file1.exists()) {
++ DataInputStream dis = null;
++ try {
++ dis = new DataInputStream(new FileInputStream(file1));
++ return uuid = new UUID(dis.readLong(), dis.readLong());
++ } catch (IOException ex) {
++ a.warn("Failed to read " + file1 + ", generating new random UUID", ex);
++ } finally {
++ if (dis != null) {
++ try {
++ dis.close();
++ } catch (IOException ex) {
++ // NOOP
++ }
++ }
++ }
++ }
++ uuid = UUID.randomUUID();
++ DataOutputStream dos = null;
++ try {
++ dos = new DataOutputStream(new FileOutputStream(file1));
++ dos.writeLong(uuid.getMostSignificantBits());
++ dos.writeLong(uuid.getLeastSignificantBits());
++ } catch (IOException ex) {
++ a.warn("Failed to write " + file1, ex);
++ } finally {
++ if (dos != null) {
++ try {
++ dos.close();
++ } catch (IOException ex) {
++ // NOOP
++ }
++ }
++ }
++ return uuid;
++ }
++
++ public File getPlayerDir() {
++ return playerDir;
++ }
++ // CraftBukkit end
+ }
diff --git a/nms-patches/WorldServer.patch b/nms-patches/WorldServer.patch
new file mode 100644
index 00000000..b1ad5607
--- /dev/null
+++ b/nms-patches/WorldServer.patch
@@ -0,0 +1,550 @@
+--- ../work/decompile-bb26c12b/net/minecraft/server/WorldServer.java 2014-11-27 08:59:46.945420772 +1100
++++ src/main/java/net/minecraft/server/WorldServer.java 2014-11-27 08:42:10.140850934 +1100
+@@ -16,6 +16,20 @@
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++// CraftBukkit start
++import java.util.*;
++import java.util.logging.Level;
++
++import org.bukkit.WeatherType;
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.util.LongHash;
++
++import org.bukkit.event.block.BlockFormEvent;
++import org.bukkit.event.weather.LightningStrikeEvent;
++import org.bukkit.event.weather.ThunderChangeEvent;
++import org.bukkit.event.weather.WeatherChangeEvent;
++// CraftBukkit end
++
+ public class WorldServer extends World implements IAsyncTaskHandler {
+
+ private static final Logger a = LogManager.getLogger();
+@@ -37,14 +51,21 @@
+ private static final List U = Lists.newArrayList(new StructurePieceTreasure[] { new StructurePieceTreasure(Items.STICK, 0, 1, 3, 10), new StructurePieceTreasure(Item.getItemOf(Blocks.PLANKS), 0, 1, 3, 10), new StructurePieceTreasure(Item.getItemOf(Blocks.LOG), 0, 1, 3, 10), new StructurePieceTreasure(Items.STONE_AXE, 0, 1, 1, 3), new StructurePieceTreasure(Items.WOODEN_AXE, 0, 1, 1, 5), new StructurePieceTreasure(Items.STONE_PICKAXE, 0, 1, 1, 3), new StructurePieceTreasure(Items.WOODEN_PICKAXE, 0, 1, 1, 5), new StructurePieceTreasure(Items.APPLE, 0, 2, 3, 5), new StructurePieceTreasure(Items.BREAD, 0, 2, 3, 3), new StructurePieceTreasure(Item.getItemOf(Blocks.LOG2), 0, 1, 3, 10)});
+ private List V = Lists.newArrayList();
+
+- public WorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, WorldData worlddata, int i, MethodProfiler methodprofiler) {
+- super(idatamanager, worlddata, WorldProvider.byDimension(i), methodprofiler, false);
++ // CraftBukkit start
++ public final int dimension;
++
++ // Add env and gen to constructor
++ public WorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, WorldData worlddata, int i, MethodProfiler methodprofiler, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
++ super(idatamanager, worlddata, WorldProvider.byDimension(env.getId()), methodprofiler, false, gen, env);
++ this.dimension = i;
++ this.pvpMode = minecraftserver.getPVP();
++ // CraftBukkit end
+ this.server = minecraftserver;
+ this.tracker = new EntityTracker(this);
+ this.manager = new PlayerChunkMap(this);
+ this.worldProvider.a(this);
+ this.chunkProvider = this.k();
+- this.Q = new PortalTravelAgent(this);
++ this.Q = new org.bukkit.craftbukkit.CraftTravelAgent(this); // CraftBukkit
+ this.B();
+ this.C();
+ this.af().a(minecraftserver.aG());
+@@ -86,6 +107,89 @@
+
+ return this;
+ }
++
++ // CraftBukkit start
++ @Override
++ public TileEntity getTileEntity(BlockPosition pos) {
++ TileEntity result = super.getTileEntity(pos);
++ Block type = getType(pos).getBlock();
++
++ if (type == Blocks.CHEST) {
++ if (!(result instanceof TileEntityChest)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.FURNACE) {
++ if (!(result instanceof TileEntityFurnace)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.DROPPER) {
++ if (!(result instanceof TileEntityDropper)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.DISPENSER) {
++ if (!(result instanceof TileEntityDispenser)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.JUKEBOX) {
++ if (!(result instanceof TileEntityRecordPlayer)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.NOTEBLOCK) {
++ if (!(result instanceof TileEntityNote)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.MOB_SPAWNER) {
++ if (!(result instanceof TileEntityMobSpawner)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if ((type == Blocks.STANDING_SIGN) || (type == Blocks.WALL_SIGN)) {
++ if (!(result instanceof TileEntitySign)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.ENDER_CHEST) {
++ if (!(result instanceof TileEntityEnderChest)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.BREWING_STAND) {
++ if (!(result instanceof TileEntityBrewingStand)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.BEACON) {
++ if (!(result instanceof TileEntityBeacon)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ } else if (type == Blocks.HOPPER) {
++ if (!(result instanceof TileEntityHopper)) {
++ result = fixTileEntity(pos, type, result);
++ }
++ }
++
++ return result;
++ }
++
++ private TileEntity fixTileEntity(BlockPosition pos, Block type, TileEntity found) {
++ this.getServer().getLogger().log(Level.SEVERE, "Block at {0},{1},{2} is {3} but has {4}" + ". "
++ + "Bukkit will attempt to fix this, but there may be additional damage that we cannot recover.", new Object[]{pos.getX(), pos.getY(), pos.getZ(), org.bukkit.Material.getMaterial(Block.getId(type)).toString(), found});
++
++ if (type instanceof IContainer) {
++ TileEntity replacement = ((IContainer) type).a(this, type.toLegacyData(this.getType(pos)));
++ replacement.world = this;
++ this.setTileEntity(pos, replacement);
++ return replacement;
++ } else {
++ this.getServer().getLogger().severe("Don't know how to fix for this type... Can't do anything! :(");
++ return found;
++ }
++ }
++
++ private boolean canSpawn(int x, int z) {
++ if (this.generator != null) {
++ return this.generator.canSpawn(this.getWorld(), x, z);
++ } else {
++ return this.worldProvider.canSpawn(x, z);
++ }
++ }
++ // CraftBukkit end
+
+ public void doTick() {
+ super.doTick();
+@@ -105,8 +209,11 @@
+ }
+
+ this.methodProfiler.a("mobSpawner");
+- if (this.getGameRules().getBoolean("doMobSpawning") && this.worldData.getType() != WorldType.DEBUG_ALL_BLOCK_STATES) {
+- this.R.a(this, this.allowMonsters, this.allowAnimals, this.worldData.getTime() % 400L == 0L);
++ // 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.worldData.getType() != WorldType.DEBUG_ALL_BLOCK_STATES && (this.allowMonsters || this.allowAnimals) && (this instanceof WorldServer && this.players.size() > 0)) {
++ this.R.a(this, this.allowMonsters && (this.ticksPerMonsterSpawns != 0 && time % this.ticksPerMonsterSpawns == 0L), this.allowAnimals && (this.ticksPerAnimalSpawns != 0 && time % this.ticksPerAnimalSpawns == 0L), this.worldData.getTime() % 400L == 0L);
++ // CraftBukkit end
+ }
+
+ this.methodProfiler.c("chunkSource");
+@@ -135,6 +242,8 @@
+ this.Q.a(this.getTime());
+ this.methodProfiler.b();
+ this.ak();
++
++ this.getWorld().processChunkGC(); // CraftBukkit
+ }
+
+ public BiomeMeta a(EnumCreatureType enumcreaturetype, BlockPosition blockposition) {
+@@ -161,7 +270,7 @@
+
+ if (entityhuman.v()) {
+ ++i;
+- } else if (entityhuman.isSleeping()) {
++ } else if (entityhuman.isSleeping() || entityhuman.fauxSleeping) { // CraftBukkit
+ ++j;
+ }
+ }
+@@ -187,26 +296,45 @@
+ }
+
+ private void ag() {
+- this.worldData.setWeatherDuration(0);
+- this.worldData.setStorm(false);
+- this.worldData.setThunderDuration(0);
+- this.worldData.setThundering(false);
++ // CraftBukkit start
++ WeatherChangeEvent weather = new WeatherChangeEvent(this.getWorld(), false);
++ this.getServer().getPluginManager().callEvent(weather);
++
++ ThunderChangeEvent thunder = new ThunderChangeEvent(this.getWorld(), false);
++ this.getServer().getPluginManager().callEvent(thunder);
++ if (!weather.isCancelled()) {
++ this.worldData.setWeatherDuration(0);
++ this.worldData.setStorm(false);
++ }
++ if (!thunder.isCancelled()) {
++ this.worldData.setThunderDuration(0);
++ this.worldData.setThundering(false);
++ }
++ // CraftBukkit end
+ }
+
+ public boolean everyoneDeeplySleeping() {
+ if (this.O && !this.isStatic) {
+ Iterator iterator = this.players.iterator();
+
++ // CraftBukkit - This allows us to assume that some people are in bed but not really, allowing time to pass in spite of AFKers
++ boolean foundActualSleepers = false;
++
+ EntityHuman entityhuman;
+
+ do {
+ if (!iterator.hasNext()) {
+- return true;
++ return foundActualSleepers;
+ }
+
+ entityhuman = (EntityHuman) iterator.next();
+- } while (!entityhuman.v() && entityhuman.isDeeplySleeping());
+-
++ // CraftBukkit start
++ if (entityhuman.isDeeplySleeping()) {
++ foundActualSleepers = true;
++ }
++ } while (!entityhuman.v() && (entityhuman.isDeeplySleeping() || entityhuman.fauxSleeping));
++ // CraftBukkit end
++
+ return false;
+ } else {
+ return false;
+@@ -227,15 +355,22 @@
+ } else {
+ int i = 0;
+ int j = 0;
+-
+- for (Iterator iterator1 = this.chunkTickList.iterator(); iterator1.hasNext(); this.methodProfiler.b()) {
+- ChunkCoordIntPair chunkcoordintpair1 = (ChunkCoordIntPair) iterator1.next();
+- int k = chunkcoordintpair1.x * 16;
+- int l = chunkcoordintpair1.z * 16;
+-
++
++ // CraftBukkit start
++ //for (Iterator iterator1 = this.chunkTickList.iterator(); iterator1.hasNext(); this.methodProfiler.b()) {
++ // ChunkCoordIntPair chunkcoordintpair1 = (ChunkCoordIntPair) iterator1.next();
++ // int k = chunkcoordintpair1.x * 16;
++ // int l = chunkcoordintpair1.z * 16;
++ for (long chunkCoord : chunkTickList.popAll()) {
++ int chunkX = LongHash.msw(chunkCoord);
++ int chunkZ = LongHash.lsw(chunkCoord);
++ int k = chunkX * 16;
++ int l = chunkZ * 16;
++
+ this.methodProfiler.a("getChunk");
+- Chunk chunk = this.getChunkAt(chunkcoordintpair1.x, chunkcoordintpair1.z);
+-
++ Chunk chunk = this.getChunkAt(chunkX, chunkZ);
++ // CraftBukkit end
++
+ this.a(k, l, chunk);
+ this.methodProfiler.c("tickChunk");
+ chunk.b(false);
+@@ -260,11 +395,29 @@
+ BlockPosition blockposition1 = blockposition.down();
+
+ if (this.w(blockposition1)) {
+- this.setTypeUpdate(blockposition1, Blocks.ICE.getBlockData());
++ // CraftBukkit start
++ BlockState blockState = this.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()).getState();
++ blockState.setTypeId(Block.getId(Blocks.ICE));
++
++ BlockFormEvent iceBlockForm = new BlockFormEvent(blockState.getBlock(), blockState);
++ this.getServer().getPluginManager().callEvent(iceBlockForm);
++ if (!iceBlockForm.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+
+ if (this.S() && this.f(blockposition, true)) {
+- this.setTypeUpdate(blockposition, Blocks.SNOW_LAYER.getBlockData());
++ // CraftBukkit start
++ BlockState blockState = this.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getState();
++ blockState.setTypeId(Block.getId(Blocks.SNOW_LAYER));
++
++ BlockFormEvent snow = new BlockFormEvent(blockState.getBlock(), blockState);
++ this.getServer().getPluginManager().callEvent(snow);
++ if (!snow.isCancelled()) {
++ blockState.update(true);
++ }
++ // CraftBukkit end
+ }
+
+ if (this.S() && this.getBiome(blockposition1).e()) {
+@@ -376,7 +529,7 @@
+ }
+
+ public void tickEntities() {
+- if (this.players.isEmpty()) {
++ if (false && this.players.isEmpty()) { // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
+ if (this.emptyTime++ >= 1200) {
+ return;
+ }
+@@ -401,7 +554,13 @@
+ throw new IllegalStateException("TickNextTick list out of synch");
+ } else {
+ if (i > 1000) {
+- i = 1000;
++ // CraftBukkit start - If the server has too much to process over time, try to alleviate that
++ if (i > 20 * 1000) {
++ i = i / 20;
++ } else {
++ i = 1000;
++ }
++ // CraftBukkit end
+ }
+
+ this.methodProfiler.a("cleaning");
+@@ -501,6 +660,7 @@
+ return arraylist;
+ }
+
++ /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed
+ public void entityJoinedWorld(Entity entity, boolean flag) {
+ if (!this.getSpawnAnimals() && (entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal)) {
+ entity.die();
+@@ -511,7 +671,9 @@
+ }
+
+ super.entityJoinedWorld(entity, flag);
++
+ }
++ // CraftBukkit end */
+
+ private boolean getSpawnNPCs() {
+ return this.server.getSpawnNPCs();
+@@ -523,14 +685,44 @@
+
+ protected IChunkProvider k() {
+ IChunkLoader ichunkloader = this.dataManager.createChunkLoader(this.worldProvider);
++
++ // CraftBukkit start
++ org.bukkit.craftbukkit.generator.InternalChunkGenerator gen;
++
++ if (this.generator != null) {
++ gen = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, this.getSeed(), this.generator);
++ } else if (this.worldProvider instanceof WorldProviderHell) {
++ gen = new org.bukkit.craftbukkit.generator.NetherChunkGenerator(this, this.getSeed());
++ } else if (this.worldProvider instanceof WorldProviderTheEnd) {
++ gen = new org.bukkit.craftbukkit.generator.SkyLandsChunkGenerator(this, this.getSeed());
++ } else {
++ gen = new org.bukkit.craftbukkit.generator.NormalChunkGenerator(this, this.getSeed());
++ }
+
+- this.chunkProviderServer = new ChunkProviderServer(this, ichunkloader, this.worldProvider.getChunkProvider());
++ this.chunkProviderServer = new ChunkProviderServer(this, ichunkloader, gen);
++ // CraftBukkit end
+ return this.chunkProviderServer;
+ }
+
+ public List getTileEntities(int i, int j, int k, int l, int i1, int j1) {
+ ArrayList arraylist = Lists.newArrayList();
+-
++
++ // CraftBukkit start - Get tile entities from chunks instead of world
++ for (int chunkX = (i >> 4); chunkX <= ((l - 1) >> 4); chunkX++) {
++ for (int chunkZ = (k >> 4); chunkZ <= ((j1 - 1) >> 4); chunkZ++) {
++ Chunk chunk = getChunkAt(chunkX, chunkZ);
++ if (chunk == null) {
++ continue;
++ }
++ for (Object te : chunk.tileEntities.values()) {
++ TileEntity tileentity = (TileEntity) te;
++ if ((tileentity.position.getX() >= i) && (tileentity.position.getY() >= j) && (tileentity.position.getZ() >= k) && (tileentity.position.getX() < l) && (tileentity.position.getY() < i1) && (tileentity.position.getZ() < j1)) {
++ arraylist.add(tileentity);
++ }
++ }
++ }
++ }
++ /*
+ for (int k1 = 0; k1 < this.h.size(); ++k1) {
+ TileEntity tileentity = (TileEntity) this.h.get(k1);
+ BlockPosition blockposition = tileentity.getPosition();
+@@ -539,6 +731,8 @@
+ arraylist.add(tileentity);
+ }
+ }
++ */
++ // CraftBukkit end
+
+ return arraylist;
+ }
+@@ -601,6 +795,23 @@
+ int i = 0;
+ int j = this.worldProvider.getSeaLevel();
+ int k = 0;
++
++ // CraftBukkit start
++ if (this.generator != null) {
++ Random rand = new Random(this.getSeed());
++ org.bukkit.Location spawn = this.generator.getFixedSpawnLocation(((WorldServer) this).getWorld(), rand);
++
++ if (spawn != null) {
++ if (spawn.getWorld() != ((WorldServer) this).getWorld()) {
++ throw new IllegalStateException("Cannot set spawn point for " + this.worldData.getName() + " to be in another world (" + spawn.getWorld().getName() + ")");
++ } else {
++ this.worldData.setSpawn(new BlockPosition(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()));
++ this.isLoading = false;
++ return;
++ }
++ }
++ }
++ // CraftBukkit end
+
+ if (blockposition != null) {
+ i = blockposition.getX();
+@@ -611,7 +822,7 @@
+
+ int l = 0;
+
+- while (!this.worldProvider.canSpawn(i, k)) {
++ while (!this.canSpawn(i, k)) { // CraftBukkit - use our own canSpawn
+ i += random.nextInt(64) - random.nextInt(64);
+ k += random.nextInt(64) - random.nextInt(64);
+ ++l;
+@@ -648,7 +859,7 @@
+ return this.worldProvider.h();
+ }
+
+- public void save(boolean flag, IProgressUpdate iprogressupdate) {
++ public void save(boolean flag, IProgressUpdate iprogressupdate) throws ExceptionWorldConflict { // CraftBukkit - added throws
+ if (this.chunkProvider.canSave()) {
+ if (iprogressupdate != null) {
+ iprogressupdate.a("Saving level");
+@@ -660,7 +871,8 @@
+ }
+
+ this.chunkProvider.saveChunks(flag, iprogressupdate);
+- List list = this.chunkProviderServer.a();
++ // CraftBukkit - ArrayList -> Collection
++ Collection list = this.chunkProviderServer.a();
+ Iterator iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+@@ -680,7 +892,7 @@
+ }
+ }
+
+- protected void a() {
++ protected void a() throws ExceptionWorldConflict { // CraftBukkit - added throws
+ this.checkSession();
+ this.worldData.a(this.af().h());
+ this.worldData.d(this.af().f());
+@@ -692,7 +904,11 @@
+ this.worldData.b(this.af().j());
+ this.worldData.e(this.af().i());
+ this.dataManager.saveWorldData(this.worldData, this.server.getPlayerList().u());
+- this.worldMaps.a();
++ // CraftBukkit start - save worldMaps once, rather than once per shared world
++ if (!(this instanceof SecondaryWorldServer)) {
++ this.worldMaps.a();
++ }
++ // CraftBukkit end
+ }
+
+ protected void a(Entity entity) {
+@@ -724,8 +940,16 @@
+ }
+
+ public boolean strikeLightning(Entity entity) {
++ // CraftBukkit start
++ LightningStrikeEvent lightning = new LightningStrikeEvent(this.getWorld(), (org.bukkit.entity.LightningStrike) entity.getBukkitEntity());
++ this.getServer().getPluginManager().callEvent(lightning);
++
++ if (lightning.isCancelled()) {
++ return false;
++ }
+ if (super.strikeLightning(entity)) {
+- this.server.getPlayerList().sendPacketNearby(entity.locX, entity.locY, entity.locZ, 512.0D, this.worldProvider.getDimension(), new PacketPlayOutSpawnEntityWeather(entity));
++ this.server.getPlayerList().sendPacketNearby(entity.locX, entity.locY, entity.locZ, 512.0D, this.dimension, new PacketPlayOutSpawnEntityWeather(entity));
++ // CraftBukkit end
+ return true;
+ } else {
+ return false;
+@@ -737,10 +961,20 @@
+ }
+
+ public Explosion createExplosion(Entity entity, double d0, double d1, double d2, float f, boolean flag, boolean flag1) {
++ // CraftBukkit start
++ Explosion explosion = super.createExplosion(entity, d0, d1, d2, f, flag, flag1);
++
++ if (explosion.wasCanceled) {
++ return explosion;
++ }
++
++ /* Remove
+ Explosion explosion = new Explosion(this, entity, d0, d1, d2, f, flag, flag1);
+
+ explosion.a();
+ explosion.a(false);
++ */
++ // CraftBukkit end - TODO: Check if explosions are still properly implemented
+ if (!flag1) {
+ explosion.clearBlocks();
+ }
+@@ -786,7 +1020,8 @@
+ BlockActionData blockactiondata = (BlockActionData) iterator.next();
+
+ if (this.a(blockactiondata)) {
+- this.server.getPlayerList().sendPacketNearby((double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this.worldProvider.getDimension(), new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c()));
++ // CraftBukkit - this.worldProvider.dimension -> this.dimension
++ this.server.getPlayerList().sendPacketNearby((double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this.dimension, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c()));
+ }
+ }
+
+@@ -809,6 +1044,7 @@
+ boolean flag = this.S();
+
+ super.p();
++ /* CraftBukkit start
+ if (this.o != this.p) {
+ this.server.getPlayerList().a(new PacketPlayOutGameStateChange(7, this.p), this.worldProvider.getDimension());
+ }
+@@ -827,6 +1063,16 @@
+ this.server.getPlayerList().sendAll(new PacketPlayOutGameStateChange(7, this.p));
+ this.server.getPlayerList().sendAll(new PacketPlayOutGameStateChange(8, this.r));
+ }
++ // */
++ if (flag != this.S()) {
++ // Only send weather packets to those affected
++ for (int i = 0; i < this.players.size(); ++i) {
++ if (((EntityPlayer) this.players.get(i)).world == this) {
++ ((EntityPlayer) this.players.get(i)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false);
++ }
++ }
++ // CraftBukkit end
++ }
+
+ }
+
+@@ -855,10 +1101,17 @@
+ }
+
+ public void a(EnumParticle enumparticle, boolean flag, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, int... aint) {
++ // CraftBukkit - visibility api support
++ sendParticles(null, enumparticle, flag, d0, d1, d2, i, d3, d4, d5, d6, aint);
++ }
++
++ public void sendParticles(EntityPlayer sender, EnumParticle enumparticle, boolean flag, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, int... aint) {
++ // CraftBukkit end
+ PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(enumparticle, flag, (float) d0, (float) d1, (float) d2, (float) d3, (float) d4, (float) d5, (float) d6, i, aint);
+
+ for (int j = 0; j < this.players.size(); ++j) {
+ EntityPlayer entityplayer = (EntityPlayer) this.players.get(j);
++ if (sender != null && !sender.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) continue; // CraftBukkit
+ BlockPosition blockposition = entityplayer.getChunkCoordinates();
+ double d7 = blockposition.c(d0, d1, d2);
+