diff --git a/TacoSpigot-API/src/main/java/org/bukkit/Bukkit.java b/TacoSpigot-API/src/main/java/org/bukkit/Bukkit.java index 9b47a61..933607e 100644 --- a/TacoSpigot-API/src/main/java/org/bukkit/Bukkit.java +++ b/TacoSpigot-API/src/main/java/org/bukkit/Bukkit.java @@ -29,6 +29,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.map.MapView; import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; @@ -1162,6 +1163,14 @@ public final class Bukkit { } // Paper end + public static void postToMainThread(Plugin plugin, boolean priority, Runnable task) { + server.postToMainThread(plugin, priority, task); + } + + public static boolean runOnMainThread(Plugin plugin, boolean priority, Runnable task) { + return server.runOnMainThread(plugin, priority, task); + } + public static Server.Spigot spigot() { return server.spigot(); diff --git a/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java b/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java index 436a518..f0de9cf 100644 --- a/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java +++ b/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java @@ -4,6 +4,8 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.entity.Entity; +import java.util.Set; + /** * Represents a chunk of blocks */ @@ -40,6 +42,14 @@ public interface Chunk { */ Block getBlock(int x, int y, int z); + /** + * Get all blocks in this chunk that are made of the given {@link Material} + * + * @param material type of block to search for + * @return all blocks found + */ + Set getBlocks(Material material); + /** * Capture thread-safe read-only snapshot of chunk data * diff --git a/TacoSpigot-API/src/main/java/org/bukkit/Server.java b/TacoSpigot-API/src/main/java/org/bukkit/Server.java index 4ecf2a5..bb2e2d1 100644 --- a/TacoSpigot-API/src/main/java/org/bukkit/Server.java +++ b/TacoSpigot-API/src/main/java/org/bukkit/Server.java @@ -29,6 +29,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.map.MapView; import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; @@ -950,6 +951,28 @@ public interface Server extends PluginMessageRecipient { CommandMap getCommandMap(); // Paper end + /** + * Post the given task to the main thread queue. This is the queue used to handle + * incoming packets (NOT the {@link BukkitScheduler} queue). + * + * The priority flag determines which end of the queue the task is posted to, and + * therefor whether it will run before (true) or after (false) any other tasks + * that are currently queued. + * + * Since incoming packets are also handled through this queue, + */ + void postToMainThread(Plugin plugin, boolean priority, Runnable task); + + /** + * If called on the main thread, run the given task immediately and return when + * the task completes. If run from any other thread, this is the same as calling + * {@link #postToMainThread(Plugin, boolean, Runnable)}. + * + * @return true if the task ran synchronously, + * false if it was added to the main thread queue + */ + boolean runOnMainThread(Plugin plugin, boolean priority, Runnable task); + class Spigot { @Deprecated diff --git a/TacoSpigot-API/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/TacoSpigot-API/src/main/java/org/bukkit/command/defaults/VersionCommand.java index 23e1448..ff02ff6 100644 --- a/TacoSpigot-API/src/main/java/org/bukkit/command/defaults/VersionCommand.java +++ b/TacoSpigot-API/src/main/java/org/bukkit/command/defaults/VersionCommand.java @@ -32,7 +32,7 @@ public class VersionCommand extends BukkitCommand { if (args.length == 0) { String[] message = new String[] { - "§3This server is running §b§leSpigot§3 by the ElevateMC development team. Version §b§l1.8.8", + "§3This server is running §b§leSpigot§3 by the ElevateMC development team. Version §b§l1.8.9", }; sender.sendMessage(message); diff --git a/TacoSpigot-API/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/TacoSpigot-API/src/main/java/org/bukkit/metadata/MetadataStoreBase.java index 738c8a9..4e08f89 100644 --- a/TacoSpigot-API/src/main/java/org/bukkit/metadata/MetadataStoreBase.java +++ b/TacoSpigot-API/src/main/java/org/bukkit/metadata/MetadataStoreBase.java @@ -119,6 +119,26 @@ public abstract class MetadataStoreBase { } } + /** + * Removes all metadata in the metadata store that originates from the + * given plugin. + * + * @param owningPlugin the plugin requesting the invalidation. + * @throws IllegalArgumentException If plugin is null + */ + public void removeAll(Plugin owningPlugin) { + Validate.notNull(owningPlugin, "Plugin cannot be null"); + for (Iterator> iterator = metadataMap.values().iterator(); iterator.hasNext(); ) { + Map values = iterator.next(); + if (values.containsKey(owningPlugin)) { + values.remove(owningPlugin); + } + if (values.isEmpty()) { + iterator.remove(); + } + } + } + /** * Creates a unique name for the object receiving metadata by combining * unique data from the subject with a metadataKey. diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java new file mode 100644 index 0000000..317e9d2 --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java @@ -0,0 +1,36 @@ +package org.github.paperspigot.event; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang.Validate; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.github.paperspigot.exception.ServerException; + +/** + * Called whenever an exception is thrown in a recoverable section of the server. + */ +public class ServerExceptionEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private ServerException exception; + + public ServerExceptionEvent (ServerException exception) { + this.exception = Preconditions.checkNotNull(exception, "exception"); + } + + /** + * Gets the wrapped exception that was thrown. + * @return Exception thrown + */ + public ServerException getException() { + return exception; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerCommandException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerCommandException.java new file mode 100644 index 0000000..aae647a --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerCommandException.java @@ -0,0 +1,64 @@ +package org.github.paperspigot.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a command throws an exception + */ +public class ServerCommandException extends ServerException { + + private final Command command; + private final CommandSender commandSender; + private final String[] arguments; + + public ServerCommandException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + public ServerCommandException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + protected ServerCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + /** + * Gets the command which threw the exception + * + * @return exception throwing command + */ + public Command getCommand() { + return command; + } + + /** + * Gets the command sender which executed the command request + * + * @return command sender of exception thrown command request + */ + public CommandSender getCommandSender() { + return commandSender; + } + + /** + * Gets the arguments which threw the exception for the command + * + * @return arguments of exception thrown command request + */ + public String[] getArguments() { + return arguments; + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerEventException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerEventException.java new file mode 100644 index 0000000..d99cab8 --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerEventException.java @@ -0,0 +1,52 @@ +package org.github.paperspigot.exception; + +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Exception thrown when a server event listener throws an exception + */ +public class ServerEventException extends ServerPluginException { + + private final Listener listener; + private final Event event; + + public ServerEventException(String message, Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + public ServerEventException(Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + protected ServerEventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + /** + * Gets the listener which threw the exception + * + * @return event listener + */ + public Listener getListener() { + return listener; + } + + /** + * Gets the event which caused the exception + * + * @return event + */ + public Event getEvent() { + return event; + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java new file mode 100644 index 0000000..f7aad05 --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerException.java @@ -0,0 +1,23 @@ +package org.github.paperspigot.exception; + +/** + * Wrapper exception for all exceptions that are thrown by the server. + */ +public class ServerException extends Exception { + + public ServerException(String message) { + super(message); + } + + public ServerException(String message, Throwable cause) { + super(message, cause); + } + + public ServerException(Throwable cause) { + super(cause); + } + + protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerInternalException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerInternalException.java new file mode 100644 index 0000000..17ad4cb --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerInternalException.java @@ -0,0 +1,35 @@ +package org.github.paperspigot.exception; + +import org.bukkit.Bukkit; +import org.bukkit.entity.ThrownExpBottle; +import org.github.paperspigot.event.ServerExceptionEvent; + +/** + * Thrown when the internal server throws a recoverable exception. + */ +public class ServerInternalException extends ServerException { + + public ServerInternalException(String message) { + super(message); + } + + public ServerInternalException(String message, Throwable cause) { + super(message, cause); + } + + public ServerInternalException(Throwable cause) { + super(cause); + } + + protected ServerInternalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public static void reportInternalException(Throwable cause) { + try { + Bukkit.getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(cause)));; + } catch (Throwable t) { + t.printStackTrace(); // Don't want to rethrow! + } + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java new file mode 100644 index 0000000..8c938d0 --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java @@ -0,0 +1,20 @@ +package org.github.paperspigot.exception; + +import org.bukkit.plugin.Plugin; + +/** + * Thrown whenever there is an exception with any enabling or disabling of plugins. + */ +public class ServerPluginEnableDisableException extends ServerPluginException { + public ServerPluginEnableDisableException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause, responsiblePlugin); + } + + public ServerPluginEnableDisableException(Throwable cause, Plugin responsiblePlugin) { + super(cause, responsiblePlugin); + } + + protected ServerPluginEnableDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java new file mode 100644 index 0000000..f9241ad --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginException.java @@ -0,0 +1,39 @@ +package org.github.paperspigot.exception; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Wrapper exception for all cases to which a plugin can be immediately blamed for + */ +public class ServerPluginException extends ServerException { + public ServerPluginException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + public ServerPluginException(Throwable cause, Plugin responsiblePlugin) { + super(cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + protected ServerPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + private final Plugin responsiblePlugin; + + /** + * Gets the plugin which is directly responsible for the exception being thrown + * + * @return plugin which is responsible for the exception throw + */ + public Plugin getResponsiblePlugin() { + return responsiblePlugin; + } + +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java new file mode 100644 index 0000000..b71303b --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java @@ -0,0 +1,61 @@ +package org.github.paperspigot.exception; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Thrown when an incoming plugin message channel throws an exception + */ +public class ServerPluginMessageException extends ServerPluginException { + + private final Player player; + private final String channel; + private final byte[] data; + + public ServerPluginMessageException(String message, Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + public ServerPluginMessageException(Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + protected ServerPluginMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + /** + * Gets the channel to which the error occurred from recieving data from + * @return exception channel + */ + public String getChannel() { + return channel; + } + + /** + * Gets the data to which the error occurred from + * @return exception data + */ + public byte[] getData() { + return data; + } + + /** + * Gets the player which the plugin message causing the exception originated from + * @return exception player + */ + public Player getPlayer() { + return player; + } +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java new file mode 100644 index 0000000..99757ee --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java @@ -0,0 +1,37 @@ +package org.github.paperspigot.exception; + +import org.bukkit.scheduler.BukkitTask; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a plugin's scheduler fails with an exception + */ +public class ServerSchedulerException extends ServerPluginException { + + private final BukkitTask task; + + public ServerSchedulerException(String message, Throwable cause, BukkitTask task) { + super(message, cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + public ServerSchedulerException(Throwable cause, BukkitTask task) { + super(cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + protected ServerSchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BukkitTask task) { + super(message, cause, enableSuppression, writableStackTrace, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + /** + * Gets the task which threw the exception + * @return exception throwing task + */ + public BukkitTask getTask() { + return task; + } + +} diff --git a/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java new file mode 100644 index 0000000..6e1e2ab --- /dev/null +++ b/TacoSpigot-API/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java @@ -0,0 +1,22 @@ +package org.github.paperspigot.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +/** + * Called when a tab-complete request throws an exception + */ +public class ServerTabCompleteException extends ServerCommandException { + + public ServerTabCompleteException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, command, commandSender, arguments); + } + + public ServerTabCompleteException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause, command, commandSender, arguments); + } + + protected ServerTabCompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace, command, commandSender, arguments); + } +} diff --git a/TacoSpigot-Server/pom.xml b/TacoSpigot-Server/pom.xml index 7bdcf33..3e176f0 100644 --- a/TacoSpigot-Server/pom.xml +++ b/TacoSpigot-Server/pom.xml @@ -95,7 +95,7 @@ mysql mysql-connector-java - 5.1.14 + 8.0.29 jar compile @@ -143,6 +143,12 @@ zstd-jni 1.5.2-3 + + org.apache.logging.log4j + log4j-core + 2.17.2 + compile + diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/Block.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/Block.java index 3c5a4f1..34484d8 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/Block.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/Block.java @@ -165,11 +165,7 @@ public class Block { } public int toLegacyData(IBlockData iblockdata) { - if (iblockdata != null && !iblockdata.a().isEmpty()) { - throw new IllegalArgumentException("Don\'t know how to convert " + iblockdata + " back into data..."); - } else { - return 0; - } + return 0; // Sportpaper - optimize toLegacyData removing unneeded sanity checks } public IBlockData updateState(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockCarpet.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockCarpet.java new file mode 100644 index 0000000..90db8a5 --- /dev/null +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockCarpet.java @@ -0,0 +1,88 @@ +package net.minecraft.server; + +import java.util.List; + +public class BlockCarpet extends Block { + + public static final BlockStateEnum COLOR = BlockStateEnum.of("color", EnumColor.class); + + protected BlockCarpet() { + super(Material.WOOL); + this.j(this.blockStateList.getBlockData().set(BlockCarpet.COLOR, EnumColor.WHITE)); + this.a(0.0F, 0.0F, 0.0F, 1.0F, 0.0625F, 1.0F); + this.a(true); + this.a(CreativeModeTab.c); + this.b(0); + } + + public MaterialMapColor g(IBlockData iblockdata) { + return iblockdata.get(BlockCarpet.COLOR).e(); + } + + public boolean c() { + return false; + } + + public boolean d() { + return false; + } + + public void j() { + this.b(0); + } + + public void updateShape(IBlockAccess iblockaccess, BlockPosition blockposition) { + this.b(0); + } + + protected void b(int i) { + this.a(0.0F, 0.0F, 0.0F, 1.0F, 0.0625F, 1.0F); + } + + // SportPaper start - No height on carpet in feet height, avoid 1.7 players glitching + public void a(World world, BlockPosition blockposition, IBlockData iblockdata, AxisAlignedBB axisalignedbb, List list, Entity entity) { + if (entity instanceof EntityHuman && blockposition.getY() == (int) entity.getBoundingBox().b) { + return; + } + super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); + } + // SportPaper end + + public boolean canPlace(World world, BlockPosition blockposition) { + return super.canPlace(world, blockposition) && this.e(world, blockposition); + } + + public void doPhysics(World world, BlockPosition blockposition, IBlockData iblockdata, Block block) { + this.e(world, blockposition, iblockdata); + } + + private boolean e(World world, BlockPosition blockposition, IBlockData iblockdata) { + if (!this.e(world, blockposition)) { + this.b(world, blockposition, iblockdata, 0); + world.setAir(blockposition); + return false; + } else { + return true; + } + } + + private boolean e(World world, BlockPosition blockposition) { + return !world.isEmpty(blockposition.down()); + } + + public int getDropData(IBlockData iblockdata) { + return iblockdata.get(BlockCarpet.COLOR).getColorIndex(); + } + + public IBlockData fromLegacyData(int i) { + return this.getBlockData().set(BlockCarpet.COLOR, EnumColor.fromColorIndex(i)); + } + + public int toLegacyData(IBlockData iblockdata) { + return iblockdata.get(BlockCarpet.COLOR).getColorIndex(); + } + + protected BlockStateList getStateList() { + return new BlockStateList(this, BlockCarpet.COLOR); + } +} diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFire.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFire.java index a1747f2..da1d12c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFire.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFire.java @@ -176,6 +176,7 @@ public class BlockFire extends Block { } BlockPosition blockposition1 = blockposition.a(j, l, k); + if (!world.isLoaded(blockposition1)) continue; // Paper int j1 = this.m(world, blockposition1); if (j1 > 0) { @@ -244,10 +245,13 @@ public class BlockFire extends Block { } private void a(World world, BlockPosition blockposition, int i, Random random, int j) { + // Paper start + final IBlockData iblockdata = world.getTypeIfLoaded(blockposition); + if (iblockdata == null) return; int k = this.c(world.getType(blockposition).getBlock()); if (random.nextInt(i) < k) { - IBlockData iblockdata = world.getType(blockposition); + //IBlockData iblockdata = world.getType(blockposition); // Paper // CraftBukkit start org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); @@ -300,7 +304,11 @@ public class BlockFire extends Block { EnumDirection[] aenumdirection = EnumDirection.values(); int j = aenumdirection.length; - for (EnumDirection enumdirection : aenumdirection) { + for (int k = 0; k < j; ++k) { + EnumDirection enumdirection = aenumdirection[k]; + + final IBlockData type = world.getTypeIfLoaded(blockposition.shift(enumdirection)); // Paper + if (type == null) continue; // Paper i = Math.max(this.d(world.getType(blockposition.shift(enumdirection)).getBlock()), i); } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java index e154f4e..1da089f 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java @@ -107,6 +107,7 @@ public class BlockFlowing extends BlockFluids { if (this.h(world, blockposition.down(), iblockdata2)) { // CraftBukkit start - Send "down" to the server + if (!canFlowTo(world, source, BlockFace.DOWN)) { return; } // Paper BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN); if (server != null) { server.getPluginManager().callEvent(event); @@ -141,13 +142,16 @@ public class BlockFlowing extends BlockFluids { EnumDirection enumdirection1 = (EnumDirection) value; // CraftBukkit start - BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection1)); - if (server != null) { - server.getPluginManager().callEvent(event); - } + if(this.h(world, blockposition.shift(enumdirection1), world.getType(blockposition.shift(enumdirection1)))) { + if (!canFlowTo(world, source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection1))) { continue; } // Paper + 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); + if (!event.isCancelled()) { + this.flow(world, blockposition.shift(enumdirection1), world.getType(blockposition.shift(enumdirection1)), k); + } } // CraftBukkit end } @@ -155,8 +159,14 @@ public class BlockFlowing extends BlockFluids { } + // Paper start + private boolean canFlowTo(World world, org.bukkit.block.Block source, BlockFace face) { + return source.getWorld().isChunkLoaded((source.getX() + face.getModX()) >> 4, (source.getZ() + face.getModZ()) >> 4); + } + // Paper end + private void flow(World world, BlockPosition blockposition, IBlockData iblockdata, int i) { - if (world.isLoaded(blockposition) && this.h(world, blockposition, iblockdata)) { // CraftBukkit - add isLoaded check + if (/*world.isLoaded(blockposition) &&*/ this.h(world, blockposition, iblockdata)) { // CraftBukkit - add isLoaded check // Paper - Already checked before we get here for isLoade if (iblockdata.getBlock() != Blocks.AIR) { if (this.material == Material.LAVA) { this.fizz(world, blockposition); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockPiston.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockPiston.java index 21c4885..cf95b2d 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockPiston.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/BlockPiston.java @@ -117,13 +117,13 @@ public class BlockPiston extends Block { } public boolean a(World world, BlockPosition blockposition, IBlockData iblockdata, int i, int j) { - EnumDirection enumdirection = iblockdata.get(BlockPiston.FACING); + EnumDirection enumdirection = (EnumDirection) iblockdata.get(BlockPiston.FACING); if (!world.isClientSide) { boolean flag = this.a(world, blockposition, enumdirection); if (flag && i == 1) { - world.setTypeAndData(blockposition, iblockdata.set(BlockPiston.EXTENDED, Boolean.TRUE), 2); + world.setTypeAndData(blockposition, iblockdata.set(BlockPiston.EXTENDED, Boolean.valueOf(true)), 2); return false; } @@ -133,11 +133,15 @@ public class BlockPiston extends Block { } if (i == 0) { + // SportBukkit start + org.bukkit.event.block.BlockPistonEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPistonEvent(world, blockposition, enumdirection, true); + if(event != null && event.isCancelled()) return false; + // SportBukkit end if (!this.a(world, blockposition, enumdirection, true)) { return false; } - world.setTypeAndData(blockposition, iblockdata.set(BlockPiston.EXTENDED, Boolean.TRUE), 2); + world.setTypeAndData(blockposition, iblockdata.set(BlockPiston.EXTENDED, Boolean.valueOf(true)), 2); world.makeSound((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, "tile.piston.out", 0.5F, world.random.nextFloat() * 0.25F + 0.6F); } else if (i == 1) { TileEntity tileentity = world.getTileEntity(blockposition.shift(enumdirection)); @@ -148,6 +152,10 @@ public class BlockPiston extends Block { world.setTypeAndData(blockposition, Blocks.PISTON_EXTENSION.getBlockData().set(BlockPistonMoving.FACING, enumdirection).set(BlockPistonMoving.TYPE, this.sticky ? BlockPistonExtension.EnumPistonType.STICKY : BlockPistonExtension.EnumPistonType.DEFAULT), 3); world.setTileEntity(blockposition, BlockPistonMoving.a(this.fromLegacyData(j), enumdirection, false, true)); + // SportBukkit start + org.bukkit.event.block.BlockPistonEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPistonEvent(world, blockposition, enumdirection, false); + if(event != null && event.isCancelled()) return false; + // SportBukkit end if (this.sticky) { BlockPosition blockposition1 = blockposition.a(enumdirection.getAdjacentX() * 2, enumdirection.getAdjacentY() * 2, enumdirection.getAdjacentZ() * 2); Block block = world.getType(blockposition1).getBlock(); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/Chunk.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/Chunk.java index 71c4630..01480e5 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/Chunk.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/Chunk.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.Logger; import com.google.common.collect.Lists; // CraftBukkit import org.bukkit.Bukkit; // CraftBukkit +import org.bukkit.craftbukkit.util.LongHash; public class Chunk { @@ -29,6 +30,7 @@ public class Chunk { private boolean h; public final World world; public final int[] heightMap; + public final long chunkKey; // Paper public final int locX; public final int locZ; private boolean k; @@ -42,7 +44,7 @@ public class Chunk { private boolean done; private boolean lit; private boolean p; - public boolean q; + public boolean q; public void markDirty() { this.q = true; }// Paper private boolean r; private long lastSaved; private int t; @@ -155,6 +157,7 @@ public class Chunk { this.world = world; this.locX = i; this.locZ = j; + this.chunkKey = LongHash.toLong(this.locX, this.locZ); // Paper this.heightMap = new int[256]; for (int k = 0; k < this.entitySlices.length; ++k) { @@ -843,6 +846,7 @@ public class Chunk { return !block.isTileEntity() ? null : ((IContainer) block).a(this.world, this.c(blockposition)); } + public final TileEntity getTileEntityImmediately(BlockPosition pos) { return this.a(pos, EnumTileEntityState.IMMEDIATE); } // Paper - OBFHELPER public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { // CraftBukkit start TileEntity tileentity = null; @@ -899,9 +903,16 @@ public class Chunk { System.out.println("Chunk coordinates: " + (this.locX * 16) + "," + (this.locZ * 16)); new Exception().printStackTrace(); // CraftBukkit end + + if (this.world.paperSpigotConfig.removeCorruptTEs) { + this.removeTileEntity(tileentity.getPosition()); + this.markDirty(); + org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity"); + } } } + public void removeTileEntity(BlockPosition blockposition) { this.e(blockposition); } // Paper - OBFHELPER public void e(BlockPosition blockposition) { if (this.h) { TileEntity tileentity = this.tileEntities.remove(blockposition); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java index 968a807..5d99555 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.Iterator; import java.util.List; +import it.unimi.dsi.fastutil.longs.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,38 +17,39 @@ import org.bukkit.craftbukkit.util.LongHash; import org.bukkit.event.world.ChunkUnloadEvent; // CraftBukkit end // TacoSpigot start -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArraySet; -import it.unimi.dsi.fastutil.longs.LongIterator; -import it.unimi.dsi.fastutil.longs.LongSet; + // TacoSpigot end public class ChunkProviderServer implements IChunkProvider { private static final Logger b = LogManager.getLogger(); - public LongSet unloadQueue = new LongArraySet(); // CraftBukkit - LongHashSet // TacoSpigot - LongHashSet -> HashArraySet + public LongSet unloadQueue = new LongOpenHashSet(20); // CraftBukkit - LongHashSet // TacoSpigot - LongHashSet -> HashArraySet public Chunk emptyChunk; public IChunkProvider chunkProvider; public IChunkLoader chunkLoader; // KigPaper - private -> public public boolean forceChunkLoad = false; // CraftBukkit - true -> false - public Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(4096, 0.5f); // TacoSpigot - use trove Long2ObjectOpenHashMap instead of craftbukkit implementation (using inital capacity and load factor chosen by Amaranth in an old impl) + // Paper start + protected Chunk lastChunkByPos = null; + public Long2ObjectMap chunks = new Long2ObjectOpenHashMap(8192, 0.5f) { + @Override + public Chunk get(long key) { + if (lastChunkByPos != null && key == lastChunkByPos.chunkKey) { + return lastChunkByPos; + } + return lastChunkByPos = super.get(key); + } + + @Override + public Chunk remove(long key) { + if (lastChunkByPos != null && key == lastChunkByPos.chunkKey) { + lastChunkByPos = null; + } + return super.remove(key); + } + }; // CraftBukkit + // Paper end public WorldServer world; - // Migot start - private ChunkRegionLoader checkedRegionLoader = null; - - public boolean doesChunkExist(int x, int z) { - if(this.checkedRegionLoader == null && this.chunkLoader instanceof ChunkRegionLoader) { - this.checkedRegionLoader = (ChunkRegionLoader) this.chunkLoader; - } - if(this.checkedRegionLoader != null) { - return this.checkedRegionLoader.chunkExists(this.world, x, z); - } - return false; - } - // Migot end - public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, IChunkProvider ichunkprovider) { this.emptyChunk = new EmptyChunk(worldserver, Integer.MIN_VALUE, Integer.MIN_VALUE); // Migot this.world = worldserver; @@ -126,7 +128,6 @@ public class ChunkProviderServer implements IChunkProvider { } public Chunk getChunkAt(int i, int j, Runnable runnable) { - unloadQueue.remove(LongHash.toLong(i, j)); // TacoSpigot - directly invoke LongHash Chunk chunk = chunks.get(LongHash.toLong(i, j)); ChunkRegionLoader loader = null; @@ -146,6 +147,7 @@ public class ChunkProviderServer implements IChunkProvider { chunk = originalGetChunkAt(i, j); } + unloadQueue.remove(LongHash.toLong(i, j)); // SportPaper // If we didn't load the chunk async and have a callback run it now if (runnable != null) { runnable.run(); @@ -154,7 +156,6 @@ public class ChunkProviderServer implements IChunkProvider { return chunk; } public Chunk originalGetChunkAt(int i, int j) { - this.unloadQueue.remove(LongHash.toLong(i, j)); // TacoSpigot - directly invoke LongHash Chunk chunk = this.chunks.get(LongHash.toLong(i, j)); boolean newChunk = false; // CraftBukkit end @@ -182,9 +183,9 @@ public class ChunkProviderServer implements IChunkProvider { } this.chunks.put(LongHash.toLong(i, j), chunk); - + chunk.addEntities(); - + // CraftBukkit start Server server = world.getServer(); if (server != null) { @@ -215,6 +216,7 @@ public class ChunkProviderServer implements IChunkProvider { world.timings.syncChunkLoadTimer.stopTiming(); // Spigot } + this.unloadQueue.remove(LongHash.toLong(i, j)); // SportPaper return chunk; } @@ -365,48 +367,62 @@ public class ChunkProviderServer implements IChunkProvider { } + // SportPaper start + public void unloadAllChunks() { + for(Chunk chunk : chunks.values()) { + unloadChunk(chunk); + } + } + + public void unloadChunk(Chunk chunk) { + unloadChunk(chunk, false); + } + + private void unloadChunk(Chunk chunk, boolean auto) { + Server server = this.world.getServer(); + ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk); + server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + + chunk.removeEntities(); + this.saveChunk(chunk); + this.saveChunkNOP(chunk); + this.chunks.remove(chunk.chunkKey); // CraftBukkit + if (!auto && this.unloadQueue.contains(chunk.chunkKey)) { + this.unloadQueue.remove(chunk.chunkKey); + } + + // 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); + } + } + } + } + } + // SportPaper end + public boolean unloadChunks() { if (!this.world.savingDisabled) { // CraftBukkit start Server server = this.world.getServer(); - // TacoSpigot start - use iterator for unloadQueue + // SportPaper start LongIterator iterator = unloadQueue.iterator(); for (int i = 0; i < 100 && iterator.hasNext(); ++i) { - long chunkcoordinates = iterator.next(); + long chunkcoordinates = iterator.nextLong(); iterator.remove(); - // TacoSpigot end + // SportPaper end 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.markAsUnloaded(); // Migot - chunk.removeEntities(); - this.saveChunk(chunk); - this.saveChunkNOP(chunk); - this.chunks.remove(chunkcoordinates); // CraftBukkit - } - - // 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); - } - } - } - } + unloadChunk(chunk, true); // SportPaper - Move to own method } // CraftBukkit end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java index 1a4a9f0..cd0efe0 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java @@ -8,6 +8,7 @@ import java.net.InetAddress; import java.net.Proxy; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -28,7 +29,7 @@ import org.bukkit.event.server.RemoteServerCommandEvent; public class DedicatedServer extends MinecraftServer implements IMinecraftServer { private static final Logger LOGGER = LogManager.getLogger(); - private final List l = Collections.synchronizedList(Lists.newArrayList()); // CraftBukkit - fix decompile error + private final Queue l = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - use a proper queue private RemoteStatusListener m; private RemoteControlListener n; public PropertyManager propertyManager; @@ -374,7 +375,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer public void B() { // CraftBukkit - fix decompile error super.B(); - this.aO(); + // this.aO(); // SportBukkit - moved to processTasks() } public boolean getAllowNether() { @@ -399,10 +400,21 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer this.l.add(new ServerCommand(s, icommandlistener)); } + // SportBukkit start + @Override + protected void processTasks() { + super.processTasks(); + processCommands(); + } + // SportBukkit end + public void processCommands() { aO(); } // SportBukkit - alias + public void aO() { SpigotTimings.serverCommandTimer.startTiming(); // Spigot - while (!this.l.isEmpty()) { - ServerCommand servercommand = this.l.remove(0); + // Paper start - use proper queue + ServerCommand servercommand; + while ((servercommand = this.l.poll()) != null) { + // Paper end // CraftBukkit start - ServerCommand for preprocessing ServerCommandEvent event = new ServerCommandEvent(console, servercommand.command); @@ -652,7 +664,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer return RemoteControlCommandListener.getInstance().j(); } }; - processQueue.add(waitable); + addMainThreadTask(waitable); try { return waitable.get(); } catch (java.util.concurrent.ExecutionException e) { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/Entity.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/Entity.java index f0f61d3..20fcc03 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/Entity.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/Entity.java @@ -17,6 +17,7 @@ import org.bukkit.entity.Painting; import org.bukkit.entity.Vehicle; import co.aikar.timings.SpigotTimings; // Spigot import co.aikar.timings.Timing; // Spigot +import org.bukkit.event.entity.EntityCombustByBlockEvent; import org.bukkit.event.entity.EntityCombustByEntityEvent; import org.bukkit.event.hanging.HangingBreakByEntityEvent; import org.bukkit.event.painting.PaintingBreakByEntityEvent; @@ -50,7 +51,7 @@ public abstract class Entity implements ICommandListener { private static int entityCount = 1; private int id; public double j; - public boolean k; + public boolean k; public boolean blocksEntitySpawning() { return k; } // Paper - OBFHELPER public Entity passenger; public Entity vehicle; public boolean attachedToPlayer; @@ -94,6 +95,7 @@ public abstract class Entity implements ICommandListener { public int ticksLived; public int maxFireTicks; public int fireTicks; + public boolean wasOnFire; // CraftBukkit - to detect when the fire goes out public boolean inWater; // Spigot - protected -> public // PAIL public int noDamageTicks; protected boolean justCreated; @@ -117,7 +119,7 @@ public abstract class Entity implements ICommandListener { public boolean ah; public boolean ai; public int portalCooldown; - protected boolean ak; + protected boolean ak; public boolean inPortal() { return ak; } // Paper - OBFHELPER protected int al; public int dimension; @@ -394,21 +396,23 @@ public abstract class Entity implements ICommandListener { this.damageEntity(DamageSource.LAVA, 4.0F); // CraftBukkit start - Fallen in lava TODO: this event spams! + Vec3D lavaPos = this.world.getLargestBlockIntersection(this.boundingBox.shrink(0.001D, 0.001D, 0.001D), Material.LAVA); + org.bukkit.block.Block lavaBlock = lavaPos == null ? null : this.world.getWorld().getBlockAt((int) lavaPos.a, (int) lavaPos.b, (int) lavaPos.c); + try { + CraftEventFactory.blockDamage = lavaBlock; + this.damageEntity(DamageSource.LAVA, 4); + } finally { + CraftEventFactory.blockDamage = null; + } 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); + // Note that in order for cancelling or custom duration to work properly, + // this event must be fired every tick, thus we cannot avoid "spamming" it. + org.bukkit.entity.Entity damagee = this.getBukkitEntity(); + EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(lavaBlock, 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); + if (!combustEvent.isCancelled()) { + this.setOnFire(combustEvent.getDuration()); } return; } @@ -802,21 +806,32 @@ public abstract class Entity implements ICommandListener { boolean flag2 = this.U(); - if (this.world.e(this.getBoundingBox().shrink(0.001D, 0.001D, 0.001D))) { - this.burn(1); + // CraftBukkit start - get the location of the fire block + Vec3D firePos = this.world.getLargestBlockIntersection(this.boundingBox.shrink(0.001D, 0.001D, 0.001D), Material.FIRE); + if (firePos != null) { + org.bukkit.block.Block fireBlock = this.bukkitEntity.getWorld().getBlockAt((int) firePos.a, (int) firePos.b, (int) firePos.c); + try { + CraftEventFactory.blockDamage = fireBlock; + this.burn(1); + } finally { + CraftEventFactory.blockDamage = null; + } if (!flag2) { - ++this.fireTicks; - // 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); + EntityCombustByBlockEvent event = new EntityCombustByBlockEvent(fireBlock, this.getBukkitEntity(), 8); + this.world.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - setOnFire(event.getDuration()); + if (!event.isCancelled()) { + // Note carefully how this works: when fireTicks is negative, the entity is + // "heating up" but not on fire yet. When fireTicks reaches 0, the entity + // "ignites" and fireTicks jumps to 160. It will then stay at that value as + // long as the player remains in fire (because the ++ below will cancel out + // the -- in the entity tick). For the event cancelling to work, it has to + // be fired every tick, thus we cannot avoid "spamming" it. + ++this.fireTicks; + if (this.fireTicks == 0) { + this.setOnFire(event.getDuration()); } - } else { - // CraftBukkit end - this.setOnFire(8); + } } } else if (this.fireTicks <= 0) { @@ -828,6 +843,14 @@ public abstract class Entity implements ICommandListener { this.fireTicks = -this.maxFireTicks; } + // CraftBukkit start + if(this.fireTicks > 0) { + this.wasOnFire = true; + } else if(this.wasOnFire && this.fireTicks <= 0) { + this.wasOnFire = false; + } + // CraftBukkit end + this.world.methodProfiler.b(); } } @@ -1139,6 +1162,7 @@ public abstract class Entity implements ICommandListener { this.lastYaw -= 360.0F; } + world.getChunkAt((int) Math.floor(this.locX) >> 4, (int) Math.floor(this.locZ) >> 4); // Paper - ensure chunk is always loaded this.setPosition(this.locX, this.locY, this.locZ); this.setYawPitch(f, f1); } @@ -2082,7 +2106,7 @@ public abstract class Entity implements ICommandListener { } public void teleportTo(Location exit, boolean portal) { - if (true) { + if (!this.dead) { // Paper WorldServer worldserver = ((CraftWorld) getBukkitEntity().getLocation().getWorld()).getHandle(); WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle(); // CraftBukkit end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityArrow.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityArrow.java index 69e9068..1c3aa31 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityArrow.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityArrow.java @@ -11,6 +11,7 @@ import org.bukkit.event.player.PlayerPickupItemEvent; import net.techcable.tacospigot.event.entity.ArrowCollideEvent; import org.bukkit.Bukkit; import org.bukkit.entity.Arrow; +import org.github.paperspigot.PaperSpigotConfig; // TacoSpigot end public class EntityArrow extends Entity implements IProjectile { @@ -101,7 +102,7 @@ public class EntityArrow extends Entity implements IProjectile { this.motX = -MathHelper.sin(this.yaw / 180.0F * 3.1415927F) * MathHelper.cos(this.pitch / 180.0F * 3.1415927F); this.motZ = MathHelper.cos(this.yaw / 180.0F * 3.1415927F) * MathHelper.cos(this.pitch / 180.0F * 3.1415927F); this.motY = -MathHelper.sin(this.pitch / 180.0F * 3.1415927F); - this.shoot(this.motX, this.motY, this.motZ, f * 1.5F, 1.0F); + this.shoot(this.motX, this.motY, this.motZ, f * 1.5F, PaperSpigotConfig.includeRandomnessInArrowTrajectory ? 1.0F : 0); // SportPaper } protected void h() { @@ -114,9 +115,11 @@ public class EntityArrow extends Entity implements IProjectile { d0 /= f2; d1 /= f2; d2 /= f2; - d0 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; - d1 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; - d2 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; + if (f1 != 0) { + d0 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; + d1 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; + d2 += this.random.nextGaussian() * (double) (this.random.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double) f1; + } d0 *= f; d1 *= f; d2 *= f; @@ -248,7 +251,7 @@ public class EntityArrow extends Entity implements IProjectile { f2 = MathHelper.sqrt(this.motX * this.motX + this.motY * this.motY + this.motZ * this.motZ); int k = MathHelper.f((double) f2 * this.damage); - if (this.isCritical()) { + if (this.isCritical() && PaperSpigotConfig.includeRandomnessInArrowDamage) { // SportPaper k += this.random.nextInt(k / 2 + 2); } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java index 410cfcc..7b78b7c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java @@ -106,7 +106,7 @@ public class EntityBoat extends Entity { this.world.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { - return true; + return false; } // f = event.getDamage(); // TODO Why don't we do this? // CraftBukkit end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityEnderDragon.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityEnderDragon.java index 6aac7ee..44f7cc3 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityEnderDragon.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityEnderDragon.java @@ -569,8 +569,12 @@ public class EntityEnderDragon extends EntityInsentient implements IComplex, IMo if (this.by == 1) { // CraftBukkit start - Use relative location for far away sounds // this.world.a(1018, new BlockPosition(this), 0); - int viewDistance = this.world.getServer().getViewDistance() * 16; - for (EntityPlayer player : MinecraftServer.getServer().getPlayerList().players) { + // Paper start + //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (EntityHuman human : world.players) { + EntityPlayer player = (EntityPlayer) human; + int viewDistance = player.viewDistance; + // Paper end double deltaX = this.locX - player.locX; double deltaZ = this.locZ - player.locZ; double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityFishingHook.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityFishingHook.java index d43ecb2..e1f9ff7 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityFishingHook.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityFishingHook.java @@ -361,6 +361,12 @@ public class EntityFishingHook extends Entity { this.motY *= f2; this.motZ *= f2; this.setPosition(this.locX, this.locY, this.locZ); + + // Paper start - These shouldn't be going through portals + if (this.inPortal()) { + this.die(); + } + // Paper end } } } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java index 5d1c850..54ee9d2 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java @@ -853,6 +853,9 @@ public abstract class EntityHuman extends EntityLiving { public boolean a(EntityHuman entityhuman) { // 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" + + if(this == entityhuman) return true; // SportBukkit - self-damage is always allowed + org.bukkit.scoreboard.Team team; if (entityhuman instanceof EntityPlayer) { EntityPlayer thatPlayer = (EntityPlayer) entityhuman; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityItem.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityItem.java index 573591b..d684d9c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityItem.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityItem.java @@ -156,6 +156,10 @@ public class EntityItem extends Entity implements HopperPusher { // Spigot end private void w() { + // Paper start - avoid item merge if stack size above max stack size + ItemStack stack = getItemStack(); + if (stack.count >= stack.getMaxStackSize()) return; + // Paper end // Spigot start double radius = world.spigotConfig.itemMerge; // Spigot end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java index f5ada58..78c37e9 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java @@ -122,7 +122,7 @@ public abstract class EntityMinecartAbstract extends Entity implements INamableT this.world.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { - return true; + return false; } f = (float) event.getDamage(); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java index e49172b..2f15a6f 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java @@ -4,12 +4,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.mojang.authlib.GameProfile; import io.netty.buffer.Unpooled; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; + +import java.util.*; import com.elevatemc.spigot.eSpigot; import org.apache.logging.log4j.LogManager; @@ -31,13 +27,14 @@ public class EntityPlayer extends EntityHuman implements ICrafting { private static final Logger bH = LogManager.getLogger(); public String locale = "en_US"; // Spigot + public long lastSave = MinecraftServer.currentTick; // Paper public PlayerConnection playerConnection; public final MinecraftServer server; public final PlayerInteractManager playerInteractManager; public double d; public double e; public final List chunkCoordIntPairQueue = Lists.newLinkedList(); - public final List removeQueue = Lists.newLinkedList(); + public final Deque removeQueue = new ArrayDeque<>(); // Paper private final ServerStatisticManager bK; private float bL = Float.MIN_VALUE; private float bM = -1.0E8F; @@ -127,6 +124,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void a(NBTTagCompound nbttagcompound) { super.a(nbttagcompound); + if (this.locY > 300) this.locY = 257; // Paper - bring down to a saner Y level if out of world if (nbttagcompound.hasKeyOfType("playerGameType", 99)) { if (MinecraftServer.getServer().getForceGamemode()) { this.playerInteractManager.setGameMode(MinecraftServer.getServer().getGamemode()); @@ -223,10 +221,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { Iterator iterator = this.removeQueue.iterator(); int j = 0; - while (iterator.hasNext() && j < i) { - aint[j++] = (Integer) iterator.next(); - iterator.remove(); - } + // Paper start + Integer integer; + while (j < i && (integer = this.removeQueue.poll()) != null) { + aint[j++] = integer.intValue(); + }// Paper end this.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(aint)); } @@ -423,7 +422,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } } - IChatBaseComponent chatmessage = this.bs().b(); + IChatBaseComponent chatmessage = damagesource == DamageSource.GENERIC ? damagesource.getLocalizedDeathMessage(this) : this.bs().b(); // CraftBukkit String deathmessage = chatmessage.c(); org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory); @@ -464,7 +463,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { EntityLiving entityliving = this.bt(); - if (entityliving != null) { + // CraftBukkit - can't have a combat tracked killer with a generic damage source + if (entityliving != null && damagesource != DamageSource.GENERIC) { EntityTypes.MonsterEggInfo entitytypes_monsteregginfo = EntityTypes.eggInfo.get(EntityTypes.a(entityliving)); if (entitytypes_monsteregginfo != null) { @@ -488,7 +488,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { if (!flag && this.invulnerableTicks > 0 && damagesource != DamageSource.OUT_OF_WORLD) { return false; } else { - if (damagesource instanceof EntityDamageSource) { + if (damagesource instanceof EntityDamageSource && !damagesource.isExplosion()) { // SportBukkit - explosion damage is not subject to FF rules Entity entity = damagesource.getEntity(); if (entity instanceof EntityHuman && !this.a((EntityHuman) entity)) { @@ -510,6 +510,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } public boolean a(EntityHuman entityhuman) { + if(this == entityhuman) return true; // SportBukkit - self-damage is always allowed return this.cr() && super.a(entityhuman); } @@ -917,7 +918,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.lastSentExp = -1; this.bM = -1.0F; this.bN = -1; - this.removeQueue.addAll(((EntityPlayer) entityhuman).removeQueue); + // Paper start - Optimize remove queue + if (this.removeQueue != ((EntityPlayer) entityhuman).removeQueue) { + this.removeQueue.addAll(((EntityPlayer) entityhuman).removeQueue); + } } protected void a(MobEffect mobeffect) { @@ -1201,6 +1205,15 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.exp = 0; this.deathTicks = 0; this.removeAllEffects(); + + // Clear potion metadata now, because new effects might get added + // before the update in the tick has a chance to run, and if they + // match the old effects, the metadata will never be marked dirty + // and will go out of sync with the client. + this.datawatcher.watch(8, (byte) 0); + this.datawatcher.watch(7, 0); + this.setInvisible(false); + this.updateEffects = true; this.activeContainer = this.defaultContainer; this.killer = null; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java index e5241cb..89d69de 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -153,7 +153,8 @@ public class EntityTrackerEntry { 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)); + // SportPaper - Fix invisibility on teleport + this.scanPlayers(new ArrayList(this.tracker.world.players)); } // CraftBukkit end object = new PacketPlayOutEntityTeleport(this.tracker.getId(), i, j, k, (byte) l, (byte) i1, this.tracker.onGround); @@ -272,7 +273,7 @@ public class EntityTrackerEntry { DataWatcher datawatcher = this.tracker.getDataWatcher(); if (datawatcher.a()) { - if (eSpigotFeature.OBFUSCATE_HEALTH.isEnabled()) { + if (eSpigotFeature.OBFUSCATE_HEALTH.isEnabled() && this.tracker instanceof EntityHuman) { List changedMetadata = datawatcher.c(); Iterator iter = changedMetadata.iterator(); boolean found = false; @@ -423,13 +424,15 @@ public class EntityTrackerEntry { } // CraftBukkit start - Fix for nonsensical head yaw - this.i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F); - // KigPaper - if (this.tracker instanceof EntityLiving) { + if(this.tracker instanceof EntityLiving) { // SportPaper - avoid processing entities that can't change head rotation this.i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F); - // CraftBukkit what the fuck were you thinking? - //this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i)); // KigPaper + // SportPaper start + // This was originally introduced by CraftBukkit, though the implementation is wrong since it's broadcasting + // the packet again in a method that is already called for each player. This would create a very serious performance issue + // with high player and entity counts (each sendPacket call involves waking up the event loop and flushing the network stream). + // this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i)); entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i)); + // SportPaper end } // CraftBukkit end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityWither.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityWither.java index 03183b9..d8e3b65 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityWither.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/EntityWither.java @@ -186,8 +186,12 @@ public class EntityWither extends EntityMonster implements IRangedEntity { // CraftBukkit start - Use relative location for far away sounds // this.world.a(1013, new BlockPosition(this), 0); - int viewDistance = this.world.getServer().getViewDistance() * 16; - for (EntityPlayer player : MinecraftServer.getServer().getPlayerList().players) { + // Paper start + //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (EntityHuman human : world.players) { + EntityPlayer player = (EntityPlayer) human; + int viewDistance = player.viewDistance; + // Paper end double deltaX = this.locX - player.locX; double deltaZ = this.locZ - player.locZ; double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBlock.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBlock.java index f25f165..c6b313a 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBlock.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBlock.java @@ -36,7 +36,8 @@ public class ItemBlock extends Item { this.a.postPlace(world, blockposition, iblockdata1, entityhuman, itemstack); } - world.makeSound((float) blockposition.getX() + 0.5F, (float) blockposition.getY() + 0.5F, (float) blockposition.getZ() + 0.5F, this.a.stepSound.getPlaceSound(), (this.a.stepSound.getVolume1() + 1.0F) / 2.0F, this.a.stepSound.getVolume2() * 0.8F); + // SPIGOT-1288 + // world.makeSound((double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.5F), (double) ((float) blockposition.getZ() + 0.5F), this.a.stepSound.getPlaceSound(), (this.a.stepSound.getVolume1() + 1.0F) / 2.0F, this.a.stepSound.getVolume2() * 0.8F); --itemstack.count; } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBucket.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBucket.java index af0f2ed..080d79c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBucket.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemBucket.java @@ -46,6 +46,7 @@ public class ItemBucket extends Item { PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), null, itemstack, Items.WATER_BUCKET); if (event.isCancelled()) { + ((EntityPlayer)entityhuman).updateInventory(entityhuman.defaultContainer); return itemstack; } // CraftBukkit end @@ -59,6 +60,7 @@ public class ItemBucket extends Item { PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), null, itemstack, Items.LAVA_BUCKET); if (event.isCancelled()) { + ((EntityPlayer)entityhuman).updateInventory(entityhuman.defaultContainer); return itemstack; } // CraftBukkit end @@ -72,6 +74,7 @@ public class ItemBucket extends Item { PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), movingobjectposition.direction, itemstack); if (event.isCancelled()) { + ((EntityPlayer)entityhuman).updateInventory(entityhuman.defaultContainer); return itemstack; } @@ -86,25 +89,27 @@ public class ItemBucket extends Item { } // CraftBukkit start - PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), movingobjectposition.direction, itemstack); + // Check that the bucket can be emptied before firing the event + if (world.isEmpty(blockposition1) || !world.getType(blockposition1).getBlock().getMaterial().isBuildable()) { + PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(entityhuman, blockposition.getX(), blockposition.getY(), blockposition.getZ(), movingobjectposition.direction, itemstack); + if (event.isCancelled()) { + ((EntityPlayer)entityhuman).updateInventory(entityhuman.defaultContainer); + return 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)]); - // PaperSpigot start - Stackable Buckets - if ((this == Items.LAVA_BUCKET && PaperSpigotConfig.stackableLavaBuckets) || - (this == Items.WATER_BUCKET && PaperSpigotConfig.stackableWaterBuckets)) { - if (--itemstack.count <= 0) { - return CraftItemStack.asNMSCopy(event.getItemStack()); + if (this.a(world, blockposition1) && !entityhuman.abilities.canInstantlyBuild) { + entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this)]); + // PaperSpigot start - Stackable Buckets + if ((this == Items.LAVA_BUCKET && PaperSpigotConfig.stackableLavaBuckets) || + (this == Items.WATER_BUCKET && PaperSpigotConfig.stackableWaterBuckets)) { + if (--itemstack.count <= 0) { + return CraftItemStack.asNMSCopy(event.getItemStack()); + } + if (!entityhuman.inventory.pickup(CraftItemStack.asNMSCopy(event.getItemStack()))) { + entityhuman.drop(CraftItemStack.asNMSCopy(event.getItemStack()), false); + } + return itemstack; } - if (!entityhuman.inventory.pickup(CraftItemStack.asNMSCopy(event.getItemStack()))) { - entityhuman.drop(CraftItemStack.asNMSCopy(event.getItemStack()), false); - } - return itemstack; } // PaperSpigot end return CraftItemStack.asNMSCopy(event.getItemStack()); // CraftBukkit diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemStack.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemStack.java index 418e965..a3ff1b5 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemStack.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/ItemStack.java @@ -218,6 +218,12 @@ public final class ItemStack { } } + // SPIGOT-1288 - play sound stripped from ItemBlock + if (this.getItem() instanceof ItemBlock) { + Block base = ((ItemBlock) this.getItem()).a; + world.makeSound((double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.5F), (double) ((float) blockposition.getZ() + 0.5F), base.stepSound.getPlaceSound(), (base.stepSound.getVolume1() + 1.0F) / 2.0F, base.stepSound.getVolume2() * 0.8F); + } + entityhuman.b(StatisticList.USE_ITEM_COUNT[Item.getId(this.item)]); } } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java index 228a14e..66f5242 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java @@ -48,8 +48,18 @@ public class LoginListener implements PacketLoginInListener, IUpdatePlayerListBo } public void c() { + // Paper start - Do not allow logins while the server is shutting down + if (!MinecraftServer.getServer().isRunning()) { + this.disconnect(org.spigotmc.SpigotConfig.restartMessage); + return; + } + // Paper end if (this.g == LoginListener.EnumProtocolState.READY_TO_ACCEPT) { - this.b(); + // Paper start - prevent logins to be processed even though disconnect was called + if (networkManager.channel != null && networkManager.channel.isOpen()) { + this.b(); + } + // Paper end } else if (this.g == LoginListener.EnumProtocolState.e) { EntityPlayer entityplayer = this.server.getPlayerList().a(this.i.getId()); @@ -239,10 +249,41 @@ public class LoginListener implements PacketLoginInListener, IUpdatePlayerListBo } } + // Paper start - Delay async prelogin until plugins are ready + private static volatile Object blockingLogins = new Object(); + + public static void checkStartupAndBlock() { + final Object lock = LoginListener.blockingLogins; + if (lock != null) { + synchronized (lock) { + for (;;) { + if (LoginListener.blockingLogins == null) { + return; + } + try { + lock.wait(); + } catch (final InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } + } + } + } + + public static void allowLogins() { + final Object lock = LoginListener.blockingLogins; + synchronized (lock) { + LoginListener.blockingLogins = null; + lock.notifyAll(); + } + } + // Paper end + // Spigot start public class LoginHandler { public void fireEvents() throws Exception { + LoginListener.checkStartupAndBlock(); // Paper - Delay async login events until plugins are ready String playerName = i.getName(); java.net.InetAddress address = ((java.net.InetSocketAddress) networkManager.getSocketAddress()).getAddress(); java.util.UUID uniqueId = i.getId(); @@ -263,7 +304,7 @@ public class LoginListener implements PacketLoginInListener, IUpdatePlayerListBo return event.getResult(); }}; - LoginListener.this.server.processQueue.add(waitable); + LoginListener.this.server.addMainThreadTask(waitable); if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { disconnect(event.getKickMessage()); return; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/MCUtil.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/MCUtil.java new file mode 100644 index 0000000..d4bde8b --- /dev/null +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/MCUtil.java @@ -0,0 +1,142 @@ + +package net.minecraft.server; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.Waitable; +import org.spigotmc.AsyncCatcher; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +public final class MCUtil { + private static final Executor asyncExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build()); + + private MCUtil() {} + + /** + * Quickly generate a stack trace for current location + * + * @return Stacktrace + */ + public static String stack() { + return ExceptionUtils.getFullStackTrace(new Throwable()); + } + + /** + * Quickly generate a stack trace for current location with message + * + * @param str + * @return Stacktrace + */ + public static String stack(String str) { + return ExceptionUtils.getFullStackTrace(new Throwable(str)); + } + + public static T ensureMain(String reason, Supplier run) { + if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().primaryThread) { + new IllegalStateException( "Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace(); + Waitable wait = new Waitable() { + @Override + protected T evaluate() { + return run.get(); + } + }; + MinecraftServer.getServer().addMainThreadTask(wait); + try { + return wait.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + } + return run.get(); + } + + public static double distance(Entity e1, Entity e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + public static double distance(BlockPosition e1, BlockPosition e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) { + return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2)); + } + + public static double distanceSq(Entity e1, Entity e2) { + return distanceSq(e1.locX,e1.locY,e1.locZ, e2.locX,e2.locY,e2.locZ); + } + + public static double distanceSq(BlockPosition pos1, BlockPosition pos2) { + return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ()); + } + + public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); + } + + public static Location toLocation(World world, double x, double y, double z) { + return new Location(world.getWorld(), x, y, z); + } + + public static Location toLocation(World world, BlockPosition pos) { + return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + + public static Location toLocation(Entity entity) { + return new Location(entity.getWorld().getWorld(), entity.locX, entity.locY, entity.locZ); + } + + public static BlockPosition toBlockPosition(Location loc) { + return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + public static boolean isEdgeOfChunk(BlockPosition pos) { + final int modX = pos.getX() & 15; + final int modZ = pos.getZ() & 15; + return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15); + } + + @Nullable + public static Chunk getLoadedChunkWithoutMarkingActive(World world, int x, int z) { + return ((ChunkProviderServer) world.chunkProvider).chunks.get(ChunkCoordIntPair.a(x, z)); + } + + @Nullable + public static Chunk getLoadedChunkWithoutMarkingActive(IChunkProvider provider, int x, int z) { + return ((ChunkProviderServer)provider).chunks.get(ChunkCoordIntPair.a(x, z)); + } + + public static void scheduleAsyncTask(Runnable run) { + asyncExecutor.execute(run); + } + + @Nullable + public static TileEntityHopper getHopper(World world, BlockPosition pos) { + Chunk chunk = world.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4); + if (chunk != null && chunk.getBlockData(pos).getBlock() == Blocks.HOPPER) { + TileEntity tileEntity = chunk.getTileEntityImmediately(pos); + if (tileEntity instanceof TileEntityHopper) { + return (TileEntityHopper) tileEntity; + } + } + return null; + } + + @Nonnull + public static World getNMSWorld(@Nonnull org.bukkit.World world) { + return ((CraftWorld) world).getHandle(); + } + + public static World getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { + return getNMSWorld(entity.getWorld()); + } +} diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/MerchantRecipeList.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/MerchantRecipeList.java new file mode 100644 index 0000000..638f0f5 --- /dev/null +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/MerchantRecipeList.java @@ -0,0 +1,81 @@ +package net.minecraft.server; + +import java.util.ArrayList; + +public class MerchantRecipeList extends ArrayList { + + public MerchantRecipeList() {} + + public MerchantRecipeList(NBTTagCompound nbttagcompound) { + this.a(nbttagcompound); + } + + public MerchantRecipe a(ItemStack itemstack, ItemStack itemstack1, int i) { + if (i > 0 && i < this.size()) { + MerchantRecipe merchantrecipe = this.get(i); + + return this.a(itemstack, merchantrecipe.getBuyItem1()) && (itemstack1 == null && !merchantrecipe.hasSecondItem() || merchantrecipe.hasSecondItem() && this.a(itemstack1, merchantrecipe.getBuyItem2())) && itemstack.count >= merchantrecipe.getBuyItem1().count && (!merchantrecipe.hasSecondItem() || itemstack1.count >= merchantrecipe.getBuyItem2().count) ? merchantrecipe : null; + } else { + for (int j = 0; j < this.size(); ++j) { + MerchantRecipe merchantrecipe1 = this.get(j); + + if (this.a(itemstack, merchantrecipe1.getBuyItem1()) && itemstack.count >= merchantrecipe1.getBuyItem1().count && (!merchantrecipe1.hasSecondItem() && itemstack1 == null || merchantrecipe1.hasSecondItem() && this.a(itemstack1, merchantrecipe1.getBuyItem2()) && itemstack1.count >= merchantrecipe1.getBuyItem2().count)) { + return merchantrecipe1; + } + } + + return null; + } + } + + private boolean a(ItemStack itemstack, ItemStack itemstack1) { + return ItemStack.c(itemstack, itemstack1) && (!itemstack1.hasTag() || itemstack.hasTag() && GameProfileSerializer.a(itemstack1.getTag(), itemstack.getTag(), false)); + } + + public void a(PacketDataSerializer packetdataserializer) { + packetdataserializer.writeByte((byte) (this.size() & 255)); + + for (int i = 0; i < this.size(); ++i) { + MerchantRecipe merchantrecipe = this.get(i); + + packetdataserializer.a(merchantrecipe.getBuyItem1()); + packetdataserializer.a(merchantrecipe.getBuyItem3()); + ItemStack itemstack = merchantrecipe.getBuyItem2(); + + packetdataserializer.writeBoolean(itemstack != null); + if (itemstack != null) { + packetdataserializer.a(itemstack); + } + + packetdataserializer.writeBoolean(merchantrecipe.h()); + packetdataserializer.writeInt(merchantrecipe.e()); + packetdataserializer.writeInt(merchantrecipe.f()); + } + + } + + public void a(NBTTagCompound nbttagcompound) { + NBTTagList nbttaglist = nbttagcompound.getList("Recipes", 10); + + for (int i = 0; i < nbttaglist.size(); ++i) { + NBTTagCompound nbttagcompound1 = nbttaglist.get(i); + + this.add(new MerchantRecipe(nbttagcompound1)); + } + + } + + public NBTTagCompound a() { + NBTTagCompound nbttagcompound = new NBTTagCompound(); + NBTTagList nbttaglist = new NBTTagList(); + + for (int i = 0; i < this.size(); ++i) { + MerchantRecipe merchantrecipe = (MerchantRecipe) this.get(i); + if (merchantrecipe.getBuyItem1() == null) continue; + nbttaglist.add(merchantrecipe.k()); + } + + nbttagcompound.set("Recipes", nbttaglist); + return nbttagcompound; + } +} diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java index 0d0858e..2309fe3 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Queue; import java.util.*; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; // CraftBukkit end @@ -64,8 +65,8 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs public final RollingAverage tps15 = new RollingAverage(60 * 15); protected final ICommandHandler b; protected final Proxy e; - protected final Queue> j = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Spigot, PAIL: Rename - protected final Queue> fastPackets = new java.util.concurrent.ConcurrentLinkedQueue<>(); // eSpigot + protected final Deque> j = new ConcurrentLinkedDeque<>(); // Spigot, PAIL: Rename // Paper - Make size() constant-time + public final Deque> taskQueue = j; // SportBukkit - alias and downcast private final MojangStatisticsGenerator n = new MojangStatisticsGenerator("server", this, az()); private final List p = Lists.newArrayList(); private final ServerPing r = new ServerPing(); @@ -89,7 +90,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs public org.bukkit.command.ConsoleCommandSender console; public org.bukkit.command.RemoteConsoleCommandSender remoteConsole; public ConsoleReader reader; - public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); + //public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); SportBukkit - use Mojang's task queue public int autosavePeriod; public double[] recentTps = new double[3]; // PaperSpigot - Fine have your darn compat with bad plugins @@ -600,6 +601,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs this.g = 0; this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); // CraftBukkit + LoginListener.allowLogins(); // Paper - Allow logins once postworld } protected void saveChunks(boolean flag) throws ExceptionWorldConflict { // CraftBukkit - added throws @@ -677,7 +679,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs // Spigot start if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { LOGGER.info("Saving usercache.json"); - this.Z.c(); + this.Z.c(false); // Paper } //Spigot end @@ -939,15 +941,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs public void B() { SpigotTimings.minecraftSchedulerTimer.startTiming(); // Spigot this.methodProfiler.a("jobs"); - Queue queue = this.j; - - // Spigot start - FutureTask entry; - int count = this.j.size(); - while (count-- > 0 && (entry = this.j.poll()) != null) { - SystemUtils.a(entry, MinecraftServer.LOGGER); - } - // Spigot end + processTasks(); SpigotTimings.minecraftSchedulerTimer.stopTiming(); // Spigot this.methodProfiler.c("levels"); @@ -957,13 +951,6 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs this.server.getScheduler().mainThreadHeartbeat(this.ticks); SpigotTimings.bukkitSchedulerTimer.stopTiming(); // Spigot - // Run tasks that are waiting on processing - SpigotTimings.processQueueTimer.startTiming(); // Spigot - while (!processQueue.isEmpty()) { - processQueue.remove().run(); - } - SpigotTimings.processQueueTimer.stopTiming(); // Spigot - SpigotTimings.chunkIOTickTimer.startTiming(); // Spigot org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.tick(); SpigotTimings.chunkIOTickTimer.stopTiming(); // Spigot @@ -988,6 +975,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs // if (i == 0 || this.getAllowNether()) { WorldServer worldserver = this.worlds.get(i); + if(!worldserver.getWorld().checkTicking()) continue; // SportBukkit this.methodProfiler.a(worldserver.getWorldData().getName()); /* Drop global time updates @@ -1654,6 +1642,48 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs return Thread.currentThread() == this.serverThread; } + // SportBukkit start + public void addMainThreadTask(FutureTask task) { + addMainThreadTask(false, task); + } + + public void addMainThreadTask(boolean priority, FutureTask task) { + synchronized(taskQueue) { + if(priority) { + //taskQueue. + taskQueue.addFirst(task); + } else { + taskQueue.addLast(task); + } + } + } + + public void addMainThreadTask(Runnable task) { + addMainThreadTask(false, task); + } + + public ListenableFuture addMainThreadTask(boolean priority, Runnable task) { + final ListenableFutureTask future = ListenableFutureTask.create(task, null); + addMainThreadTask(priority, future); + return future; + } + + protected void processTasks() { + if(!isMainThread()) throw new IllegalStateException("Tasks must be processed on the main thread"); + for(;;) { + final FutureTask task; + synchronized(taskQueue) { + task = taskQueue.poll(); + } + if(task == null) { + break; + } else { + SystemUtils.a(task, MinecraftServer.LOGGER); + } + } + } + // SportBukkit end + public int aK() { return 256; } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/NBTTagList.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/NBTTagList.java index 1d91aaf..45ea25d 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/NBTTagList.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/NBTTagList.java @@ -12,7 +12,7 @@ import org.apache.logging.log4j.Logger; public class NBTTagList extends NBTBase { private static final Logger b = LogManager.getLogger(); - private List list = Lists.newArrayList(); + public List list = Lists.newArrayList(); // Paper private byte type = 0; public NBTTagList() {} diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/NavigationAbstract.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/NavigationAbstract.java index b69e9d7..f51cd35 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/NavigationAbstract.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/NavigationAbstract.java @@ -5,7 +5,7 @@ import java.util.List; public abstract class NavigationAbstract { - protected EntityInsentient b; + protected EntityInsentient b; public Entity getEntity() { return b; } // Paper - OBFHELPER protected World c; protected PathEntity d; protected double e; @@ -38,6 +38,7 @@ public abstract class NavigationAbstract { } public PathEntity a(BlockPosition blockposition) { + if (!getEntity().getWorld().getWorldBorder().isInBounds(blockposition)) return null; // Paper - don't path out of world border if (!this.b()) { return null; } else { @@ -72,6 +73,7 @@ public abstract class NavigationAbstract { this.c.methodProfiler.a("pathfind"); BlockPosition blockposition = (new BlockPosition(this.b)).up(); + if (!getEntity().getWorld().getWorldBorder().isInBounds(blockposition)) return null; // Paper - don't path out of world border int i = (int) (f + 16.0F); ChunkCache chunkcache = new ChunkCache(this.c, blockposition.a(-i, -i, -i), blockposition.a(i, i, i), 0); PathEntity pathentity = this.j.a(chunkcache, this.b, entity, f); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/PacketPlayOutSpawnEntity.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/PacketPlayOutSpawnEntity.java index b21e8f4..5662606 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/PacketPlayOutSpawnEntity.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/PacketPlayOutSpawnEntity.java @@ -81,33 +81,18 @@ public class PacketPlayOutSpawnEntity implements Packet { this.d = packetdataserializer.readInt(); this.h = packetdataserializer.readByte(); this.i = packetdataserializer.readByte(); + this.k = packetdataserializer.readInt(); + if (this.k > 0) { + this.e = packetdataserializer.readShort(); + this.f = packetdataserializer.readShort(); + this.g = packetdataserializer.readShort(); + } + } public void b(PacketDataSerializer packetdataserializer) throws IOException { packetdataserializer.b(this.a); packetdataserializer.writeByte(this.j); - switch (this.k) { - case 0: { - this.d -= 32; - this.i = 128; - break; - } - case 1: { - this.b += 32; - this.i = 64; - break; - } - case 2: { - this.d += 32; - this.i = 0; - break; - } - case 3: { - this.b -= 32; - this.i = 192; - break; - } - } packetdataserializer.writeInt(this.b); packetdataserializer.writeInt(this.c); packetdataserializer.writeInt(this.d); @@ -119,6 +104,7 @@ public class PacketPlayOutSpawnEntity implements Packet { packetdataserializer.writeShort(this.f); packetdataserializer.writeShort(this.g); } + } public void b_old(PacketDataSerializer packetdataserializer) throws IOException { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java index 4614a47..b29c5dc 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; // CraftBukkit start import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.HashSet; @@ -79,11 +80,12 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList private int g; private boolean h; private int i; - private long j; - private long k; + private long j; private void setLastPing(long lastPing) { this.j = lastPing;}; private long getLastPing() { return this.j;}; // Paper - OBFHELPER + private long k; private void setKeepAliveID(long keepAliveID) { this.k = keepAliveID;}; private long getKeepAliveID() {return this.k; }; // Paper - OBFHELPER // CraftBukkit start - multithreaded fields private volatile int chatThrottle; private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); + private final AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits // CraftBukkit end private int m; private IntHashMap n = new IntHashMap(); @@ -142,6 +144,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList this.minecraftServer.methodProfiler.b(); // CraftBukkit start for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ; + if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable /* Use thread-safe field access instead if (this.chatThrottle > 0) { --this.chatThrottle; @@ -648,6 +651,8 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList double d3 = d0 * d0 + d1 * d1 + d2 * d2; if (d3 > 36.0D) { + if (worldserver.isChunkLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4, true)) // Paper - Fix block break desync - Don't send for unloaded chunks + this.sendPacket(new PacketPlayOutBlockChange(worldserver, blockposition)); // Paper - Fix block break desync return; } else if (blockposition.getY() >= this.minecraftServer.getMaxBuildHeight()) { return; @@ -761,7 +766,24 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } } - if (!cancelled) { + if (cancelled) { + this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 + // SportPaper start - Fix client desync + if (itemstack.getItem() == Item.getItemOf(Blocks.WATERLILY)) { + MovingObjectPosition movingObjectPosition1 = this.player.world.rayTrace(vec3d, vec3d1, true, false, false); + if (movingObjectPosition1 != null) { + BlockPosition blockPosition = movingObjectPosition1.a().up(); + org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(worldserver, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()).update(true, false); + } + } else if (itemstack.getItem() == Items.BUCKET) { + MovingObjectPosition movingObjectPosition1 = this.player.world.rayTrace(vec3d, vec3d1, true, false, false); + if (movingObjectPosition1 != null) { + BlockPosition blockPosition = movingObjectPosition1.a(); + org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(worldserver, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()).update(true, false); + } + } + // SportPaper end + } else { this.player.playerInteractManager.useItem(this.player, this.player.world, itemstack); } } @@ -1019,7 +1041,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } }; - this.minecraftServer.processQueue.add(waitable); + this.minecraftServer.addMainThreadTask(waitable); try { waitable.get(); @@ -1049,7 +1071,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } else if (getPlayer().isConversing()) { // Spigot start final String message = s; - this.minecraftServer.processQueue.add( new Waitable() + this.minecraftServer.addMainThreadTask( new Waitable() { @Override protected Object evaluate() @@ -1096,7 +1118,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } }; - this.minecraftServer.processQueue.add(waitable); + this.minecraftServer.addMainThreadTask(waitable); try { waitable.get(); @@ -1133,7 +1155,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList return null; } }; - minecraftServer.processQueue.add(wait); + minecraftServer.addMainThreadTask(wait); try { wait.get(); return; @@ -1179,7 +1201,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList return null; }}; if (async) { - minecraftServer.processQueue.add(waitable); + minecraftServer.addMainThreadTask(waitable); } else { waitable.run(); } @@ -1435,6 +1457,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } if (event.isCancelled()) { + this.player.updateInventory(this.player.activeContainer); // Paper - Refresh player inventory return; } // CraftBukkit end @@ -1565,7 +1588,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } if (packetplayinwindowclick.c() == 0 || packetplayinwindowclick.c() == 1) { action = InventoryAction.NOTHING; // Don't want to repeat ourselves - if (packetplayinwindowclick.b() == -999) { + if (packetplayinwindowclick.b() < 0) { // Paper - GH-404 if (player.inventory.getCarried() != null) { action = packetplayinwindowclick.c() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; } @@ -2006,6 +2029,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList } + private long getCurrentMillis() { return d(); } // Paper - OBFHELPER private long d() { return System.nanoTime() / 1000000L; } @@ -2028,7 +2052,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList public void a(PacketPlayInTabComplete packetplayintabcomplete) { PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.u()); // CraftBukkit start - if (chatSpamField.addAndGet(this, 10) > 500 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { + if (tabSpamLimiter.addAndGet(PaperSpigotConfig.tabSpamIncrement) > PaperSpigotConfig.tabSpamLimit && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { // Paper start - split and make configurable this.disconnect("disconnect.spam"); return; } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java index 62ebc3c..41f2cd5 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java @@ -157,6 +157,7 @@ public abstract class PlayerList { playerconnection.sendPacket(new PacketPlayOutSpawnPosition(blockposition)); playerconnection.sendPacket(new PacketPlayOutAbilities(entityplayer.abilities)); playerconnection.sendPacket(new PacketPlayOutHeldItemSlot(entityplayer.inventory.itemInHandIndex)); + playerconnection.sendPacket(new PacketPlayOutEntityStatus(entityplayer, (byte) (worldserver.getGameRules().getBoolean("reducedDebugInfo") ? 22 : 23))); // Paper - fix this rule not being initialized on the client entityplayer.getStatisticManager().d(); entityplayer.getStatisticManager().updateStatistics(entityplayer); this.sendScoreboard((ScoreboardServer) worldserver.getScoreboard(), entityplayer); @@ -561,6 +562,7 @@ public abstract class PlayerList { BlockPosition blockposition1; // CraftBukkit start - fire PlayerRespawnEvent + this.players.add(entityplayer); // Add player back to this list earlier than vanilla does if (location == null) { boolean isBedSpawn = false; CraftWorld cworld = (CraftWorld) this.server.server.getWorld(entityplayer.spawnWorld); @@ -622,10 +624,12 @@ public abstract class PlayerList { entityplayer.playerConnection.sendPacket(new PacketPlayOutExperience(entityplayer.exp, entityplayer.expTotal, entityplayer.expLevel)); this.b(entityplayer, worldserver); - if (!entityplayer.playerConnection.isDisconnected()) { + // Don't re-add player to player list if disconnected + if (entityplayer.playerConnection.isDisconnected()) { + this.players.remove(entityplayer); + } else { worldserver.getPlayerChunkMap().addPlayer(entityplayer); worldserver.addEntity(entityplayer); - this.players.add(entityplayer); this.playersByName.put(entityplayer.getName(), entityplayer); // Spigot this.j.put(entityplayer.getUniqueID(), entityplayer); } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/RegionFileCache.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/RegionFileCache.java index 9065094..71e5687 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/RegionFileCache.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/RegionFileCache.java @@ -1,16 +1,19 @@ package net.minecraft.server; import com.google.common.collect.Maps; +import org.github.paperspigot.PaperSpigotConfig; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; public class RegionFileCache { - public static final Map a = Maps.newHashMap(); // Spigot - private -> public + public static final Map a = new LinkedHashMap(PaperSpigotConfig.regionFileCacheSize, 0.75f, true); // Spigot - private -> public, Paper - HashMap -> LinkedHashMap // PaperSpigot start public static synchronized RegionFile a(File file, int i, int j) { @@ -30,8 +33,8 @@ public class RegionFileCache { file1.mkdirs(); } - if (RegionFileCache.a.size() >= 256) { - a(); + if (RegionFileCache.a.size() >= PaperSpigotConfig.regionFileCacheSize) { // Paper + trimCache(); // Paper } RegionFile regionfile1 = new RegionFile(file2); @@ -41,6 +44,21 @@ public class RegionFileCache { } } + // Paper Start + private static synchronized void trimCache() { + Iterator> itr = RegionFileCache.a.entrySet().iterator(); + int count = RegionFileCache.a.size() - PaperSpigotConfig.regionFileCacheSize; + while (count-- >= 0 && itr.hasNext()) { + try { + itr.next().getValue().c(); + } catch (IOException ioexception) { + ioexception.printStackTrace(); + } + itr.remove(); + } + } + // Paper End + public static synchronized void a() { for (RegionFile regionfile : RegionFileCache.a.values()) { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java index be617ce..2c2665e 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java @@ -21,6 +21,7 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.io.IOException; import java.net.InetAddress; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -69,7 +70,7 @@ public class ServerConnection { this.d = true; } - // KigPaper start - Paper-0117 by Aikar + // Paper start - prevent blocking on adding a new network manager while the server is ticking private final List pending = Collections.synchronizedList(Lists.newArrayList()); private void addPending() { synchronized (pending) { @@ -77,7 +78,7 @@ public class ServerConnection { pending.clear(); } } - // KigPaper end + // Paper end public void a(InetAddress inetaddress, int i) throws IOException { List list = this.g; @@ -115,7 +116,7 @@ public class ServerConnection { NetworkManager networkmanager = new NetworkManager(EnumProtocolDirection.SERVERBOUND); //ServerConnection.this.h.add(networkmanager); - pending.add(networkmanager); // KigPaper + pending.add(networkmanager); // Paper channel.pipeline().addLast("packet_handler", networkmanager); networkmanager.a(new HandshakeListener(ServerConnection.this.f, networkmanager)); } @@ -126,14 +127,26 @@ public class ServerConnection { public void b() { this.d = false; + // CraftBukkit start - handle processQueue while closing channels to prevent deadlock + ArrayList futures = new ArrayList(); + for (ChannelFuture channelfuture : this.g) { - try { - channelfuture.channel().close().sync(); - } catch (InterruptedException interruptedexception) { - ServerConnection.e.error("Interrupted whilst closing channel"); - } + futures.add(channelfuture.channel().close()); } + for(;;) { + futures.removeIf(java.util.concurrent.Future::isDone); + f.processTasks(); + + if(futures.isEmpty()) break; + + try { + Thread.sleep(50); + } catch(InterruptedException e) { + e.printStackTrace(); + } + } + // CraftBukkit end } public void processFastPackets() { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/SpawnerCreature.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/SpawnerCreature.java index a0c3f76..530a75c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/SpawnerCreature.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/SpawnerCreature.java @@ -46,6 +46,7 @@ public final class SpawnerCreature { // Spigot end public int a(WorldServer worldserver, boolean flag, boolean flag1, boolean flag2) { + org.spigotmc.AsyncCatcher.catchOp("check for eligible spawn chunks"); // Paper - At least until we figure out what is calling this async if (!flag && !flag1) { return 0; } else { @@ -122,8 +123,10 @@ public final class SpawnerCreature { // CraftBukkit end if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2)) { + /* Paper start - As far as I can tell neither of these are even used k = worldserver.a(enumcreaturetype.a()); int l1 = limit * i / a; // CraftBukkit - use per-world limits + */ // Paper end if ((mobcnt = getEntityCount(worldserver, enumcreaturetype.a())) <= limit * i / 289) { // TacoSpigot - use 17x17 like vanilla (a at top of file) Iterator iterator1 = this.b.iterator(); diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntityFurnace.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntityFurnace.java index 324981c..f0a7014 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntityFurnace.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntityFurnace.java @@ -188,7 +188,7 @@ public class TileEntityFurnace extends TileEntityContainer implements IUpdatePla if (this.isBurning() && this.canBurn()) { this.cookTime += elapsedTicks; if (this.cookTime >= this.cookTimeTotal) { - this.cookTime = 0; + this.cookTime -= this.cookTimeTotal; // Paper this.cookTimeTotal = this.a(this.items[0]); this.burn(); flag1 = true; diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java index e71a71f..31d387c 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java @@ -164,7 +164,7 @@ public class TileEntitySkull extends TileEntity { } else { executor.execute(() -> { final GameProfile profile1 = skinCache.getUnchecked(gameprofile.getName().toLowerCase()); - MinecraftServer.getServer().processQueue.add(() -> { + MinecraftServer.getServer().addMainThreadTask(() -> { if (profile1 == null) { callback.apply(gameprofile); } else { diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/UserCache.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/UserCache.java index 6deb220..1637404 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/UserCache.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/UserCache.java @@ -35,6 +35,7 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; public class UserCache { @@ -82,7 +83,7 @@ public class UserCache { }; minecraftserver.getGameProfileRepository().findProfilesByNames(new String[] { s}, Agent.MINECRAFT, profilelookupcallback); - if (!minecraftserver.getOnlineMode() && agameprofile[0] == null) { + if (!minecraftserver.getOnlineMode() && agameprofile[0] == null && !StringUtils.isBlank(s)) { // Paper - Don't lookup a profile with a blank UUID uuid = EntityHuman.a(new GameProfile(null, s)); GameProfile gameprofile = new GameProfile(uuid, s); @@ -96,7 +97,7 @@ public class UserCache { this.a(gameprofile, null); } - private void a(GameProfile gameprofile, Date date) { + private synchronized void a(GameProfile gameprofile, Date date) { // Paper - synchronize UUID uuid = gameprofile.getId(); if (date == null) { @@ -110,8 +111,9 @@ public class UserCache { String s = gameprofile.getName().toLowerCase(Locale.ROOT); UserCache.UserCacheEntry usercache_usercacheentry = new UserCache.UserCacheEntry(gameprofile, date, null); - if (this.d.containsKey(uuid)) { - UserCache.UserCacheEntry usercache_usercacheentry1 = this.d.get(uuid); + //if (this.d.containsKey(uuid)) { // Paper + UserCache.UserCacheEntry usercache_usercacheentry1 = this.d.get(uuid); + if (usercache_usercacheentry1 != null) { // Paper this.c.remove(usercache_usercacheentry1.a().getName().toLowerCase(Locale.ROOT)); this.e.remove(gameprofile); @@ -123,7 +125,7 @@ public class UserCache { if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.c(); // Spigot - skip saving if disabled } - public GameProfile getProfile(String s) { + public synchronized GameProfile getProfile(String s) { // Paper - synchronize String s1 = s.toLowerCase(Locale.ROOT); UserCache.UserCacheEntry usercache_usercacheentry = this.c.get(s1); @@ -152,13 +154,14 @@ public class UserCache { return usercache_usercacheentry == null ? null : usercache_usercacheentry.a(); } - public String[] a() { + public synchronized String[] a() { // Paper - synchronize ArrayList arraylist = Lists.newArrayList(this.c.keySet()); return (String[]) arraylist.toArray(new String[0]); } - public GameProfile a(UUID uuid) { + public GameProfile getProfile(UUID uuid) { return a(uuid); } // Paper - OBFHELPER + public synchronized GameProfile a(UUID uuid) { // Paper - synchronize UserCache.UserCacheEntry usercache_usercacheentry = this.d.get(uuid); return usercache_usercacheentry == null ? null : usercache_usercacheentry.a(); @@ -195,33 +198,43 @@ public class UserCache { this.a(usercache_usercacheentry.a(), usercache_usercacheentry.b()); } } - } catch (FileNotFoundException filenotfoundexception) { - // Spigot Start - } catch (com.google.gson.JsonSyntaxException ex) { + } catch (Exception ex) { + // SportPaper - Catch all UserCache exceptions in one and always delete JsonList.a.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." ); this.g.delete(); - // Spigot End - } catch (JsonParseException ignored) { } finally { IOUtils.closeQuietly(bufferedreader); } } + // Paper start public void c() { + c(true); + } + public void c(boolean asyncSave) { String s = this.b.toJson(this.a(org.spigotmc.SpigotConfig.userCacheCap)); - BufferedWriter bufferedwriter = null; + Runnable save = () -> { + BufferedWriter bufferedwriter = null; - try { - bufferedwriter = Files.newWriter(this.g, Charsets.UTF_8); - bufferedwriter.write(s); - return; - } catch (FileNotFoundException filenotfoundexception) { - return; - } catch (IOException ignored) { - } finally { - IOUtils.closeQuietly(bufferedwriter); + try { + bufferedwriter = Files.newWriter(this.g, Charsets.UTF_8); + bufferedwriter.write(s); + return; + } catch (FileNotFoundException filenotfoundexception) { + return; + } catch (IOException ioexception) { + ; + } finally { + IOUtils.closeQuietly(bufferedwriter); + } + }; + if (asyncSave) { + MCUtil.scheduleAsyncTask(save); + } else { + save.run(); } + // Paper end } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/World.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/World.java index afed21d..fdcbe27 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/World.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/World.java @@ -24,6 +24,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.bukkit.util.Vector; + +import javax.annotation.Nullable; // PaperSpigot end // CraftBukkit start @@ -438,7 +440,7 @@ public abstract class World implements IBlockAccess { // 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); + blockstate = world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot this.capturedBlockStates.add(blockstate); } // CraftBukkit end @@ -488,7 +490,7 @@ public abstract class World implements IBlockAccess { // 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 + if ((flag & 2) != 0 && (!this.isClientSide || (flag & 4) == 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); } @@ -769,6 +771,7 @@ public abstract class World implements IBlockAccess { if (blockposition.getY() >= 256) { blockposition = new BlockPosition(blockposition.getX(), 255, blockposition.getZ()); } + if (!this.isLoaded(blockposition)) return 0; // Paper Chunk chunk = this.getChunkAtWorldCoords(blockposition); @@ -921,7 +924,8 @@ public abstract class World implements IBlockAccess { int i1 = MathHelper.floor(vec3d.b); int j1 = MathHelper.floor(vec3d.c); BlockPosition blockposition = new BlockPosition(l, i1, j1); - IBlockData iblockdata = this.getType(blockposition); + IBlockData iblockdata = this.getTypeIfLoaded(blockposition); // SportPaper + if (iblockdata == null) return null; // SportPaper Block block = iblockdata.getBlock(); if ((!flag1 || block.a(this, blockposition, iblockdata) != null) && block.a(iblockdata, flag)) { @@ -1023,7 +1027,8 @@ public abstract class World implements IBlockAccess { i1 = MathHelper.floor(vec3d.b) - (enumdirection == EnumDirection.UP ? 1 : 0); j1 = MathHelper.floor(vec3d.c) - (enumdirection == EnumDirection.SOUTH ? 1 : 0); blockposition = new BlockPosition(l, i1, j1); - IBlockData iblockdata1 = this.getType(blockposition); + IBlockData iblockdata1 = this.getTypeIfLoaded(blockposition); // SportPaper + if (iblockdata1 == null) return null; // SportPaper Block block1 = iblockdata1.getBlock(); if (!flag1 || block1.a(this, blockposition, iblockdata1) != null) { @@ -1118,6 +1123,18 @@ public abstract class World implements IBlockAccess { public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason org.spigotmc.AsyncCatcher.catchOp( "entity add"); // Spigot if (entity == null) return false; + + // Workaround for https://bugs.mojang.com/browse/MC-72248 + // If an EntityFallingBlock spawns inside a block of the same type, the client will ALWAYS remove the block, + // whereas the server will only remove it if ticksLived is 0. This creates invisible blocks on the client. + // The imperfect workaround is to not spawn the falling block at all if it will cause such a desync. + if(entity instanceof EntityFallingBlock && ((EntityFallingBlock) entity).getBlock().getBlock() == this.getType(new BlockPosition(entity)).getBlock()) { + EntityFallingBlock fallingBlock = (EntityFallingBlock) entity; + if(fallingBlock.ticksLived != 0 && fallingBlock.getBlock().getBlock() == this.getType(new BlockPosition(fallingBlock)).getBlock()) { + return false; + } + } + // CraftBukkit end int i = MathHelper.floor(entity.locX / 16.0D); int j = MathHelper.floor(entity.locZ / 16.0D); @@ -1372,6 +1389,7 @@ public abstract class World implements IBlockAccess { // Spigot end if (entity instanceof EntityItem) return arraylist; // PaperSpigot - Optimize item movement + if (entity instanceof EntityArmorStand && !entity.world.paperSpigotConfig.armorStandEntityLookups) return arraylist; // Paper if (entity instanceof EntityArmorStand) return arraylist; // TacoSpigot - Optimize armor stand movement if (entity instanceof EntityTNTPrimed) return arraylist; // TacoSpigot - Optimize tnt entity movement if (entity instanceof EntityFallingBlock) return arraylist; // TacoSpigot - Optimize falling block movement @@ -1892,7 +1910,7 @@ public abstract class World implements IBlockAccess { } int k = MathHelper.floor(entity.locX / 16.0D); - int l = MathHelper.floor(entity.locY / 16.0D); + int l = Math.min(15, Math.max(0, MathHelper.floor(entity.locY / 16.0D))); // Paper - stay consistent with chunk add/remove behavior int i1 = MathHelper.floor(entity.locZ / 16.0D); if (!entity.ad || entity.ae != k || entity.af != l || entity.ag != i1) { @@ -1926,6 +1944,33 @@ public abstract class World implements IBlockAccess { return this.a(axisalignedbb, (Entity) null); } + // Paper start - Based on method below + /** + * @param axisalignedbb area to search within + * @param entity causing the action ex. block placer + * @return if there are no visible players colliding + */ + public boolean checkNoVisiblePlayerCollisions(AxisAlignedBB axisalignedbb, @Nullable Entity entity) { + List list = this.getEntities((Entity) null, axisalignedbb); + + for (int i = 0; i < list.size(); ++i) { + Entity entity1 = (Entity) list.get(i); + + if (entity instanceof EntityPlayer && entity1 instanceof EntityPlayer) { + if (!((EntityPlayer) entity).getBukkitEntity().canSee(((EntityPlayer) entity1).getBukkitEntity())) { + continue; + } + } + + if (!entity1.dead && entity1.blocksEntitySpawning()) { + return false; + } + } + + return true; + } + // Paper end + public boolean a(AxisAlignedBB axisalignedbb, Entity entity) { List list = this.getEntities(null, axisalignedbb); @@ -2115,6 +2160,35 @@ public abstract class World implements IBlockAccess { } // ImHacking end + // CraftBukkit start + public Vec3D getLargestBlockIntersection(AxisAlignedBB aabb, Material material) { + int xMin = MathHelper.floor(aabb.a); + int xMax = MathHelper.floor(aabb.d + 1.0D); + int yMin = MathHelper.floor(aabb.b); + int yMax = MathHelper.floor(aabb.e + 1.0D); + int zMin = MathHelper.floor(aabb.c); + int zMax = MathHelper.floor(aabb.f + 1.0D); + double maxVolume = 0; + Vec3D block = null; + for (int x = xMin; x < xMax; ++x) { + for (int y = yMin; y < yMax; ++y) { + for (int z = zMin; z < zMax; ++z) { + IBlockData type = this.getType(new BlockPosition(x, y, z)); + if (material == type.getBlock().getMaterial()) { + AxisAlignedBB i = new AxisAlignedBB(x, x+1, y, y+1, z, z+1).a(aabb); + double volume = (i.d - i.a) * (i.e - i.b) * (i.f - i.c); + if(volume > maxVolume) { + maxVolume = volume; + block = new Vec3D(x, y, z); + } + } + } + } + } + return block; + } + // CraftBukkit end + public boolean b(AxisAlignedBB axisalignedbb, Material material) { int i = MathHelper.floor(axisalignedbb.a); int j = MathHelper.floor(axisalignedbb.d + 1.0D); @@ -2991,7 +3065,7 @@ public abstract class World implements IBlockAccess { AxisAlignedBB axisalignedbb = flag ? null : block.a(this, blockposition, block.getBlockData()); // CraftBukkit start - store default return - boolean defaultReturn = (axisalignedbb == null || this.a(axisalignedbb, entity)) && (block1.getMaterial() == Material.ORIENTABLE && block == Blocks.ANVIL || block1.getMaterial().isReplaceable() && block.canPlace(this, blockposition, enumdirection, itemstack)); + boolean defaultReturn = axisalignedbb != null && !this.checkNoVisiblePlayerCollisions(axisalignedbb, entity) ? false : (block1.getMaterial() == Material.ORIENTABLE && block == Blocks.ANVIL ? true : block1.getMaterial().isReplaceable() && block.canPlace(this, blockposition, enumdirection, itemstack)); // Paper - Use our entity search BlockCanBuildEvent event = new BlockCanBuildEvent(this.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftMagicNumbers.getId(block), defaultReturn); this.getServer().getPluginManager().callEvent(event); @@ -3095,7 +3169,7 @@ public abstract class World implements IBlockAccess { for (EntityHuman player : this.players) { EntityHuman entityhuman1 = player; // CraftBukkit start - Fixed an NPE - if (entityhuman1 == null || entityhuman1.dead) { + if (entityhuman1 == null || !entityhuman1.ad()) { // CraftBukkit allow for more complex logic by using the "is alive" method continue; } // CraftBukkit end @@ -3141,7 +3215,7 @@ public abstract class World implements IBlockAccess { for (EntityHuman player : this.players) { EntityHuman entityhuman1 = player; // CraftBukkit start - Fixed an NPE - if (entityhuman1 == null || entityhuman1.dead || !entityhuman1.affectsSpawning) { + if (entityhuman1 == null || !entityhuman1.ad() || !entityhuman1.affectsSpawning) { // CraftBukkit allow for more complex logic by using the "is alive" method continue; } // CraftBukkit end diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldBorder.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldBorder.java index fa397dc..ec8a9f0 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldBorder.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldBorder.java @@ -29,6 +29,7 @@ public class WorldBorder { this.l = 5; } + public boolean isInBounds(BlockPosition blockposition) { return a(blockposition); } // Paper - OBFHELPER public boolean a(BlockPosition blockposition) { return (double) (blockposition.getX() + 1) > this.b() && (double) blockposition.getX() < this.d() && (double) (blockposition.getZ() + 1) > this.c() && (double) blockposition.getZ() < this.e(); } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldManager.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldManager.java index 2db25a9..9e54726 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldManager.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldManager.java @@ -68,6 +68,7 @@ public class WorldManager implements IWorldAccess { if (entity instanceof EntityHuman) entityhuman = (EntityHuman) entity; // CraftBukkit end + PacketPlayOutBlockBreakAnimation packet = null; // SportPaper - cache packet while (iterator.hasNext()) { EntityPlayer entityplayer = (EntityPlayer) iterator.next(); @@ -83,7 +84,10 @@ public class WorldManager implements IWorldAccess { // CraftBukkit end if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) { - entityplayer.playerConnection.sendPacket(new PacketPlayOutBlockBreakAnimation(i, blockposition, j)); + // SportPaper start + if (packet == null) packet = new PacketPlayOutBlockBreakAnimation(i, blockposition, j); + entityplayer.playerConnection.sendPacket(packet); + // SportPaper end } } } diff --git a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java index 0912ca7..fed25d4 100644 --- a/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java +++ b/TacoSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java @@ -959,15 +959,17 @@ public class WorldServer extends World implements IAsyncTaskHandler { this.chunkProvider.saveChunks(flag, iprogressupdate); // CraftBukkit - ArrayList -> Collection - Collection arraylist = this.chunkProviderServer.a(); + /* //Paper start Collection arraylist = chunkproviderserver.a(); + Iterator iterator = arraylist.iterator(); - for (Object value : arraylist) { - Chunk chunk = (Chunk) value; + while (iterator.hasNext()) { + Chunk chunk = (Chunk) iterator.next(); if (chunk != null && !this.manager.a(chunk.locX, chunk.locZ)) { this.chunkProviderServer.queueUnload(chunk.locX, chunk.locZ); } } + */// Paper end } } diff --git a/TacoSpigot-Server/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java b/TacoSpigot-Server/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java deleted file mode 100644 index 851de7e..0000000 --- a/TacoSpigot-Server/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; -import java.nio.charset.Charset; - -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.helpers.Booleans; -import org.apache.logging.log4j.core.helpers.Loader; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.util.PropertiesUtil; - -/** - * ConsoleAppender appends log events to System.out or - * System.err using a layout specified by the user. The - * default target is System.out. - * @doubt accessing System.out or .err as a byte stream instead of a writer - * bypasses the JVM's knowledge of the proper encoding. (RG) Encoding - * is handled within the Layout. Typically, a Layout will generate a String - * and then call getBytes which may use a configured encoding or the system - * default. OTOH, a Writer cannot print byte streams. - */ -@Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true) -public final class ConsoleAppender extends AbstractOutputStreamAppender { - - private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; - private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); - - /** - * Enumeration of console destinations. - */ - public enum Target { - /** Standard output. */ - SYSTEM_OUT, - /** Standard error output. */ - SYSTEM_ERR - } - - private ConsoleAppender(final String name, final Layout layout, final Filter filter, - final OutputStreamManager manager, - final boolean ignoreExceptions) { - super(name, layout, filter, ignoreExceptions, true, manager); - } - - /** - * Create a Console Appender. - * @param layout The layout to use (required). - * @param filter The Filter or null. - * @param t The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". - * @param follow If true will follow changes to the underlying output stream. - * @param name The name of the Appender (required). - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @return The ConsoleAppender. - */ - @PluginFactory - public static ConsoleAppender createAppender( - @PluginElement("Layout") Layout layout, - @PluginElement("Filters") final Filter filter, - @PluginAttribute("target") final String t, - @PluginAttribute("name") final String name, - @PluginAttribute("follow") final String follow, - @PluginAttribute("ignoreExceptions") final String ignore) { - if (name == null) { - LOGGER.error("No name provided for ConsoleAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createLayout(null, null, null, null, null); - } - final boolean isFollow = Boolean.parseBoolean(follow); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t); - return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, layout), ignoreExceptions); - } - - private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout layout) { - final String type = target.name(); - final OutputStream os = getOutputStream(follow, target); - return OutputStreamManager.getManager(target.name() + "." + follow, new FactoryData(os, type, layout), factory); - } - - private static OutputStream getOutputStream(final boolean follow, final Target target) { - final String enc = Charset.defaultCharset().name(); - PrintStream printStream = null; - try { - printStream = target == Target.SYSTEM_OUT ? - follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out : - follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err; - } catch (final UnsupportedEncodingException ex) { // should never happen - throw new IllegalStateException("Unsupported default encoding " + enc, ex); - } - final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); - if (!propsUtil.getStringProperty("os.name").startsWith("Windows") || - propsUtil.getBooleanProperty("log4j.skipJansi")) { - return printStream; - } - try { - final ClassLoader loader = Loader.getClassLoader(); - // We type the parameter as a wildcard to avoid a hard reference to Jansi. - final Class clazz = loader.loadClass(JANSI_CLASS); - final Constructor constructor = clazz.getConstructor(OutputStream.class); - return (OutputStream) constructor.newInstance(printStream); - } catch (final ClassNotFoundException cnfe) { - LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); - } catch (final NoSuchMethodException nsme) { - LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); - } catch (final Throwable ex) { // CraftBukkit - Exception -> Throwable - LOGGER.warn("Unable to instantiate {}", JANSI_CLASS); - } - return printStream; - } - - /** - * An implementation of OutputStream that redirects to the current System.err. - */ - private static class SystemErrStream extends OutputStream { - public SystemErrStream() { - } - - @Override - public void close() { - // do not close sys err! - } - - @Override - public void flush() { - System.err.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.err.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) - throws IOException { - System.err.write(b, off, len); - } - - @Override - public void write(final int b) { - System.err.write(b); - } - } - - /** - * An implementation of OutputStream that redirects to the current System.out. - */ - private static class SystemOutStream extends OutputStream { - public SystemOutStream() { - } - - @Override - public void close() { - // do not close sys out! - } - - @Override - public void flush() { - System.out.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - System.out.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) - throws IOException { - System.out.write(b, off, len); - } - - @Override - public void write(final int b) throws IOException { - System.out.write(b); - } - } - - /** - * Data to pass to factory method. - */ - private static class FactoryData { - private final OutputStream os; - private final String type; - private final Layout layout; - - /** - * Constructor. - * @param os The OutputStream. - * @param type The name of the target. - * @param layout A Serializable layout - */ - public FactoryData(final OutputStream os, final String type, final Layout layout) { - this.os = os; - this.type = type; - this.layout = layout; - } - } - - /** - * Factory to create the Appender. - */ - private static class ConsoleManagerFactory implements ManagerFactory { - - /** - * Create an OutputStreamManager. - * @param name The name of the entity to manage. - * @param data The data required to create the entity. - * @return The OutputStreamManager - */ - @Override - public OutputStreamManager createManager(final String name, final FactoryData data) { - return new OutputStreamManager(data.os, data.type, data.layout); - } - } - -} \ No newline at end of file diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index 36e45b8..f98f3da 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -1,9 +1,7 @@ package org.bukkit.craftbukkit; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; +import java.util.*; import net.minecraft.server.*; @@ -13,6 +11,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Entity; import org.bukkit.ChunkSnapshot; import org.bukkit.entity.HumanEntity; @@ -79,6 +78,29 @@ public class CraftChunk implements Chunk { return new CraftBlock(this, (getX() << 4) | (x & 0xF), y, (getZ() << 4) | (z & 0xF)); } + @Override + public Set getBlocks(org.bukkit.Material material) { + Set blocks = new HashSet(); + + net.minecraft.server.Block nmsBlock = CraftMagicNumbers.getBlock(material); + net.minecraft.server.Chunk chunk = getHandle(); + + for(ChunkSection section : chunk.getSections()) { + if(section == null || section.a()) continue; // ChunkSection.a() -> true if section is empty + + char[] blockIds = section.getIdArray(); + for(int i = 0; i < blockIds.length; i++) { + // This does a lookup in the block registry, but does not create any objects, so should be pretty efficient + IBlockData blockData = net.minecraft.server.Block.d.a(blockIds[i]); + if(blockData != null && blockData.getBlock() == nmsBlock) { + blocks.add(getBlock(i & 0xf, section.getYPosition() | (i >> 8), (i >> 4) & 0xf)); + } + } + } + + return blocks; + } + @Override public Entity[] getEntities() { int count = 0, index = 0; diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index ff2626c..ac5e8b1 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -82,12 +82,7 @@ import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.permissions.Permissible; import org.bukkit.permissions.Permission; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginLoadOrder; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.ServicesManager; -import org.bukkit.plugin.SimplePluginManager; -import org.bukkit.plugin.SimpleServicesManager; +import org.bukkit.plugin.*; import org.bukkit.plugin.java.JavaPluginLoader; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.potion.Potion; @@ -649,7 +644,7 @@ public final class CraftServer implements Server { return dispatchCommand(fSender, fCommandLine); } }; - net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait); + net.minecraft.server.MinecraftServer.getServer().addMainThreadTask(wait); try { return wait.get(); } catch (InterruptedException e) { @@ -731,8 +726,18 @@ public final class CraftServer implements Server { world.tacoSpigotConfig.init(); // TacoSpigot } + Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper pluginManager.clearPlugins(); commandMap.clearCommands(); + + // Paper start + for (Plugin plugin : pluginClone) { + entityMetadata.removeAll(plugin); + worldMetadata.removeAll(plugin); + playerMetadata.removeAll(plugin); + } + // Paper end + resetRecipes(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot org.github.paperspigot.PaperSpigotConfig.registerCommands(); // PaperSpigot @@ -1787,6 +1792,46 @@ public final class CraftServer implements Server { return CraftMagicNumbers.INSTANCE; } + @Override + public void postToMainThread(Plugin plugin, boolean priority, Runnable task) { + getHandle().getServer().addMainThreadTask(priority, wrapTask(plugin, task)); + } + + @Override + public boolean runOnMainThread(Plugin plugin, boolean priority, Runnable task) { + task = wrapTask(plugin, task); + + if(getHandle().getServer().isMainThread()) { + task.run(); + return true; + } else { + postToMainThread(plugin, priority, task); + return false; + } + } + + private Runnable wrapTask(final Plugin plugin, final Runnable task) { + if(!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); + } + + class Wrapped implements Runnable { + @Override public void run() { + try { + task.run(); + } catch(Throwable throwable) { + plugin.getLogger().log( + Level.SEVERE, + "Exception running task from plugin " + plugin.getDescription().getFullName(), + throwable + ); + } + } + } + + return task instanceof Wrapped ? task : new Wrapped(); + } + private final Spigot spigot = new Spigot() { diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index ad46b58..92c0681 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -55,6 +55,7 @@ import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.util.Vector; +import org.github.paperspigot.PaperSpigotConfig; public class CraftWorld implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; @@ -76,6 +77,26 @@ public class CraftWorld implements World { private int chunkLoadCount = 0; private int chunkGCTickCount; + // Paper start - Provide fast information methods + public int getEntityCount() { + return world.entityList.size(); + } + public int getTileEntityCount() { + // We don't use the full world tile entity list, so we must iterate chunks + int size = 0; + for (net.minecraft.server.Chunk chunk : world.chunkProviderServer.chunks.values()) { + size += chunk.tileEntities.size(); + } + return size; + } + public int getChunkCount() { + return world.chunkProviderServer.chunks.size(); + } + public int getPlayerCount() { + return world.players.size(); + } + // Paper end + private static final Random rand = new Random(); public CraftWorld(WorldServer world, ChunkGenerator gen, Environment env) { @@ -89,6 +110,32 @@ public class CraftWorld implements World { } } + private boolean ticking = false; + + public boolean isTicking() { + return ticking; + } + + public boolean checkTicking() { + boolean shouldTick = PaperSpigotConfig.tickEmptyWorlds || hasPlayers(); + if(ticking) { + if(!shouldTick) { // Empty + ticking = false; + world.getServer().getLogger().info("Stopping world " + getName()); + getHandle().chunkProviderServer.unloadAllChunks(); + } + } else if(shouldTick) { + ticking = true; + world.getServer().getLogger().info("Starting world " + getName()); + setKeepSpawnInMemory(getKeepSpawnInMemory()); + } + return ticking; + } + + public boolean hasPlayers() { + return getPlayerCount() > 0; + } + public Block getBlockAt(int x, int y, int z) { return getChunkAt(x >> 4, z >> 4).getBlock(x & 0xF, y, z & 0xF); } @@ -299,7 +346,6 @@ public class CraftWorld implements World { return world.chunkProviderServer.getChunkAt(x, z) != null; } - world.chunkProviderServer.unloadQueue.remove(LongHash.toLong(x, z)); // TacoSpigot - invoke LongHash directly net.minecraft.server.Chunk chunk = world.chunkProviderServer.chunks.get(LongHash.toLong(x, z)); if (chunk == null) { @@ -309,6 +355,7 @@ public class CraftWorld implements World { chunkLoadPostProcess(chunk, x, z); world.timings.syncChunkLoadTimer.stopTiming(); // Spigot } + world.chunkProviderServer.unloadQueue.remove(LongHash.toLong(x, z)); return chunk != null; } @@ -906,7 +953,7 @@ public class CraftWorld implements World { Validate.isTrue(material.isBlock(), "Material must be a block"); double x = location.getBlockX() + 0.5; - double y = location.getBlockY() + 0.5; + double y = location.getBlockY(); double z = location.getBlockZ() + 0.5; // PaperSpigot start - Add FallingBlock source location API @@ -940,6 +987,9 @@ public class CraftWorld implements World { // order is important for some of these if (Boat.class.isAssignableFrom(clazz)) { entity = new EntityBoat(world, x, y, z); + } else if (org.bukkit.entity.Item.class.isAssignableFrom(clazz)) { + entity = new EntityItem(world, x, y, z, new net.minecraft.server.ItemStack(net.minecraft.server.Item.getItemOf(net.minecraft.server.Blocks.DIRT))); + // Paper end } else if (FallingBlock.class.isAssignableFrom(clazz)) { x = location.getBlockX(); y = location.getBlockY(); diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java index 8a6adea..a1a6d75 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java @@ -1,9 +1,6 @@ package org.bukkit.craftbukkit.chunkio; -import net.minecraft.server.Chunk; -import net.minecraft.server.ChunkProviderServer; -import net.minecraft.server.ChunkRegionLoader; -import net.minecraft.server.World; +import net.minecraft.server.*; import org.bukkit.craftbukkit.util.AsynchronousExecutor; public class ChunkIOExecutor { @@ -13,7 +10,7 @@ public class ChunkIOExecutor { private static final AsynchronousExecutor instance = new AsynchronousExecutor<>(new ChunkIOProvider(), BASE_THREADS); public static Chunk syncChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z) { - return instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider)); + return MCUtil.ensureMain("Async Chunk Load", () -> instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider))); // Paper } public static void queueChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) { diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java index 7a86b43..d3025c6 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java @@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.chunkio; import java.io.IOException; import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkCoordIntPair; import net.minecraft.server.ChunkRegionLoader; import net.minecraft.server.NBTTagCompound; @@ -33,8 +34,8 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider offers = waitable.get(); if (offers == null) { diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java index ecfc316..3f909c1 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java @@ -64,6 +64,16 @@ public class CraftFish extends AbstractProjectile implements Fish { this.biteChance = chance; } + // Paper start + @Override + public void remove() { + super.remove(); + if (getHandle().owner != null) { + getHandle().owner.hookedFish = null; + } + } + // Paper end + @Deprecated public LivingEntity _INVALID_getShooter() { return (LivingEntity) getShooter(); diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 69fe35b..89cec73 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -1,10 +1,7 @@ package org.bukkit.craftbukkit.event; import java.net.InetAddress; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.*; import com.google.common.base.Function; import com.google.common.base.Functions; @@ -484,6 +481,10 @@ public class CraftEventFactory { blockDamage = null; if (source == DamageSource.CACTUS) { cause = DamageCause.CONTACT; + } else if (source == DamageSource.FIRE) { + cause = DamageCause.FIRE; + } else if (source == DamageSource.LAVA) { + cause = DamageCause.LAVA; } else { throw new RuntimeException(String.format("Unhandled damage of %s by %s from %s", entity, damager, source.translationIndex)); // Spigot } @@ -515,7 +516,9 @@ public class CraftEventFactory { DamageCause cause = null; if (source == DamageSource.FIRE) { cause = DamageCause.FIRE; - } else if (source == DamageSource.STARVE) { + } else if (source == DamageSource.LAVA) { + cause = DamageCause.LAVA; + } else if (source == DamageSource.STARVE) { cause = DamageCause.STARVATION; } else if (source == DamageSource.WITHER) { cause = DamageCause.WITHER; @@ -982,4 +985,59 @@ public class CraftEventFactory { return event; } // PaperSpigot end + + public static BlockPistonEvent callBlockPistonEvent(final World world, BlockPosition pos, EnumDirection facing, boolean extending) { + // When retracting, PistonExtendsChecker expects the piston arm to already be + // removed, so we have to temporarily remove it or it will get in the way. + BlockPosition extensionPos = pos.shift(facing); + IBlockData extensionData = null; + if(!extending) { + extensionData = world.getType(extensionPos); + world.setTypeAndData(extensionPos, Blocks.AIR.getBlockData(), 0); + } + + // This thing searches for all the blocks that will be moved. We have to use it twice, + // because it's too late to cancel by the time NMS calls it. A potential optimization + // would be to save this result and reuse it in the NMS code, but that's probably not + // worth the messy diff. + PistonExtendsChecker checker = new PistonExtendsChecker(world, pos, facing, extending); + boolean moved = checker.a(); + + if(extensionData != null) { + world.setTypeAndData(extensionPos, extensionData, 0); + } + + // This means vanilla mechanics will obstruct the piston, so no need for any event. + if(!moved) return null; + + // A sort of lazy list that avoids wrapping blocks until needed + final List movedBlocks = checker.getMovedBlocks(); + final List brokenBlocks = checker.getBrokenBlocks(); + List blocks = new AbstractList() { + @Override + public int size() { + return movedBlocks.size() + brokenBlocks.size(); + } + + @Override + public org.bukkit.block.Block get(int index) { + if (index >= size() || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + BlockPosition pos = (BlockPosition) (index < movedBlocks.size() ? movedBlocks.get(index) : brokenBlocks.get(index - movedBlocks.size())); + return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + } + }; + + org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + BlockPistonEvent event; + if(extending) { + event = new BlockPistonExtendEvent(block, blocks, CraftBlock.notchToBlockFace(facing)); + } else { + event = new BlockPistonRetractEvent(block, blocks, CraftBlock.notchToBlockFace(facing.opposite())); + } + + world.getServer().getPluginManager().callEvent(event); + return event; + } } diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java index d0031b6..75bf8f7 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java @@ -195,8 +195,15 @@ public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { boolean isEmpty() { return super.isEmpty() && patterns.isEmpty() && base == null; } - - + + @Override + public CraftMetaBanner clone() { + CraftMetaBanner meta = (CraftMetaBanner) super.clone(); + meta.base = this.base; + meta.patterns = this.patterns; + return meta; + } + @Override boolean applicableTo(Material type) { return type == Material.BANNER; diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java index d60686d..f461a35 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java @@ -47,7 +47,7 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) static final ItemMetaKey BLOCK_ENTITY_TAG = new ItemMetaKey("BlockEntityTag"); - final Material material; + Material material; NBTTagCompound blockEntityTag; CraftMetaBlockState(CraftMetaItem meta, Material material) { @@ -335,4 +335,12 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta blockEntityTag = new NBTTagCompound(); te.b(blockEntityTag); } + + @Override + public CraftMetaBlockState clone() { + CraftMetaBlockState meta = (CraftMetaBlockState) super.clone(); + meta.material = this.material; + meta.blockEntityTag = this.blockEntityTag; + return meta; + } } diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index 31cb63d..a7cb26e 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -75,7 +75,7 @@ import net.minecraft.server.IAttribute; *
  • SerializableMeta.Deserializers deserializer() */ @DelegateDeserialization(CraftMetaItem.SerializableMeta.class) -class CraftMetaItem implements ItemMeta, Repairable { +public class CraftMetaItem implements ItemMeta, Repairable { static class ItemMetaKey { diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java new file mode 100644 index 0000000..12b40e0 --- /dev/null +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.bukkit.craftbukkit.scheduler; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.bukkit.plugin.Plugin; +import org.github.paperspigot.ServerSchedulerReportingWrapper; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class CraftAsyncScheduler extends CraftScheduler { + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), + new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("Craft Async Scheduler Management Thread").build()); + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { + super(true); + executor.allowCoreThreadTimeOut(true); + executor.prestartAllCoreThreads(); + } + + @Override + public void cancelTask(int taskId) { + this.management.execute(() -> this.removeTask(taskId)); + } + + private synchronized void removeTask(int taskId) { + parsePending(); + this.pending.removeIf((task) -> { + if (task.getTaskId() == taskId) { + task.cancel0(); + return true; + } + return false; + }); + } + + @Override + public void mainThreadHeartbeat(int currentTick) { + this.currentTick = currentTick; + this.management.execute(() -> this.runTasks(currentTick)); + } + + private synchronized void runTasks(int currentTick) { + parsePending(); + while (!this.pending.isEmpty() && this.pending.peek().getNextRun() <= currentTick) { + CraftTask task = this.pending.remove(); + if (executeTask(task)) { + final long period = task.getPeriod(); + if (period > 0) { + task.setNextRun(currentTick + period); + temp.add(task); + } + } + parsePending(); + } + this.pending.addAll(temp); + temp.clear(); + } + + private boolean executeTask(CraftTask task) { + if (isValid(task)) { + this.runners.put(task.getTaskId(), task); + this.executor.execute(new ServerSchedulerReportingWrapper(task)); + return true; + } + return false; + } + + @Override + public synchronized void cancelTasks(Plugin plugin) { + parsePending(); + for (Iterator iterator = this.pending.iterator(); iterator.hasNext(); ) { + CraftTask task = iterator.next(); + if (task.getTaskId() != -1 && (plugin == null || task.getOwner().equals(plugin))) { + task.cancel0(); + iterator.remove(); + } + } + } + + @Override + public synchronized void cancelAllTasks() { + cancelTasks(null); + } + + /** + * Task is not cancelled + * @param runningTask + * @return + */ + static boolean isValid(CraftTask runningTask) { + return runningTask.getPeriod() >= CraftTask.NO_REPEATING; + } +} diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 18f948d..77d21fd 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -21,6 +21,9 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitWorker; +import org.github.paperspigot.ServerSchedulerReportingWrapper; +import org.github.paperspigot.event.ServerExceptionEvent; +import org.github.paperspigot.exception.ServerSchedulerException; /** * The fundamental concepts for this implementation: @@ -32,7 +35,7 @@ import org.bukkit.scheduler.BukkitWorker; *
  • Changing the period on a task is delicate. * Any future task needs to notify waiting threads. * Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}. - * Another utility method is provided for this, {@link #cancelTask(int)}
  • + * Another utility method is provided for this, {@link #cancelTask(CraftTask)} *
  • {@link #runners} provides a moderately up-to-date view of active tasks. * If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.
  • *
  • Async tasks are responsible for removing themselves from runners
  • @@ -47,28 +50,32 @@ public class CraftScheduler implements BukkitScheduler { */ private final AtomicInteger ids = new AtomicInteger(1); /** - * Current head of linked-list. This reference is always stale, {@code CraftTask#next} is the live reference. + * Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference. */ private volatile CraftTask head = new CraftTask(); /** * Tail of a linked-list. AtomicReference only matters when adding to queue */ - private final AtomicReference tail = new AtomicReference<>(head); + private final AtomicReference tail = new AtomicReference(head); /** * Main thread logic only */ - private final PriorityQueue pending = new PriorityQueue<>(10, - (o1, o2) -> (int) (o1.getNextRun() - o2.getNextRun())); + final PriorityQueue pending = new PriorityQueue(10, // Paper + new Comparator() { + public int compare(final CraftTask o1, final CraftTask o2) { + return (int) (o1.getNextRun() - o2.getNextRun()); + } + }); /** * Main thread logic only */ - private final List temp = new ArrayList<>(); + private final List temp = new ArrayList(); /** * These are tasks that are currently active. It's provided for 'viewing' the current state. */ - private final ConcurrentHashMap runners = new ConcurrentHashMap<>(); - private volatile int currentTick = -1; - private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot + final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper + volatile int currentTick = -1; // Paper + //private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot // Paper - moved to AsyncScheduler private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; private CraftAsyncDebugger debugTail = debugHead; private static final int RECENT_TICKS; @@ -77,6 +84,23 @@ public class CraftScheduler implements BukkitScheduler { RECENT_TICKS = 30; } + // Paper start + private final CraftScheduler asyncScheduler; + private final boolean isAsyncScheduler; + public CraftScheduler() { + this(false); + } + + public CraftScheduler(boolean isAsync) { + this.isAsyncScheduler = isAsync; + if (isAsync) { + this.asyncScheduler = this; + } else { + this.asyncScheduler = new CraftAsyncScheduler(); + } + } + // Paper end + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { return this.scheduleSyncDelayedTask(plugin, task, 0l); } @@ -143,12 +167,12 @@ public class CraftScheduler implements BukkitScheduler { } else if (period < -1l) { period = -1l; } - return handle(new CraftAsyncTask(runners, plugin, runnable, nextId(), period), delay); + return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper } public Future callSyncMethod(final Plugin plugin, final Callable task) { validate(plugin, task); - final CraftFuture future = new CraftFuture<>(task, plugin, nextId()); + final CraftFuture future = new CraftFuture(task, plugin, nextId()); handle(future, 0l); return future; } @@ -157,6 +181,11 @@ public class CraftScheduler implements BukkitScheduler { if (taskId <= 0) { return; } + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelTask(taskId); + } + // Paper end CraftTask task = runners.get(taskId); if (task != null) { task.cancel0(); @@ -196,6 +225,11 @@ public class CraftScheduler implements BukkitScheduler { public void cancelTasks(final Plugin plugin) { Validate.notNull(plugin, "Cannot cancel tasks of null plugin"); + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelTasks(plugin); + } + // Paper end final CraftTask task = new CraftTask( new Runnable() { public void run() { @@ -233,18 +267,25 @@ public class CraftScheduler implements BukkitScheduler { } public void cancelAllTasks() { + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelAllTasks(); + } + // Paper end final CraftTask task = new CraftTask( - () -> { - Iterator it = CraftScheduler.this.runners.values().iterator(); - while (it.hasNext()) { - CraftTask task1 = it.next(); - task1.cancel0(); - if (task1.isSync()) { - it.remove(); + new Runnable() { + public void run() { + Iterator it = CraftScheduler.this.runners.values().iterator(); + while (it.hasNext()) { + CraftTask task = it.next(); + task.cancel0(); + if (task.isSync()) { + it.remove(); + } } + CraftScheduler.this.pending.clear(); + CraftScheduler.this.temp.clear(); } - CraftScheduler.this.pending.clear(); - CraftScheduler.this.temp.clear(); }){{this.timings=co.aikar.timings.SpigotTimings.getCancelTasksTimer();}}; // Spigot handle(task, 0l); for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { @@ -259,6 +300,13 @@ public class CraftScheduler implements BukkitScheduler { } public boolean isCurrentlyRunning(final int taskId) { + // Paper start + if (!isAsyncScheduler) { + if (this.asyncScheduler.isCurrentlyRunning(taskId)) { + return true; + } + } + // Paper end final CraftTask task = runners.get(taskId); if (task == null || task.isSync()) { return false; @@ -273,6 +321,11 @@ public class CraftScheduler implements BukkitScheduler { if (taskId <= 0) { return false; } + // Paper start + if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) { + return true; + } + // Paper end for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { if (task.getTaskId() == taskId) { return task.getPeriod() >= -1l; // The task will run @@ -283,7 +336,13 @@ public class CraftScheduler implements BukkitScheduler { } public List getActiveWorkers() { - final ArrayList workers = new ArrayList<>(); + // Paper start + if (!isAsyncScheduler) { + //noinspection TailRecursion + return this.asyncScheduler.getActiveWorkers(); + } + // Paper end + final ArrayList workers = new ArrayList(); for (final CraftTask taskObj : runners.values()) { // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread if (taskObj.isSync()) { @@ -299,7 +358,7 @@ public class CraftScheduler implements BukkitScheduler { } public List getPendingTasks() { - final ArrayList truePending = new ArrayList<>(); + final ArrayList truePending = new ArrayList(); for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { if (task.getTaskId() != -1) { // -1 is special code @@ -307,7 +366,7 @@ public class CraftScheduler implements BukkitScheduler { } } - final ArrayList pending = new ArrayList<>(); + final ArrayList pending = new ArrayList(); for (CraftTask task : runners.values()) { if (task.getPeriod() >= -1l) { pending.add(task); @@ -319,6 +378,11 @@ public class CraftScheduler implements BukkitScheduler { pending.add(task); } } + // Paper start + if (!this.isAsyncScheduler) { + pending.addAll(this.asyncScheduler.getPendingTasks()); + } + // Paper end return pending; } @@ -326,6 +390,11 @@ public class CraftScheduler implements BukkitScheduler { * This method is designed to never block or wait for locks; an immediate execution of all current tasks. */ public void mainThreadHeartbeat(final int currentTick) { + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.mainThreadHeartbeat(currentTick); + } + // Paper end this.currentTick = currentTick; final List temp = this.temp; parsePending(); @@ -342,18 +411,24 @@ public class CraftScheduler implements BukkitScheduler { try { task.run(); } catch (final Throwable throwable) { + // Paper start + String msg = String.format( + "Task #%s for %s generated an exception", + task.getTaskId(), + task.getOwner().getDescription().getFullName()); task.getOwner().getLogger().log( Level.WARNING, - String.format( - "Task #%s for %s generated an exception", - task.getTaskId(), - task.getOwner().getDescription().getFullName()), + msg, throwable); + task.getOwner().getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task)) + ); + // Paper end } parsePending(); } else { debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); - executor.execute(task); + task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper // We don't need to parse pending // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) } @@ -370,7 +445,7 @@ public class CraftScheduler implements BukkitScheduler { debugHead = debugHead.getNextHead(currentTick); } - private void addTask(final CraftTask task) { + protected void addTask(final CraftTask task) { final AtomicReference tail = this.tail; CraftTask tailTask = tail.get(); while (!tail.compareAndSet(tailTask, task)) { @@ -379,7 +454,13 @@ public class CraftScheduler implements BukkitScheduler { tailTask.setNext(task); } - private CraftTask handle(final CraftTask task, final long delay) { + protected CraftTask handle(final CraftTask task, final long delay) { // Paper + // Paper start + if (!this.isAsyncScheduler && !task.isSync()) { + this.asyncScheduler.handle(task, delay); + return task; + } + // Paper end task.setNextRun(currentTick + delay); addTask(task); return task; @@ -397,7 +478,7 @@ public class CraftScheduler implements BukkitScheduler { return ids.incrementAndGet(); } - private void parsePending() { + void parsePending() { // Paper CraftTask head = this.head; CraftTask task = head.getNext(); CraftTask lastTask = head; @@ -412,8 +493,8 @@ public class CraftScheduler implements BukkitScheduler { // We split this because of the way things are ordered for all of the async calls in CraftScheduler // (it prevents race-conditions) for (task = head; task != lastTask; task = head) { - head = task.getNext(); - task.setNext(null); + head = task.getNext(); + task.setNext(null); } this.head = lastTask; } diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java index 4b1e352..44cbcda 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -10,6 +10,11 @@ import org.bukkit.scheduler.BukkitTask; public class CraftTask implements BukkitTask, Runnable { // Spigot private volatile CraftTask next = null; + public static final int ERROR = 0; + public static final int NO_REPEATING = -1; + public static final int CANCEL = -2; + public static final int PROCESS_FOR_FUTURE = -3; + public static final int DONE_FOR_FUTURE = -4; /** * -1 means no repeating
    * -2 means cancel
    diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java index 2078e9b..0e7e717 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java @@ -144,8 +144,14 @@ public final class CraftChatMessage { public static String fromComponent(IChatBaseComponent component, EnumChatFormat defaultColor) { if (component == null) return ""; StringBuilder out = new StringBuilder(); - - for (IChatBaseComponent c : component) { + // SportPaper - limit iterations to 2 + byte iterations = 0; + + for (IChatBaseComponent c : (Iterable) component) { + if (++iterations > 2) { + break; + } + ChatModifier modi = c.getChatModifier(); out.append(modi.getColor() == null ? defaultColor : modi.getColor()); if (modi.isBold()) { diff --git a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java index 03b8717..72a0681 100644 --- a/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java +++ b/TacoSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java @@ -8,7 +8,7 @@ import java.util.TreeSet; public class HashTreeSet implements Set { - private HashSet hash = new HashSet<>(); + private Set hash = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet(); //Paper - Replace java.util.HashSet with ObjectOpenHashSet private TreeSet tree = new TreeSet<>(); public HashTreeSet() { diff --git a/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotConfig.java b/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotConfig.java index 9c700b7..2ca1479 100644 --- a/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotConfig.java +++ b/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotConfig.java @@ -146,13 +146,13 @@ public class PaperSpigotConfig public static double babyZombieMovementSpeed; private static void babyZombieMovementSpeed() { - babyZombieMovementSpeed = getDouble( "settings.baby-zombie-movement-speed", 0.5D ); // Player moves at 0.1F, for reference + babyZombieMovementSpeed = getDouble( "baby-zombie-movement-speed", 0.5D ); // Player moves at 0.1F, for reference } public static boolean interactLimitEnabled; private static void interactLimitEnabled() { - interactLimitEnabled = getBoolean( "settings.limit-player-interactions", true ); + interactLimitEnabled = getBoolean( "limit-player-interactions", true ); if ( !interactLimitEnabled ) { Bukkit.getLogger().log( Level.INFO, "Disabling player interaction limiter, your server may be more vulnerable to malicious users" ); @@ -223,12 +223,38 @@ public class PaperSpigotConfig warnForExcessiveVelocity = getBoolean("warnWhenSettingExcessiveVelocity", true); } + public static int regionFileCacheSize = 256; + private static void regionFileCacheSize() { + regionFileCacheSize = getInt("region-file-cache-size", 256); + } + public static boolean saveEmptyScoreboardTeams; private static void saveEmptyScoreboardTeams() { saveEmptyScoreboardTeams = getBoolean("save-empty-scoreboard-teams", false); } - // KigPaper start + public static int tabSpamIncrement = 10; + public static int tabSpamLimit = 500; + private static void tabSpamLimiters() { + tabSpamIncrement = getInt("spam-limiter.tab-spam-increment", tabSpamIncrement); + tabSpamLimit = getInt("spam-limiter.tab-spam-limit", tabSpamLimit); + } + + public static boolean includeRandomnessInArrowTrajectory = false; + private static void includeRandomnessInArrowTrajectory() { + includeRandomnessInArrowTrajectory = getBoolean("include-randomness-in-arrow-trajectory", includeRandomnessInArrowTrajectory); + } + + public static boolean includeRandomnessInArrowDamage = true; + private static void includeRandomnessInArrowDamage() { + includeRandomnessInArrowDamage = getBoolean("include-randomness-in-arrow-damage", includeRandomnessInArrowDamage); + } + + public static boolean tickEmptyWorlds = true; + private static void tickEmptyWorlds() { + tickEmptyWorlds = getBoolean("tick-empty-worlds", tickEmptyWorlds); + } + public static boolean betterVehicleHitboxes; private static void betterVehicleHitboxes() { betterVehicleHitboxes = getBoolean("better-vehicle-hitboxes", true); @@ -263,5 +289,4 @@ public class PaperSpigotConfig private static void kickChatMessageLength() { kickChatMessageLength = getBoolean("kick-chat-message-length", false); } - // KigPaper end } diff --git a/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java b/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java index 4309634..7398a6d 100644 --- a/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java +++ b/TacoSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java @@ -402,4 +402,14 @@ public class PaperSpigotWorldConfig { portalSearchRadius = getInt("portal-search-radius", 128); } + + public boolean removeCorruptTEs; + private void removeCorruptTEs() { + removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false); + } + + public boolean armorStandEntityLookups; + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); + } } diff --git a/TacoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java b/TacoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java new file mode 100644 index 0000000..e4244a6 --- /dev/null +++ b/TacoSpigot-Server/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java @@ -0,0 +1,38 @@ +package org.github.paperspigot; + +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.scheduler.CraftTask; +import org.github.paperspigot.event.ServerExceptionEvent; +import org.github.paperspigot.exception.ServerSchedulerException; + +/** + * Reporting wrapper to catch exceptions not natively + */ +public class ServerSchedulerReportingWrapper implements Runnable { + + private final CraftTask internalTask; + + public ServerSchedulerReportingWrapper(CraftTask internalTask) { + this.internalTask = Preconditions.checkNotNull(internalTask, "internalTask"); + } + + @Override + public void run() { + try { + internalTask.run(); + } catch (RuntimeException e) { + internalTask.getOwner().getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(e, internalTask)) + ); + throw e; + } catch (Throwable t) { + internalTask.getOwner().getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(t, internalTask)) + ); //Do not rethrow, since it is not permitted with Runnable#run + } + } + + public CraftTask getInternalTask() { + return internalTask; + } +} diff --git a/TacoSpigot-Server/src/main/java/org/spigotmc/ActivationRange.java b/TacoSpigot-Server/src/main/java/org/spigotmc/ActivationRange.java index adff0c9..645cdac 100644 --- a/TacoSpigot-Server/src/main/java/org/spigotmc/ActivationRange.java +++ b/TacoSpigot-Server/src/main/java/org/spigotmc/ActivationRange.java @@ -126,9 +126,11 @@ public class ActivationRange { for ( int j1 = k; j1 <= l; ++j1 ) { - if ( world.getWorld().isChunkLoaded( i1, j1 ) ) + Chunk chunk = world.getChunkIfLoaded( i1, j1 ); // SportPaper - remove double chunk lookup + + if ( chunk != null ) { - activateChunkEntities( world.getChunkAt( i1, j1 ) ); + activateChunkEntities( chunk ); } } } diff --git a/TacoSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java b/TacoSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java index e7d47be..3e52830 100644 --- a/TacoSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java +++ b/TacoSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java @@ -23,7 +23,7 @@ public class RestartCommand extends Command { if ( testPermission( sender ) ) { - MinecraftServer.getServer().processQueue.add(RestartCommand::restart); + MinecraftServer.getServer().addMainThreadTask(RestartCommand::restart); } return true; } diff --git a/TacoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java b/TacoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java index 9fba1c7..8c012c0 100644 --- a/TacoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java +++ b/TacoSpigot-Server/src/main/java/org/spigotmc/WatchdogThread.java @@ -52,7 +52,7 @@ public class WatchdogThread extends Thread while ( !stopping ) { // - if ( lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime ) + if ( lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable { Logger log = Bukkit.getServer().getLogger(); log.log( Level.SEVERE, "The server has stopped responding!" ); diff --git a/TacoSpigot-Server/src/main/resources/log4j2.xml b/TacoSpigot-Server/src/main/resources/log4j2.xml index f37d1c2..0452fbc 100644 --- a/TacoSpigot-Server/src/main/resources/log4j2.xml +++ b/TacoSpigot-Server/src/main/resources/log4j2.xml @@ -3,10 +3,10 @@ - + - +