diff --git a/Remapped-Spigot-Server-Patches/0001-POM-Changes.patch b/Remapped-Spigot-Server-Patches/0001-POM-Changes.patch new file mode 100644 index 000000000..8d0da0d54 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0001-POM-Changes.patch @@ -0,0 +1,291 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 29 Feb 2016 20:40:33 -0600 +Subject: [PATCH] POM Changes + + +diff --git a/pom.xml b/pom.xml +index 3fc047371e8f8a626e69697fad549d689c5dce89..a5d87d22cb1588d15e08da3b37e51c5e261c7799 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,15 +1,14 @@ + + 4.0.0 +- org.spigotmc +- spigot ++ paper + jar + 1.16.5-R0.1-SNAPSHOT +- Spigot +- https://www.spigotmc.org/ ++ Paper ++ https://papermc.io + + +- true ++ + UTF-8 + unknown + git +@@ -20,21 +19,39 @@ + + + +- org.spigotmc +- spigot-parent ++ com.destroystokyo.paper ++ paper-parent + dev-SNAPSHOT + ../pom.xml + + ++ ++ ++ ++ org.apache.logging.log4j ++ log4j-bom ++ 2.11.2 ++ pom ++ import ++ ++ ++ ++ + + +- org.spigotmc +- spigot-api ++ com.destroystokyo.paper ++ paper-api ++ ${project.version} ++ compile ++ ++ ++ com.destroystokyo.paper ++ paper-mojangapi + ${project.version} + compile + + +- org.spigotmc ++ io.papermc + minecraft-server + ${minecraft.version}-SNAPSHOT + compile +@@ -45,18 +62,15 @@ + 2.12.1 + compile + ++ ++ org.apache.logging.log4j ++ log4j-api ++ compile ++ + + org.apache.logging.log4j + log4j-iostreams +- 2.8.1 + compile +- +- +- +- org.apache.logging.log4j +- log4j-api +- +- + + + org.ow2.asm +@@ -64,12 +78,23 @@ + 9.1 + compile + ++ ++ ++ co.aikar ++ cleaner ++ 1.0-SNAPSHOT ++ ++ ++ io.netty ++ netty-all ++ 4.1.50.Final ++ + + + com.googlecode.json-simple + json-simple + 1.1.1 +- runtime ++ compile + + + org.xerial +@@ -80,7 +105,7 @@ + + mysql + mysql-connector-java +- 5.1.49 ++ 8.0.23 + runtime + + +@@ -105,7 +130,7 @@ + + org.apache.logging.log4j + log4j-slf4j-impl +- 2.8.1 ++ + runtime + + +@@ -132,34 +157,22 @@ + + + ++ paper-${minecraft.version} ++ clean install + + +- net.md-5 +- scriptus +- 0.4.1 ++ com.lukegb.mojo ++ gitdescribe-maven-plugin ++ 1.3 ++ ++ git-Paper- ++ .. ++ + + +- ex-spigot +- +- ${bt.name}-Spigot-%s +- ../ +- spigot.desc +- +- initialize +- +- describe +- +- +- +- ex-craftbukkit +- +- -%s +- ../../CraftBukkit +- craftbukkit.desc +- +- initialize ++ compile + +- describe ++ gitdescribe + + + +@@ -169,6 +182,7 @@ + maven-jar-plugin + 3.2.0 + ++ true + + + false +@@ -176,11 +190,13 @@ + + org.bukkit.craftbukkit.Main + CraftBukkit +- ${spigot.desc}${craftbukkit.desc} +- ${project.build.outputTimestamp} ++ ++ ${describe} ++ ${maven.build.timestamp} + Bukkit + ${api.version} + Bukkit Team ++ true + + + +@@ -216,14 +232,24 @@ + shade + + ++ ${project.build.directory}/dependency-reduced-pom.xml + ${shadeSourcesJar} + + +- org.spigotmc:minecraft-server ++ io.papermc:minecraft-server + + com/google/common/** + com/google/gson/** + com/google/thirdparty/** ++ ++ io/netty/** ++ META-INF/native/libnetty* ++ com/mojang/brigadier/** ++ META-INF/MANIFEST.MF ++ com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.class ++ com/mojang/datafixers/util/Either* ++ org/apache/logging/log4j/** ++ META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat + + + +@@ -245,10 +271,11 @@ + jline + org.bukkit.craftbukkit.libs.jline + +- +- it.unimi +- org.bukkit.craftbukkit.libs.it.unimi +- ++ ++ ++ ++ ++ + + org.apache.commons.codec + org.bukkit.craftbukkit.libs.org.apache.commons.codec +@@ -316,10 +343,6 @@ + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 +- +- +- eclipse +- + + + org.codehaus.plexus +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 4452427d0a8298d119ca29ef397b7a94f19eec28..46a16e31775b28c44f95a8ac5545ebcb656c74b6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -186,7 +186,7 @@ public class Main { + } + + if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { +- Date buildDate = new Date(Integer.parseInt(Main.class.getPackage().getImplementationVendor()) * 1000L); ++ Date buildDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(Main.class.getPackage().getImplementationVendor()); // Paper + + Calendar deadline = Calendar.getInstance(); + deadline.add(Calendar.DAY_OF_YEAR, -28); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 93046379d0cefd5d3236fc59e698809acdc18f80..674096cab190d62622f9947853b056f57d43a2a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +11,7 @@ public final class Versioning { + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.spigotmc/spigot-api/pom.properties"); ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.destroystokyo.paper/paper-api/pom.properties"); + Properties properties = new Properties(); + + if (stream != null) { diff --git a/Remapped-Spigot-Server-Patches/0002-Paper-config-files.patch b/Remapped-Spigot-Server-Patches/0002-Paper-config-files.patch new file mode 100644 index 000000000..363f09b24 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0002-Paper-config-files.patch @@ -0,0 +1,820 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 29 Feb 2016 21:02:09 -0600 +Subject: [PATCH] Paper config files + +Loads each yml file for early init too so it can be used for early options + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d05eeaa711a09bb121b530654821894e795ff4ea +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -0,0 +1,286 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Functions; ++import com.google.common.base.Joiner; ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Iterables; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.ChunkPos; ++import org.apache.commons.lang3.tuple.MutablePair; ++import org.apache.commons.lang3.tuple.Pair; ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.Location; ++import org.bukkit.World; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.entity.Player; ++ ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class PaperCommand extends Command { ++ private static final String BASE_PERM = "bukkit.command.paper."; ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); ++ ++ public PaperCommand(String name) { ++ super(name); ++ this.description = "Paper related commands"; ++ this.usageMessage = "/paper [" + Joiner.on(" | ").join(SUBCOMMANDS) + "]"; ++ this.setPermission("bukkit.command.paper;" + Joiner.on(';').join(SUBCOMMANDS.stream().map(s -> BASE_PERM + s).collect(Collectors.toSet()))); ++ } ++ ++ private static boolean testPermission(CommandSender commandSender, String permission) { ++ if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; ++ commandSender.sendMessage(Bukkit.getPermissionMessage()); ++ return false; ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ if (args.length <= 1) ++ return getListMatchingLast(sender, args, SUBCOMMANDS); ++ ++ switch (args[0].toLowerCase(Locale.ENGLISH)) ++ { ++ case "entity": ++ if (args.length == 2) ++ return getListMatchingLast(sender, args, "help", "list"); ++ if (args.length == 3) ++ return getListMatchingLast(sender, args, EntityType.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); ++ break; ++ } ++ return Collections.emptyList(); ++ } ++ ++ // Code from Mojang - copyright them ++ public static List getListMatchingLast(CommandSender sender, String[] args, String... matches) { ++ return getListMatchingLast(sender, args, (Collection) Arrays.asList(matches)); ++ } ++ ++ public static boolean matches(String s, String s1) { ++ return s1.regionMatches(true, 0, s, 0, s.length()); ++ } ++ ++ public static List getListMatchingLast(CommandSender sender, String[] strings, Collection collection) { ++ String last = strings[strings.length - 1]; ++ ArrayList results = Lists.newArrayList(); ++ ++ if (!collection.isEmpty()) { ++ Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator(); ++ ++ while (iterator.hasNext()) { ++ String s1 = (String) iterator.next(); ++ ++ if (matches(last, s1) && (sender.hasPermission(BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { ++ results.add(s1); ++ } ++ } ++ ++ if (results.isEmpty()) { ++ iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ Object object = iterator.next(); ++ ++ if (object instanceof ResourceLocation && matches(last, ((ResourceLocation) object).getPath())) { ++ results.add(String.valueOf(object)); ++ } ++ } ++ } ++ } ++ ++ return results; ++ } ++ // end copy stuff ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ if (args.length == 0) { ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return false; ++ } ++ if (SUBCOMMANDS.contains(args[0].toLowerCase(Locale.ENGLISH))) { ++ if (!testPermission(sender, args[0].toLowerCase(Locale.ENGLISH))) return true; ++ } ++ switch (args[0].toLowerCase(Locale.ENGLISH)) { ++ case "heap": ++ dumpHeap(sender); ++ break; ++ case "entity": ++ listEntities(sender, args); ++ break; ++ case "reload": ++ doReload(sender); ++ break; ++ case "ver": ++ if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) ++ case "version": ++ Command ver = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); ++ if (ver != null) { ++ ver.execute(sender, commandLabel, new String[0]); ++ break; ++ } ++ // else - fall through to default ++ default: ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return false; ++ } ++ ++ return true; ++ } ++ ++ /* ++ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 ++ */ ++ private void listEntities(CommandSender sender, String[] args) { ++ if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper entity [list] help for more information on a specific command."); ++ return; ++ } ++ ++ switch (args[1].toLowerCase(Locale.ENGLISH)) { ++ case "list": ++ String filter = "*"; ++ if (args.length > 2) { ++ if (args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter."); ++ return; ++ } ++ filter = args[2]; ++ } ++ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); ++ Set names = EntityType.getEntityNameList().stream() ++ .filter(n -> n.toString().matches(cleanfilter)) ++ .collect(Collectors.toSet()); ++ ++ if (names.isEmpty()) { ++ sender.sendMessage(ChatColor.RED + "Invalid filter, does not match any entities. Use /paper entity list for a proper list"); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ ++ String worldName; ++ if (args.length > 3) { ++ worldName = args[3]; ++ } else if (sender instanceof Player) { ++ worldName = ((Player) sender).getWorld().getName(); ++ } else { ++ sender.sendMessage(ChatColor.RED + "Please specify the name of a world"); ++ sender.sendMessage(ChatColor.RED + "To do so without a filter, specify '*' as the filter"); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ ++ Map>> list = Maps.newHashMap(); ++ World bukkitWorld = Bukkit.getWorld(worldName); ++ if (bukkitWorld == null) { ++ sender.sendMessage(ChatColor.RED + "Could not load world for " + worldName + ". Please select a valid world."); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ ServerLevel world = ((CraftWorld) Bukkit.getWorld(worldName)).getHandle(); ++ ++ Map nonEntityTicking = Maps.newHashMap(); ++ ServerChunkCache chunkProviderServer = world.getChunkSource(); ++ ++ Collection entities = world.entitiesById.values(); ++ entities.forEach(e -> { ++ ResourceLocation key = new ResourceLocation(""); // TODO: update in next patch ++ ++ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); ++ ChunkPos chunk = new ChunkPos(e.xChunk, e.zChunk); ++ info.left++; ++ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); ++ if (!chunkProviderServer.isInEntityTickingChunk(e)) { ++ nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); ++ } ++ }); ++ ++ if (names.size() == 1) { ++ ResourceLocation name = names.iterator().next(); ++ Pair> info = list.get(name); ++ int nonTicking = nonEntityTicking.getOrDefault(name, Integer.valueOf(0)).intValue(); ++ if (info == null) { ++ sender.sendMessage(ChatColor.RED + "No entities found."); ++ return; ++ } ++ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); ++ info.getRight().entrySet().stream() ++ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isEntityTickingChunk(e.getKey()) ? " (Ticking)" : " (Non-Ticking)"))); ++ } else { ++ List> info = list.entrySet().stream() ++ .filter(e -> names.contains(e.getKey())) ++ .map(e -> Pair.of(e.getKey(), e.getValue().left)) ++ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .collect(Collectors.toList()); ++ ++ if (info == null || info.size() == 0) { ++ sender.sendMessage(ChatColor.RED + "No entities found."); ++ return; ++ } ++ ++ int count = info.stream().mapToInt(Pair::getRight).sum(); ++ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); ++ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); ++ info.forEach(e -> { ++ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), Integer.valueOf(0)).intValue(); ++ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); ++ }); ++ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); ++ } ++ break; ++ } ++ } ++ ++ private void dumpHeap(CommandSender sender) { ++ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); ++ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); ++ ++ Command.broadcastCommandMessage(sender, ChatColor.YELLOW + "Writing JVM heap data..."); ++ ++ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); ++ if (file != null) { ++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Heap dump saved to " + file); ++ } else { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Failed to write heap dump, see sever log for details"); ++ } ++ } ++ ++ private void doReload(CommandSender sender) { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); ++ ++ MinecraftServer console = MinecraftServer.getServer(); ++ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); ++ for (ServerLevel world : console.getAllLevels()) { ++ world.paperConfig.init(); ++ } ++ console.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Paper config reload complete."); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2c0514892d3993bef57ecf677cf8bb0fbe0216e4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -0,0 +1,185 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Throwables; ++ ++import java.io.File; ++import java.io.IOException; ++import java.lang.reflect.InvocationTargetException; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.regex.Pattern; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.command.Command; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.InvalidConfigurationException; ++import org.bukkit.configuration.file.YamlConfiguration; ++ ++public class PaperConfig { ++ ++ private static File CONFIG_FILE; ++ private static final String HEADER = "This is the main configuration file for Paper.\n" ++ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" ++ + "with caution, and make sure you know what each option does before configuring.\n" ++ + "\n" ++ + "If you need help with the configuration or have any questions related to Paper,\n" ++ + "join us in our Discord or IRC channel.\n" ++ + "\n" ++ + "Discord: https://discord.gg/papermc\n" ++ + "IRC: #paper @ irc.esper.net ( https://webchat.esper.net/?channels=paper ) \n" ++ + "Website: https://papermc.io/ \n" ++ + "Docs: https://paper.readthedocs.org/ \n"; ++ /*========================================================================*/ ++ public static YamlConfiguration config; ++ static int version; ++ static Map commands; ++ private static boolean verbose; ++ private static boolean fatalError; ++ /*========================================================================*/ ++ ++ public static void init(File configFile) { ++ CONFIG_FILE = configFile; ++ config = new YamlConfiguration(); ++ try { ++ config.load(CONFIG_FILE); ++ } catch (IOException ex) { ++ } catch (InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not load paper.yml, please correct your syntax errors", ex); ++ throw Throwables.propagate(ex); ++ } ++ config.options().header(HEADER); ++ config.options().copyDefaults(true); ++ verbose = getBoolean("verbose", false); ++ ++ commands = new HashMap(); ++ commands.put("paper", new PaperCommand("paper")); ++ ++ version = getInt("config-version", 20); ++ set("config-version", 20); ++ readConfig(PaperConfig.class, null); ++ } ++ ++ protected static void logError(String s) { ++ Bukkit.getLogger().severe(s); ++ } ++ ++ protected static void fatal(String s) { ++ fatalError = true; ++ throw new RuntimeException("Fatal paper.yml config error: " + s); ++ } ++ ++ protected static void log(String s) { ++ if (verbose) { ++ Bukkit.getLogger().info(s); ++ } ++ } ++ ++ public static void registerCommands() { ++ for (Map.Entry entry : commands.entrySet()) { ++ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); ++ } ++ } ++ ++ static void readConfig(Class clazz, Object instance) { ++ for (Method method : clazz.getDeclaredMethods()) { ++ if (Modifier.isPrivate(method.getModifiers())) { ++ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { ++ try { ++ method.setAccessible(true); ++ method.invoke(instance); ++ } catch (InvocationTargetException ex) { ++ throw Throwables.propagate(ex.getCause()); ++ } catch (Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); ++ } ++ } ++ } ++ } ++ ++ try { ++ config.save(CONFIG_FILE); ++ } catch (IOException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); ++ } ++ } ++ ++ private static final Pattern SPACE = Pattern.compile(" "); ++ private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]"); ++ public static int getSeconds(String str) { ++ str = SPACE.matcher(str).replaceAll(""); ++ final char unit = str.charAt(str.length() - 1); ++ str = NOT_NUMERIC.matcher(str).replaceAll(""); ++ double num; ++ try { ++ num = Double.parseDouble(str); ++ } catch (Exception e) { ++ num = 0D; ++ } ++ switch (unit) { ++ case 'd': num *= (double) 60*60*24; break; ++ case 'h': num *= (double) 60*60; break; ++ case 'm': num *= (double) 60; break; ++ default: case 's': break; ++ } ++ return (int) num; ++ } ++ ++ protected static String timeSummary(int seconds) { ++ String time = ""; ++ ++ if (seconds > 60 * 60 * 24) { ++ time += TimeUnit.SECONDS.toDays(seconds) + "d"; ++ seconds %= 60 * 60 * 24; ++ } ++ ++ if (seconds > 60 * 60) { ++ time += TimeUnit.SECONDS.toHours(seconds) + "h"; ++ seconds %= 60 * 60; ++ } ++ ++ if (seconds > 0) { ++ time += TimeUnit.SECONDS.toMinutes(seconds) + "m"; ++ } ++ return time; ++ } ++ ++ private static void set(String path, Object val) { ++ config.set(path, val); ++ } ++ ++ private static boolean getBoolean(String path, boolean def) { ++ config.addDefault(path, def); ++ return config.getBoolean(path, config.getBoolean(path)); ++ } ++ ++ private static double getDouble(String path, double def) { ++ config.addDefault(path, def); ++ return config.getDouble(path, config.getDouble(path)); ++ } ++ ++ private static float getFloat(String path, float def) { ++ // TODO: Figure out why getFloat() always returns the default value. ++ return (float) getDouble(path, (double) def); ++ } ++ ++ private static int getInt(String path, int def) { ++ config.addDefault(path, def); ++ return config.getInt(path, config.getInt(path)); ++ } ++ ++ private static List getList(String path, T def) { ++ config.addDefault(path, def); ++ return (List) config.getList(path, config.getList(path)); ++ } ++ ++ private static String getString(String path, String def) { ++ config.addDefault(path, def); ++ return config.getString(path, config.getString(path)); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b31109d2dadd29e8852468c19265066b773d2be0 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -0,0 +1,68 @@ ++package com.destroystokyo.paper; ++ ++import java.util.List; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.spigotmc.SpigotWorldConfig; ++ ++import static com.destroystokyo.paper.PaperConfig.log; ++import static com.destroystokyo.paper.PaperConfig.logError; ++ ++public class PaperWorldConfig { ++ ++ private final String worldName; ++ private final SpigotWorldConfig spigotConfig; ++ private YamlConfiguration config; ++ private boolean verbose; ++ ++ public PaperWorldConfig(String worldName, SpigotWorldConfig spigotConfig) { ++ this.worldName = worldName; ++ this.spigotConfig = spigotConfig; ++ this.config = PaperConfig.config; ++ init(); ++ } ++ ++ public void init() { ++ this.config = PaperConfig.config; // grab updated reference ++ log("-------- World Settings For [" + worldName + "] --------"); ++ PaperConfig.readConfig(PaperWorldConfig.class, this); ++ } ++ ++ private void set(String path, Object val) { ++ config.set("world-settings.default." + path, val); ++ if (config.get("world-settings." + worldName + "." + path) != null) { ++ config.set("world-settings." + worldName + "." + path, val); ++ } ++ } ++ ++ private boolean getBoolean(String path, boolean def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getBoolean("world-settings." + worldName + "." + path, config.getBoolean("world-settings.default." + path)); ++ } ++ ++ private double getDouble(String path, double def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getDouble("world-settings." + worldName + "." + path, config.getDouble("world-settings.default." + path)); ++ } ++ ++ private int getInt(String path, int def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getInt("world-settings." + worldName + "." + path, config.getInt("world-settings.default." + path)); ++ } ++ ++ private float getFloat(String path, float def) { ++ // TODO: Figure out why getFloat() always returns the default value. ++ return (float) getDouble(path, (double) def); ++ } ++ ++ private List getList(String path, List def) { ++ config.addDefault("world-settings.default." + path, def); ++ return (List) config.getList("world-settings." + worldName + "." + path, config.getList("world-settings.default." + path)); ++ } ++ ++ private String getString(String path, String def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 9366b5551047e87e455fafbf45be5fb145aa875b..5d83a8d4c69144219219877c521c364d912d2452 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -95,6 +95,12 @@ public class Main { + DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(iregistrycustom_dimension, optionset); // CraftBukkit - CLI argument support + + dedicatedserversettings.forceSave(); ++ // Paper start - load config files for access below if needed ++ org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = loadConfigFile((File) optionset.valueOf("bukkit-settings")); ++ org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = loadConfigFile((File) optionset.valueOf("spigot-settings")); ++ org.bukkit.configuration.file.YamlConfiguration paperConfiguration = loadConfigFile((File) optionset.valueOf("paper-settings")); ++ // Paper end ++ + java.nio.file.Path java_nio_file_path1 = Paths.get("eula.txt"); + Eula eula = new Eula(java_nio_file_path1); + +@@ -236,6 +242,20 @@ public class Main { + + } + ++ // Paper start - load config files ++ private static org.bukkit.configuration.file.YamlConfiguration loadConfigFile(File configFile) throws Exception { ++ org.bukkit.configuration.file.YamlConfiguration config = new org.bukkit.configuration.file.YamlConfiguration(); ++ if (configFile.exists()) { ++ try { ++ config.load(configFile); ++ } catch (Exception ex) { ++ throw new Exception("Failed to load configuration file: " + configFile.getName(), ex); ++ } ++ } ++ return config; ++ } ++ // Paper end ++ + public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier booleansupplier, ImmutableSet> worlds) { // CraftBukkit + Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, worlds, eraseCache); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 2228f83f251851aa683f739ac5ce2ec98f059f3f..23d6f803eafa78fd51ea4cdc4ca25c78661bc80b 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -184,6 +184,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + org.spigotmc.SpigotConfig.init((java.io.File) options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ // Paper start ++ try { ++ com.destroystokyo.paper.PaperConfig.init((java.io.File) options.valueOf("paper-settings")); ++ } catch (Exception e) { ++ DedicatedServer.LOGGER.error("Unable to load server configuration", e); ++ return false; ++ } ++ com.destroystokyo.paper.PaperConfig.registerCommands(); ++ // Paper end + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index aa7bf54e4b93a9b6085aa943500f5dec5f60a117..7cc5070f70a4f740add9d971385ceaa4d44275a2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -307,15 +307,15 @@ public class ServerChunkCache extends ChunkSource { + } + } + +- @Override +- public boolean isEntityTickingChunk(Entity entity) { ++ public final boolean isInEntityTickingChunk(Entity entity) { return this.isEntityTickingChunk(entity); } // Paper - OBFHELPER ++ @Override public boolean isEntityTickingChunk(Entity entity) { + long i = ChunkPos.asLong(Mth.floor(entity.getX()) >> 4, Mth.floor(entity.getZ()) >> 4); + + return this.checkChunkFuture(i, (Function>>) ChunkHolder::getEntityTickingChunkFuture); // CraftBukkit - decompile error + } + +- @Override +- public boolean isEntityTickingChunk(ChunkPos pos) { ++ public final boolean isEntityTickingChunk(ChunkPos chunkcoordintpair) { return this.isEntityTickingChunk(chunkcoordintpair); } // Paper - OBFHELPER ++ @Override public boolean isEntityTickingChunk(ChunkPos pos) { + return this.checkChunkFuture(pos.toLong(), (Function>>) ChunkHolder::getEntityTickingChunkFuture); // CraftBukkit - decompile error + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index f82fd4a50921c3c4791be18a43778e6fd216f557..ff482d0349c18d0d1ba902ea0d10611b1ca4e588 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -2,6 +2,7 @@ package net.minecraft.world.entity; + + import com.google.common.collect.ImmutableSet; + import java.util.Optional; ++import java.util.Set; // Paper + import java.util.UUID; + import java.util.function.Function; + import java.util.stream.Stream; +@@ -599,4 +600,10 @@ public class EntityType { + return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions, this.clientTrackingRange, this.updateInterval); + } + } ++ ++ // Paper start ++ public static Set getEntityNameList() { ++ return Registry.ENTITY_TYPE.keySet(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b7c64fcf49ea50fa38a121d906ec6df20a1be31b..f08de81dcc4acd5a3e44407b431ce827a19b2e9c 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -129,6 +129,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean populating; + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + ++ public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper ++ + public final SpigotTimings.WorldTimingsHandler timings; // Spigot + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -149,6 +151,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot ++ this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, env); + this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 761ad2d7e538d1e299d3050446274addcde7d772..328d1e2b128b62f24917719c79823c9fb64a0dcf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -806,6 +806,7 @@ public final class CraftServer implements Server { + } + + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot ++ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper + for (ServerLevel world : console.getAllLevels()) { + world.worldDataServer.setDifficulty(config.difficulty); + world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); +@@ -839,6 +840,7 @@ public final class CraftServer implements Server { + world.ticksPerAmbientSpawns = this.getTicksPerAmbientSpawns(); + } + world.spigotConfig.init(); // Spigot ++ world.paperConfig.init(); // Paper + } + + pluginManager.clearPlugins(); +@@ -846,6 +848,7 @@ public final class CraftServer implements Server { + resetRecipes(); + reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot ++ com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper + overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*"); + ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + +@@ -2101,4 +2104,35 @@ public final class CraftServer implements Server { + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ public static java.nio.file.Path dumpHeap(java.nio.file.Path dir, String name) { ++ try { ++ java.nio.file.Files.createDirectories(dir); ++ ++ javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer(); ++ java.nio.file.Path file; ++ ++ try { ++ Class clazz = Class.forName("openj9.lang.management.OpenJ9DiagnosticsMXBean"); ++ Object openj9Mbean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "openj9.lang.management:type=OpenJ9Diagnostics", clazz); ++ java.lang.reflect.Method m = clazz.getMethod("triggerDumpToFile", String.class, String.class); ++ file = dir.resolve(name + ".phd"); ++ m.invoke(openj9Mbean, "heap", file.toString()); ++ } catch (ClassNotFoundException e) { ++ Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); ++ Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz); ++ java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class); ++ file = dir.resolve(name + ".hprof"); ++ m.invoke(hotspotMBean, file.toString(), true); ++ } ++ ++ return file; ++ } catch (Throwable t) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not write heap", t); ++ return null; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 46a16e31775b28c44f95a8ac5545ebcb656c74b6..05aedca561919a12ced1925c5cc9af585bb04523 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -129,6 +129,14 @@ public class Main { + .defaultsTo(new File("spigot.yml")) + .describedAs("Yml file"); + // Spigot End ++ ++ // Paper Start ++ acceptsAll(asList("paper", "paper-settings"), "File for paper settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("paper.yml")) ++ .describedAs("Yml file"); ++ // Paper end + } + }; + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 83d83ff7ceffbb77723da721b869dfd0091e496d..0efcbab8f8806aeb8dd8bd6384e5a7cee375d100 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -39,36 +39,36 @@ public class SpigotWorldConfig + config.set( "world-settings.default." + path, val ); + } + +- private boolean getBoolean(String path, boolean def) ++ public boolean getBoolean(String path, boolean def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getBoolean( "world-settings." + worldName + "." + path, config.getBoolean( "world-settings.default." + path ) ); + } + +- private double getDouble(String path, double def) ++ public double getDouble(String path, double def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getDouble( "world-settings." + worldName + "." + path, config.getDouble( "world-settings.default." + path ) ); + } + +- private int getInt(String path) ++ public int getInt(String path) // Paper - private -> public + { + return config.getInt( "world-settings." + worldName + "." + path ); + } + +- private int getInt(String path, int def) ++ public int getInt(String path, int def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getInt( "world-settings." + worldName + "." + path, config.getInt( "world-settings.default." + path ) ); + } + +- private List getList(String path, T def) ++ public List getList(String path, T def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return (List) config.getList( "world-settings." + worldName + "." + path, config.getList( "world-settings.default." + path ) ); + } + +- private String getString(String path, String def) ++ public String getString(String path, String def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) ); diff --git a/Remapped-Spigot-Server-Patches/0003-MC-Dev-fixes.patch b/Remapped-Spigot-Server-Patches/0003-MC-Dev-fixes.patch new file mode 100644 index 000000000..10c424b9a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0003-MC-Dev-fixes.patch @@ -0,0 +1,929 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 30 Mar 2016 19:36:20 -0400 +Subject: [PATCH] MC Dev fixes + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 16220ce9a7db722c8b351f8016fd1df066ffcb98..8c378d3f3138953b3b22b289fecdb6b40a09ab63 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -65,8 +65,8 @@ public class Util { + return Collectors.toMap(Entry::getKey, Entry::getValue); + } + +- public static > String getPropertyName(Property iblockstate, Object object) { +- return iblockstate.value((Comparable) object); ++ public static > String a(Property iblockstate, T object) { // Paper - decompile fix ++ return iblockstate.getName(object); // Paper - decompile fix + } + + public static String makeDescriptionId(String type, @Nullable ResourceLocation id) { +@@ -234,8 +234,8 @@ public class Util { + public static T findPreviousInIterable(Iterable iterable, @Nullable T t0) { + Iterator iterator = iterable.iterator(); + +- Object object; +- Object object1; ++ T object; // Paper - decompile fix ++ T object1; // Paper - decompile fix + + for (object1 = null; iterator.hasNext(); object1 = object) { + object = iterator.next(); +@@ -260,7 +260,7 @@ public class Util { + } + + public static Strategy identityStrategy() { +- return Util.IdentityStrategy.INSTANCE; ++ return (Strategy) Util.IdentityStrategy.INSTANCE; // Paper - decompile fix + } + + public static CompletableFuture> sequence(List> futures) { +@@ -271,7 +271,7 @@ public class Util { + futures.forEach((completablefuture1) -> { + int i = list1.size(); + +- list1.add((Object) null); ++ list1.add(null); // Paper - decompile fix + acompletablefuture[i] = completablefuture1.whenComplete((object, throwable) -> { + if (throwable != null) { + completablefuture.completeExceptionally(throwable); +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index aa35d67cc27a4f982575eab3be46f4187f29d3fe..88147a1f25cf2fd549412b653b8f0eb5c60bb55d 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -241,8 +241,8 @@ public class BlockPos extends Vec3i { + }; + } + +- public static Iterable withinManhattan(BlockPos center, int xRange, int yRange, int zRange) { +- int l = xRange + yRange + zRange; ++ public static Iterable withinManhattan(BlockPos center, int xRange, int yRange, int zRange) { // Paper - decompile issues - variable name conflicts to inner class field refs ++ int l_decompiled = xRange + yRange + zRange; // Paper - decompile issues + int i1 = center.getX(); + int j1 = center.getY(); + int k1 = center.getZ(); +@@ -270,15 +270,15 @@ public class BlockPos extends Vec3i { + ++this.x; + if (this.x > this.maxX) { + ++this.currentDepth; +- if (this.currentDepth > x) { ++ if (this.currentDepth > l_decompiled) { // Paper - use proper l above (first line of this method) + return (BlockPos) this.endOfData(); + } + +- this.maxX = Math.min(currentDepth, this.currentDepth); ++ this.maxX = Math.min(xRange, this.currentDepth); // Paper - decompile issues + this.x = -this.maxX; + } + +- this.maxY = Math.min(maxX, this.currentDepth - Math.abs(this.x)); ++ this.maxY = Math.min(yRange, this.currentDepth - Math.abs(this.x)); // Paper - decompile issues + this.y = -this.maxY; + } + +@@ -286,7 +286,7 @@ public class BlockPos extends Vec3i { + int i2 = this.y; + int j2 = this.currentDepth - Math.abs(l1) - Math.abs(i2); + +- if (j2 <= maxY) { ++ if (j2 <= zRange) { // Paper - decompile issues + this.zMirror = j2 != 0; + blockposition_mutableblockposition = this.cursor.set(i1 + l1, j1 + i2, k1 + j2); + } +@@ -355,13 +355,13 @@ public class BlockPos extends Vec3i { + }; + } + +- public static Iterable spiralAround(BlockPos blockposition, int i, Direction enumdirection, Direction enumdirection1) { ++ public static Iterable spiralAround(BlockPos blockposition, int I, Direction enumdirection, Direction enumdirection1) { // Paper - decompile fix + Validate.validState(enumdirection.getAxis() != enumdirection1.getAxis(), "The two directions cannot be on the same axis", new Object[0]); + return () -> { + return new AbstractIterator() { + private final Direction[] directions = new Direction[]{enumdirection, enumdirection1, enumdirection.getOpposite(), enumdirection1.getOpposite()}; + private final BlockPos.MutableBlockPos cursor = blockposition.mutable().move(enumdirection1); +- private final int legs = 4 * legSize; ++ private final int legs = 4 * I; + private int leg = -1; + private int legSize; + private int legIndex; +diff --git a/src/main/java/net/minecraft/core/IdMapper.java b/src/main/java/net/minecraft/core/IdMapper.java +index 71c591487853bec3cecf5777c09ddc05bd658b64..424c6cacc2e7c7b1c9d0b92fe198237033a3fcbd 100644 +--- a/src/main/java/net/minecraft/core/IdMapper.java ++++ b/src/main/java/net/minecraft/core/IdMapper.java +@@ -27,7 +27,7 @@ public class IdMapper implements IdMap { + this.tToId.put(value, id); + + while (this.idToT.size() <= id) { +- this.idToT.add((Object) null); ++ this.idToT.add(null); // Paper - decompile fix + } + + this.idToT.set(id, value); +@@ -41,6 +41,13 @@ public class IdMapper implements IdMap { + this.addMapping(value, this.nextId); + } + ++ // Paper start - decompile fix ++ @Override ++ public int a(T t) { ++ return getId(t); ++ } ++ // Paper end ++ + public int getId(T entry) { + Integer integer = (Integer) this.tToId.get(entry); + +diff --git a/src/main/java/net/minecraft/nbt/ListTag.java b/src/main/java/net/minecraft/nbt/ListTag.java +index efc632a8ac13f77aaf2229a09e84416c09c86255..084340dc73acb3d972e0717b48da820c027a5137 100644 +--- a/src/main/java/net/minecraft/nbt/ListTag.java ++++ b/src/main/java/net/minecraft/nbt/ListTag.java +@@ -53,7 +53,7 @@ public class ListTag extends CollectionTag { + return "TAG_List"; + } + }; +- private static final ByteSet INLINE_ELEMENT_TYPES = new ByteOpenHashSet(Arrays.asList(1, 2, 3, 4, 5, 6)); ++ private static final ByteSet INLINE_ELEMENT_TYPES = new ByteOpenHashSet(Arrays.asList((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6)); // Paper - decompiler fix + private final List list; + private byte type; + +diff --git a/src/main/java/net/minecraft/nbt/NbtIo.java b/src/main/java/net/minecraft/nbt/NbtIo.java +index 5494db9e8e04d976aa1b005a108b452cd481d0bb..c4fbc0bc8b80d16f09d3c6642acc7476a0817868 100644 +--- a/src/main/java/net/minecraft/nbt/NbtIo.java ++++ b/src/main/java/net/minecraft/nbt/NbtIo.java +@@ -18,6 +18,7 @@ import java.util.zip.GZIPOutputStream; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; ++import io.netty.buffer.ByteBufInputStream; // Paper + + public class NbtIo { + +@@ -137,7 +138,7 @@ public class NbtIo { + + public static CompoundTag read(DataInput input, NbtAccounter tracker) throws IOException { + // Spigot start +- if ( input instanceof io.netty.buffer.ByteBufInputStream ) ++ if ( input instanceof ByteBufInputStream) // Paper + { + input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, tracker)); + } +diff --git a/src/main/java/net/minecraft/nbt/Tag.java b/src/main/java/net/minecraft/nbt/Tag.java +index 483c33e9b2d64c1a003d3bb543486b8a545bc96a..85e9c5f4620fcf48cb3655fbb2db58b3fb31aa74 100644 +--- a/src/main/java/net/minecraft/nbt/Tag.java ++++ b/src/main/java/net/minecraft/nbt/Tag.java +@@ -20,7 +20,7 @@ public interface Tag { + + TagType getType(); + +- Tag copy(); ++ public Tag copy(); // Paper - decompile fix + + default String getAsString() { + return this.toString(); +diff --git a/src/main/java/net/minecraft/network/ConnectionProtocol.java b/src/main/java/net/minecraft/network/ConnectionProtocol.java +index a12d169edf4e087b18fedf7199d6eb7ee58f0305..fca778d131aa10e88d5f7ed8d57eda6803318184 100644 +--- a/src/main/java/net/minecraft/network/ConnectionProtocol.java ++++ b/src/main/java/net/minecraft/network/ConnectionProtocol.java +@@ -14,23 +14,29 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.PacketFlow; + import net.minecraft.network.protocol.game.*; + import net.minecraft.network.protocol.handshake.ClientIntentionPacket; ++import net.minecraft.network.protocol.handshake.ServerHandshakePacketListener; ++import net.minecraft.network.protocol.login.ClientLoginPacketListener; + import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; + import net.minecraft.network.protocol.login.ClientboundGameProfilePacket; + import net.minecraft.network.protocol.login.ClientboundHelloPacket; + import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket; + import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; ++import net.minecraft.network.protocol.login.ServerLoginPacketListener; + import net.minecraft.network.protocol.login.ServerboundCustomQueryPacket; + import net.minecraft.network.protocol.login.ServerboundHelloPacket; + import net.minecraft.network.protocol.login.ServerboundKeyPacket; ++import net.minecraft.network.protocol.status.ClientStatusPacketListener; + import net.minecraft.network.protocol.status.ClientboundPongResponsePacket; + import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket; ++import net.minecraft.network.protocol.status.ServerStatusPacketListener; + import net.minecraft.network.protocol.status.ServerboundPingRequestPacket; + import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; + import org.apache.logging.log4j.LogManager; + + public enum ConnectionProtocol { + +- HANDSHAKING(-1, protocol().addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ClientIntentionPacket.class, ClientIntentionPacket::new))), PLAY(0, protocol().addFlow(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::new).a(ClientboundAddExperienceOrbPacket.class, ClientboundAddExperienceOrbPacket::new).a(ClientboundAddMobPacket.class, ClientboundAddMobPacket::new).a(ClientboundAddPaintingPacket.class, ClientboundAddPaintingPacket::new).a(ClientboundAddPlayerPacket.class, ClientboundAddPlayerPacket::new).a(ClientboundAnimatePacket.class, ClientboundAnimatePacket::new).a(ClientboundAwardStatsPacket.class, ClientboundAwardStatsPacket::new).a(ClientboundBlockBreakAckPacket.class, ClientboundBlockBreakAckPacket::new).a(ClientboundBlockDestructionPacket.class, ClientboundBlockDestructionPacket::new).a(ClientboundBlockEntityDataPacket.class, ClientboundBlockEntityDataPacket::new).a(ClientboundBlockEventPacket.class, ClientboundBlockEventPacket::new).a(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new).a(ClientboundBossEventPacket.class, ClientboundBossEventPacket::new).a(ClientboundChangeDifficultyPacket.class, ClientboundChangeDifficultyPacket::new).a(ClientboundChatPacket.class, ClientboundChatPacket::new).a(ClientboundCommandSuggestionsPacket.class, ClientboundCommandSuggestionsPacket::new).a(ClientboundCommandsPacket.class, ClientboundCommandsPacket::new).a(ClientboundContainerAckPacket.class, ClientboundContainerAckPacket::new).a(ClientboundContainerClosePacket.class, ClientboundContainerClosePacket::new).a(ClientboundContainerSetContentPacket.class, ClientboundContainerSetContentPacket::new).a(ClientboundContainerSetDataPacket.class, ClientboundContainerSetDataPacket::new).a(ClientboundContainerSetSlotPacket.class, ClientboundContainerSetSlotPacket::new).a(ClientboundCooldownPacket.class, ClientboundCooldownPacket::new).a(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket::new).a(ClientboundCustomSoundPacket.class, ClientboundCustomSoundPacket::new).a(ClientboundDisconnectPacket.class, ClientboundDisconnectPacket::new).a(ClientboundEntityEventPacket.class, ClientboundEntityEventPacket::new).a(ClientboundExplodePacket.class, ClientboundExplodePacket::new).a(ClientboundForgetLevelChunkPacket.class, ClientboundForgetLevelChunkPacket::new).a(ClientboundGameEventPacket.class, ClientboundGameEventPacket::new).a(ClientboundHorseScreenOpenPacket.class, ClientboundHorseScreenOpenPacket::new).a(ClientboundKeepAlivePacket.class, ClientboundKeepAlivePacket::new).a(ClientboundLevelChunkPacket.class, ClientboundLevelChunkPacket::new).a(ClientboundLevelEventPacket.class, ClientboundLevelEventPacket::new).a(ClientboundLevelParticlesPacket.class, ClientboundLevelParticlesPacket::new).a(ClientboundLightUpdatePacket.class, ClientboundLightUpdatePacket::new).a(ClientboundLoginPacket.class, ClientboundLoginPacket::new).a(ClientboundMapItemDataPacket.class, ClientboundMapItemDataPacket::new).a(ClientboundMerchantOffersPacket.class, ClientboundMerchantOffersPacket::new).a(ClientboundMoveEntityPacket.Pos.class, ClientboundMoveEntityPacket.Pos::new).a(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket.PosRot::new).a(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket.Rot::new).a(ClientboundMoveEntityPacket.class, ClientboundMoveEntityPacket::new).a(ClientboundMoveVehiclePacket.class, ClientboundMoveVehiclePacket::new).a(ClientboundOpenBookPacket.class, ClientboundOpenBookPacket::new).a(ClientboundOpenScreenPacket.class, ClientboundOpenScreenPacket::new).a(ClientboundOpenSignEditorPacket.class, ClientboundOpenSignEditorPacket::new).a(ClientboundPlaceGhostRecipePacket.class, ClientboundPlaceGhostRecipePacket::new).a(ClientboundPlayerAbilitiesPacket.class, ClientboundPlayerAbilitiesPacket::new).a(ClientboundPlayerCombatPacket.class, ClientboundPlayerCombatPacket::new).a(ClientboundPlayerInfoPacket.class, ClientboundPlayerInfoPacket::new).a(ClientboundPlayerLookAtPacket.class, ClientboundPlayerLookAtPacket::new).a(ClientboundPlayerPositionPacket.class, ClientboundPlayerPositionPacket::new).a(ClientboundRecipePacket.class, ClientboundRecipePacket::new).a(ClientboundRemoveEntitiesPacket.class, ClientboundRemoveEntitiesPacket::new).a(ClientboundRemoveMobEffectPacket.class, ClientboundRemoveMobEffectPacket::new).a(ClientboundResourcePackPacket.class, ClientboundResourcePackPacket::new).a(ClientboundRespawnPacket.class, ClientboundRespawnPacket::new).a(ClientboundRotateHeadPacket.class, ClientboundRotateHeadPacket::new).a(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new).a(ClientboundSelectAdvancementsTabPacket.class, ClientboundSelectAdvancementsTabPacket::new).a(ClientboundSetBorderPacket.class, ClientboundSetBorderPacket::new).a(ClientboundSetCameraPacket.class, ClientboundSetCameraPacket::new).a(ClientboundSetCarriedItemPacket.class, ClientboundSetCarriedItemPacket::new).a(ClientboundSetChunkCacheCenterPacket.class, ClientboundSetChunkCacheCenterPacket::new).a(ClientboundSetChunkCacheRadiusPacket.class, ClientboundSetChunkCacheRadiusPacket::new).a(ClientboundSetDefaultSpawnPositionPacket.class, ClientboundSetDefaultSpawnPositionPacket::new).a(ClientboundSetDisplayObjectivePacket.class, ClientboundSetDisplayObjectivePacket::new).a(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::new).a(ClientboundSetEntityLinkPacket.class, ClientboundSetEntityLinkPacket::new).a(ClientboundSetEntityMotionPacket.class, ClientboundSetEntityMotionPacket::new).a(ClientboundSetEquipmentPacket.class, ClientboundSetEquipmentPacket::new).a(ClientboundSetExperiencePacket.class, ClientboundSetExperiencePacket::new).a(ClientboundSetHealthPacket.class, ClientboundSetHealthPacket::new).a(ClientboundSetObjectivePacket.class, ClientboundSetObjectivePacket::new).a(ClientboundSetPassengersPacket.class, ClientboundSetPassengersPacket::new).a(ClientboundSetPlayerTeamPacket.class, ClientboundSetPlayerTeamPacket::new).a(ClientboundSetScorePacket.class, ClientboundSetScorePacket::new).a(ClientboundSetTimePacket.class, ClientboundSetTimePacket::new).a(ClientboundSetTitlesPacket.class, ClientboundSetTitlesPacket::new).a(ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new).a(ClientboundSoundPacket.class, ClientboundSoundPacket::new).a(ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new).a(ClientboundTabListPacket.class, ClientboundTabListPacket::new).a(ClientboundTagQueryPacket.class, ClientboundTagQueryPacket::new).a(ClientboundTakeItemEntityPacket.class, ClientboundTakeItemEntityPacket::new).a(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::new).a(ClientboundUpdateAdvancementsPacket.class, ClientboundUpdateAdvancementsPacket::new).a(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::new).a(ClientboundUpdateMobEffectPacket.class, ClientboundUpdateMobEffectPacket::new).a(ClientboundUpdateRecipesPacket.class, ClientboundUpdateRecipesPacket::new).a(ClientboundUpdateTagsPacket.class, ClientboundUpdateTagsPacket::new)).a(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ServerboundAcceptTeleportationPacket.class, ServerboundAcceptTeleportationPacket::new).a(ServerboundBlockEntityTagQuery.class, ServerboundBlockEntityTagQuery::new).a(ServerboundChangeDifficultyPacket.class, ServerboundChangeDifficultyPacket::new).a(ServerboundChatPacket.class, ServerboundChatPacket::new).a(ServerboundClientCommandPacket.class, ServerboundClientCommandPacket::new).a(ServerboundClientInformationPacket.class, ServerboundClientInformationPacket::new).a(ServerboundCommandSuggestionPacket.class, ServerboundCommandSuggestionPacket::new).a(ServerboundContainerAckPacket.class, ServerboundContainerAckPacket::new).a(ServerboundContainerButtonClickPacket.class, ServerboundContainerButtonClickPacket::new).a(ServerboundContainerClickPacket.class, ServerboundContainerClickPacket::new).a(ServerboundContainerClosePacket.class, ServerboundContainerClosePacket::new).a(ServerboundCustomPayloadPacket.class, ServerboundCustomPayloadPacket::new).a(ServerboundEditBookPacket.class, ServerboundEditBookPacket::new).a(ServerboundEntityTagQuery.class, ServerboundEntityTagQuery::new).a(ServerboundInteractPacket.class, ServerboundInteractPacket::new).a(ServerboundJigsawGeneratePacket.class, ServerboundJigsawGeneratePacket::new).a(ServerboundKeepAlivePacket.class, ServerboundKeepAlivePacket::new).a(ServerboundLockDifficultyPacket.class, ServerboundLockDifficultyPacket::new).a(ServerboundMovePlayerPacket.Pos.class, ServerboundMovePlayerPacket.Pos::new).a(ServerboundMovePlayerPacket.PosRot.class, ServerboundMovePlayerPacket.PosRot::new).a(ServerboundMovePlayerPacket.Rot.class, ServerboundMovePlayerPacket.Rot::new).a(ServerboundMovePlayerPacket.class, ServerboundMovePlayerPacket::new).a(ServerboundMoveVehiclePacket.class, ServerboundMoveVehiclePacket::new).a(ServerboundPaddleBoatPacket.class, ServerboundPaddleBoatPacket::new).a(ServerboundPickItemPacket.class, ServerboundPickItemPacket::new).a(ServerboundPlaceRecipePacket.class, ServerboundPlaceRecipePacket::new).a(ServerboundPlayerAbilitiesPacket.class, ServerboundPlayerAbilitiesPacket::new).a(ServerboundPlayerActionPacket.class, ServerboundPlayerActionPacket::new).a(ServerboundPlayerCommandPacket.class, ServerboundPlayerCommandPacket::new).a(ServerboundPlayerInputPacket.class, ServerboundPlayerInputPacket::new).a(ServerboundRecipeBookChangeSettingsPacket.class, ServerboundRecipeBookChangeSettingsPacket::new).a(ServerboundRecipeBookSeenRecipePacket.class, ServerboundRecipeBookSeenRecipePacket::new).a(ServerboundRenameItemPacket.class, ServerboundRenameItemPacket::new).a(ServerboundResourcePackPacket.class, ServerboundResourcePackPacket::new).a(ServerboundSeenAdvancementsPacket.class, ServerboundSeenAdvancementsPacket::new).a(ServerboundSelectTradePacket.class, ServerboundSelectTradePacket::new).a(ServerboundSetBeaconPacket.class, ServerboundSetBeaconPacket::new).a(ServerboundSetCarriedItemPacket.class, ServerboundSetCarriedItemPacket::new).a(ServerboundSetCommandBlockPacket.class, ServerboundSetCommandBlockPacket::new).a(ServerboundSetCommandMinecartPacket.class, ServerboundSetCommandMinecartPacket::new).a(ServerboundSetCreativeModeSlotPacket.class, ServerboundSetCreativeModeSlotPacket::new).a(ServerboundSetJigsawBlockPacket.class, ServerboundSetJigsawBlockPacket::new).a(ServerboundSetStructureBlockPacket.class, ServerboundSetStructureBlockPacket::new).a(ServerboundSignUpdatePacket.class, ServerboundSignUpdatePacket::new).a(ServerboundSwingPacket.class, ServerboundSwingPacket::new).a(ServerboundTeleportToEntityPacket.class, ServerboundTeleportToEntityPacket::new).a(ServerboundUseItemOnPacket.class, ServerboundUseItemOnPacket::new).a(ServerboundUseItemPacket.class, ServerboundUseItemPacket::new))), STATUS(1, protocol().addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ServerboundStatusRequestPacket.class, ServerboundStatusRequestPacket::new).a(ServerboundPingRequestPacket.class, ServerboundPingRequestPacket::new)).a(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ClientboundStatusResponsePacket.class, ClientboundStatusResponsePacket::new).a(ClientboundPongResponsePacket.class, ClientboundPongResponsePacket::new))), LOGIN(2, protocol().addFlow(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ClientboundLoginDisconnectPacket.class, ClientboundLoginDisconnectPacket::new).a(ClientboundHelloPacket.class, ClientboundHelloPacket::new).a(ClientboundGameProfilePacket.class, ClientboundGameProfilePacket::new).a(ClientboundLoginCompressionPacket.class, ClientboundLoginCompressionPacket::new).a(ClientboundCustomQueryPacket.class, ClientboundCustomQueryPacket::new)).a(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet<>()).addPacket(ServerboundHelloPacket.class, ServerboundHelloPacket::new).a(ServerboundKeyPacket.class, ServerboundKeyPacket::new).a(ServerboundCustomQueryPacket.class, ServerboundCustomQueryPacket::new))); ++ // Paper - fix decompile error - add generic names to < > like PacketListenerPlayOut ++ HANDSHAKING(-1, protocol().addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ClientIntentionPacket.class, ClientIntentionPacket::new))), PLAY(0, protocol().addFlow(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::new).addPacket(ClientboundAddExperienceOrbPacket.class, ClientboundAddExperienceOrbPacket::new).addPacket(ClientboundAddMobPacket.class, ClientboundAddMobPacket::new).addPacket(ClientboundAddPaintingPacket.class, ClientboundAddPaintingPacket::new).addPacket(ClientboundAddPlayerPacket.class, ClientboundAddPlayerPacket::new).addPacket(ClientboundAnimatePacket.class, ClientboundAnimatePacket::new).addPacket(ClientboundAwardStatsPacket.class, ClientboundAwardStatsPacket::new).addPacket(ClientboundBlockBreakAckPacket.class, ClientboundBlockBreakAckPacket::new).addPacket(ClientboundBlockDestructionPacket.class, ClientboundBlockDestructionPacket::new).addPacket(ClientboundBlockEntityDataPacket.class, ClientboundBlockEntityDataPacket::new).addPacket(ClientboundBlockEventPacket.class, ClientboundBlockEventPacket::new).addPacket(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new).addPacket(ClientboundBossEventPacket.class, ClientboundBossEventPacket::new).addPacket(ClientboundChangeDifficultyPacket.class, ClientboundChangeDifficultyPacket::new).addPacket(ClientboundChatPacket.class, ClientboundChatPacket::new).addPacket(ClientboundCommandSuggestionsPacket.class, ClientboundCommandSuggestionsPacket::new).addPacket(ClientboundCommandsPacket.class, ClientboundCommandsPacket::new).addPacket(ClientboundContainerAckPacket.class, ClientboundContainerAckPacket::new).addPacket(ClientboundContainerClosePacket.class, ClientboundContainerClosePacket::new).addPacket(ClientboundContainerSetContentPacket.class, ClientboundContainerSetContentPacket::new).addPacket(ClientboundContainerSetDataPacket.class, ClientboundContainerSetDataPacket::new).addPacket(ClientboundContainerSetSlotPacket.class, ClientboundContainerSetSlotPacket::new).addPacket(ClientboundCooldownPacket.class, ClientboundCooldownPacket::new).addPacket(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket::new).addPacket(ClientboundCustomSoundPacket.class, ClientboundCustomSoundPacket::new).addPacket(ClientboundDisconnectPacket.class, ClientboundDisconnectPacket::new).addPacket(ClientboundEntityEventPacket.class, ClientboundEntityEventPacket::new).addPacket(ClientboundExplodePacket.class, ClientboundExplodePacket::new).addPacket(ClientboundForgetLevelChunkPacket.class, ClientboundForgetLevelChunkPacket::new).addPacket(ClientboundGameEventPacket.class, ClientboundGameEventPacket::new).addPacket(ClientboundHorseScreenOpenPacket.class, ClientboundHorseScreenOpenPacket::new).addPacket(ClientboundKeepAlivePacket.class, ClientboundKeepAlivePacket::new).addPacket(ClientboundLevelChunkPacket.class, ClientboundLevelChunkPacket::new).addPacket(ClientboundLevelEventPacket.class, ClientboundLevelEventPacket::new).addPacket(ClientboundLevelParticlesPacket.class, ClientboundLevelParticlesPacket::new).addPacket(ClientboundLightUpdatePacket.class, ClientboundLightUpdatePacket::new).addPacket(ClientboundLoginPacket.class, ClientboundLoginPacket::new).addPacket(ClientboundMapItemDataPacket.class, ClientboundMapItemDataPacket::new).addPacket(ClientboundMerchantOffersPacket.class, ClientboundMerchantOffersPacket::new).addPacket(ClientboundMoveEntityPacket.Pos.class, ClientboundMoveEntityPacket.Pos::new).addPacket(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket.PosRot::new).addPacket(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket.Rot::new).addPacket(ClientboundMoveEntityPacket.class, ClientboundMoveEntityPacket::new).addPacket(ClientboundMoveVehiclePacket.class, ClientboundMoveVehiclePacket::new).addPacket(ClientboundOpenBookPacket.class, ClientboundOpenBookPacket::new).addPacket(ClientboundOpenScreenPacket.class, ClientboundOpenScreenPacket::new).addPacket(ClientboundOpenSignEditorPacket.class, ClientboundOpenSignEditorPacket::new).addPacket(ClientboundPlaceGhostRecipePacket.class, ClientboundPlaceGhostRecipePacket::new).addPacket(ClientboundPlayerAbilitiesPacket.class, ClientboundPlayerAbilitiesPacket::new).addPacket(ClientboundPlayerCombatPacket.class, ClientboundPlayerCombatPacket::new).addPacket(ClientboundPlayerInfoPacket.class, ClientboundPlayerInfoPacket::new).addPacket(ClientboundPlayerLookAtPacket.class, ClientboundPlayerLookAtPacket::new).addPacket(ClientboundPlayerPositionPacket.class, ClientboundPlayerPositionPacket::new).addPacket(ClientboundRecipePacket.class, ClientboundRecipePacket::new).addPacket(ClientboundRemoveEntitiesPacket.class, ClientboundRemoveEntitiesPacket::new).addPacket(ClientboundRemoveMobEffectPacket.class, ClientboundRemoveMobEffectPacket::new).addPacket(ClientboundResourcePackPacket.class, ClientboundResourcePackPacket::new).addPacket(ClientboundRespawnPacket.class, ClientboundRespawnPacket::new).addPacket(ClientboundRotateHeadPacket.class, ClientboundRotateHeadPacket::new).addPacket(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new).addPacket(ClientboundSelectAdvancementsTabPacket.class, ClientboundSelectAdvancementsTabPacket::new).addPacket(ClientboundSetBorderPacket.class, ClientboundSetBorderPacket::new).addPacket(ClientboundSetCameraPacket.class, ClientboundSetCameraPacket::new).addPacket(ClientboundSetCarriedItemPacket.class, ClientboundSetCarriedItemPacket::new).addPacket(ClientboundSetChunkCacheCenterPacket.class, ClientboundSetChunkCacheCenterPacket::new).addPacket(ClientboundSetChunkCacheRadiusPacket.class, ClientboundSetChunkCacheRadiusPacket::new).addPacket(ClientboundSetDefaultSpawnPositionPacket.class, ClientboundSetDefaultSpawnPositionPacket::new).addPacket(ClientboundSetDisplayObjectivePacket.class, ClientboundSetDisplayObjectivePacket::new).addPacket(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::new).addPacket(ClientboundSetEntityLinkPacket.class, ClientboundSetEntityLinkPacket::new).addPacket(ClientboundSetEntityMotionPacket.class, ClientboundSetEntityMotionPacket::new).addPacket(ClientboundSetEquipmentPacket.class, ClientboundSetEquipmentPacket::new).addPacket(ClientboundSetExperiencePacket.class, ClientboundSetExperiencePacket::new).addPacket(ClientboundSetHealthPacket.class, ClientboundSetHealthPacket::new).addPacket(ClientboundSetObjectivePacket.class, ClientboundSetObjectivePacket::new).addPacket(ClientboundSetPassengersPacket.class, ClientboundSetPassengersPacket::new).addPacket(ClientboundSetPlayerTeamPacket.class, ClientboundSetPlayerTeamPacket::new).addPacket(ClientboundSetScorePacket.class, ClientboundSetScorePacket::new).addPacket(ClientboundSetTimePacket.class, ClientboundSetTimePacket::new).addPacket(ClientboundSetTitlesPacket.class, ClientboundSetTitlesPacket::new).addPacket(ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new).addPacket(ClientboundSoundPacket.class, ClientboundSoundPacket::new).addPacket(ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new).addPacket(ClientboundTabListPacket.class, ClientboundTabListPacket::new).addPacket(ClientboundTagQueryPacket.class, ClientboundTagQueryPacket::new).addPacket(ClientboundTakeItemEntityPacket.class, ClientboundTakeItemEntityPacket::new).addPacket(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::new).addPacket(ClientboundUpdateAdvancementsPacket.class, ClientboundUpdateAdvancementsPacket::new).addPacket(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::new).addPacket(ClientboundUpdateMobEffectPacket.class, ClientboundUpdateMobEffectPacket::new).addPacket(ClientboundUpdateRecipesPacket.class, ClientboundUpdateRecipesPacket::new).addPacket(ClientboundUpdateTagsPacket.class, ClientboundUpdateTagsPacket::new)).addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ServerboundAcceptTeleportationPacket.class, ServerboundAcceptTeleportationPacket::new).addPacket(ServerboundBlockEntityTagQuery.class, ServerboundBlockEntityTagQuery::new).addPacket(ServerboundChangeDifficultyPacket.class, ServerboundChangeDifficultyPacket::new).addPacket(ServerboundChatPacket.class, ServerboundChatPacket::new).addPacket(ServerboundClientCommandPacket.class, ServerboundClientCommandPacket::new).addPacket(ServerboundClientInformationPacket.class, ServerboundClientInformationPacket::new).addPacket(ServerboundCommandSuggestionPacket.class, ServerboundCommandSuggestionPacket::new).addPacket(ServerboundContainerAckPacket.class, ServerboundContainerAckPacket::new).addPacket(ServerboundContainerButtonClickPacket.class, ServerboundContainerButtonClickPacket::new).addPacket(ServerboundContainerClickPacket.class, ServerboundContainerClickPacket::new).addPacket(ServerboundContainerClosePacket.class, ServerboundContainerClosePacket::new).addPacket(ServerboundCustomPayloadPacket.class, ServerboundCustomPayloadPacket::new).addPacket(ServerboundEditBookPacket.class, ServerboundEditBookPacket::new).addPacket(ServerboundEntityTagQuery.class, ServerboundEntityTagQuery::new).addPacket(ServerboundInteractPacket.class, ServerboundInteractPacket::new).addPacket(ServerboundJigsawGeneratePacket.class, ServerboundJigsawGeneratePacket::new).addPacket(ServerboundKeepAlivePacket.class, ServerboundKeepAlivePacket::new).addPacket(ServerboundLockDifficultyPacket.class, ServerboundLockDifficultyPacket::new).addPacket(ServerboundMovePlayerPacket.Pos.class, ServerboundMovePlayerPacket.Pos::new).addPacket(ServerboundMovePlayerPacket.PosRot.class, ServerboundMovePlayerPacket.PosRot::new).addPacket(ServerboundMovePlayerPacket.Rot.class, ServerboundMovePlayerPacket.Rot::new).addPacket(ServerboundMovePlayerPacket.class, ServerboundMovePlayerPacket::new).addPacket(ServerboundMoveVehiclePacket.class, ServerboundMoveVehiclePacket::new).addPacket(ServerboundPaddleBoatPacket.class, ServerboundPaddleBoatPacket::new).addPacket(ServerboundPickItemPacket.class, ServerboundPickItemPacket::new).addPacket(ServerboundPlaceRecipePacket.class, ServerboundPlaceRecipePacket::new).addPacket(ServerboundPlayerAbilitiesPacket.class, ServerboundPlayerAbilitiesPacket::new).addPacket(ServerboundPlayerActionPacket.class, ServerboundPlayerActionPacket::new).addPacket(ServerboundPlayerCommandPacket.class, ServerboundPlayerCommandPacket::new).addPacket(ServerboundPlayerInputPacket.class, ServerboundPlayerInputPacket::new).addPacket(ServerboundRecipeBookChangeSettingsPacket.class, ServerboundRecipeBookChangeSettingsPacket::new).addPacket(ServerboundRecipeBookSeenRecipePacket.class, ServerboundRecipeBookSeenRecipePacket::new).addPacket(ServerboundRenameItemPacket.class, ServerboundRenameItemPacket::new).addPacket(ServerboundResourcePackPacket.class, ServerboundResourcePackPacket::new).addPacket(ServerboundSeenAdvancementsPacket.class, ServerboundSeenAdvancementsPacket::new).addPacket(ServerboundSelectTradePacket.class, ServerboundSelectTradePacket::new).addPacket(ServerboundSetBeaconPacket.class, ServerboundSetBeaconPacket::new).addPacket(ServerboundSetCarriedItemPacket.class, ServerboundSetCarriedItemPacket::new).addPacket(ServerboundSetCommandBlockPacket.class, ServerboundSetCommandBlockPacket::new).addPacket(ServerboundSetCommandMinecartPacket.class, ServerboundSetCommandMinecartPacket::new).addPacket(ServerboundSetCreativeModeSlotPacket.class, ServerboundSetCreativeModeSlotPacket::new).addPacket(ServerboundSetJigsawBlockPacket.class, ServerboundSetJigsawBlockPacket::new).addPacket(ServerboundSetStructureBlockPacket.class, ServerboundSetStructureBlockPacket::new).addPacket(ServerboundSignUpdatePacket.class, ServerboundSignUpdatePacket::new).addPacket(ServerboundSwingPacket.class, ServerboundSwingPacket::new).addPacket(ServerboundTeleportToEntityPacket.class, ServerboundTeleportToEntityPacket::new).addPacket(ServerboundUseItemOnPacket.class, ServerboundUseItemOnPacket::new).addPacket(ServerboundUseItemPacket.class, ServerboundUseItemPacket::new))), STATUS(1, protocol().addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ServerboundStatusRequestPacket.class, ServerboundStatusRequestPacket::new).addPacket(ServerboundPingRequestPacket.class, ServerboundPingRequestPacket::new)).addFlow(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ClientboundStatusResponsePacket.class, ClientboundStatusResponsePacket::new).addPacket(ClientboundPongResponsePacket.class, ClientboundPongResponsePacket::new))), LOGIN(2, protocol().addFlow(PacketFlow.CLIENTBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ClientboundLoginDisconnectPacket.class, ClientboundLoginDisconnectPacket::new).addPacket(ClientboundHelloPacket.class, ClientboundHelloPacket::new).addPacket(ClientboundGameProfilePacket.class, ClientboundGameProfilePacket::new).addPacket(ClientboundLoginCompressionPacket.class, ClientboundLoginCompressionPacket::new).addPacket(ClientboundCustomQueryPacket.class, ClientboundCustomQueryPacket::new)).addFlow(PacketFlow.SERVERBOUND, (new ConnectionProtocol.PacketSet()).addPacket(ServerboundHelloPacket.class, ServerboundHelloPacket::new).addPacket(ServerboundKeyPacket.class, ServerboundKeyPacket::new).addPacket(ServerboundCustomQueryPacket.class, ServerboundCustomQueryPacket::new))); + + private static final ConnectionProtocol[] LOOKUP = new ConnectionProtocol[4]; + private static final Map>, ConnectionProtocol> PROTOCOL_BY_PACKET = Maps.newHashMap(); +@@ -115,7 +121,7 @@ public enum ConnectionProtocol { + private final List>> idToConstructor; + + private PacketSet() { +- this.classToId = (Object2IntMap) Util.make((Object) (new Object2IntOpenHashMap()), (object2intopenhashmap) -> { ++ this.classToId = (Object2IntMap) Util.make(new Object2IntOpenHashMap(), (object2intopenhashmap) -> { // Paper - fix decompile error + object2intopenhashmap.defaultReturnValue(-1); + }); + this.idToConstructor = Lists.newArrayList(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 21f461ce884bc547dbe81c5430be530423c1605c..37a51dee4cd37844e80fdd5c9853947201151dfc 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1673,9 +1673,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoopmap(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error + }, this).thenCompose((immutablelist) -> { +- return ServerResources.loadResources(immutablelist, this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, this.getFunctionCompilationLevel(), this.executor, this); ++ return ServerResources.loadResources(immutablelist, this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, this.getFunctionCompilationLevel(), this.executor, this); // Paper - decompile error + }).thenAcceptAsync((datapackresources) -> { + this.resources.close(); + this.resources = datapackresources; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 37e938b258ee4eb5f5bab56145e83b640d80bcc7..23506a8903ce64fbfe849bb94e589bdbb6e61a74 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1913,7 +1913,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + // CraftBukkit - decompile error +- return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(it.unimi.dsi.fastutil.objects.Object2IntMap.Entry::getIntValue).reversed()).limit(5L).map((it_unimi_dsi_fastutil_objects_object2intmap_entry) -> { ++ return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Object2IntMap.Entry::getIntValue).reversed()).limit(5L).map((it_unimi_dsi_fastutil_objects_object2intmap_entry) -> { // Paper - decompile fix + return it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey() + ":" + it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue(); + }).collect(Collectors.joining(",")); + } catch (Exception exception) { +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index ad8a9d14e17b5e40b2cc3a83154931734d6c73d7..cc4190b3a8904d1eaae0f542a3b3090583f5ff82 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -179,9 +179,9 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + public void tryScheduleUpdate() { + if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { +- this.taskMailbox.tell((Object) (() -> { +- this.b(); +- this.g.set(false); ++ this.taskMailbox.tell((() -> { // Paper - decompile error ++ this.runUpdate(); ++ this.scheduled.set(false); + })); + } + +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index 90df41ec5081fe4ef2bc507e7289b18e0fea6e53..0c118d482e304c567fe7fe778c6ff386f960bdde 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -23,7 +23,7 @@ public final class Ticket implements Comparable> { + } else { + int j = Integer.compare(System.identityHashCode(this.type), System.identityHashCode(ticket.type)); + +- return j != 0 ? j : this.type.getComparator().compare(this.key, ticket.key); ++ return j != 0 ? j : this.type.getComparator().compare(this.key, (T)ticket.key); // Paper - decompile fix + } + } + +diff --git a/src/main/java/net/minecraft/stats/ServerStatsCounter.java b/src/main/java/net/minecraft/stats/ServerStatsCounter.java +index d624c390f8550c6aa44ca5920d127c901903ce27..7d435998680a363ad06c2e08139010c2573f7fb3 100644 +--- a/src/main/java/net/minecraft/stats/ServerStatsCounter.java ++++ b/src/main/java/net/minecraft/stats/ServerStatsCounter.java +@@ -203,7 +203,7 @@ public class ServerStatsCounter extends StatsCounter { + ObjectIterator objectiterator = this.stats.object2IntEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.objects.Object2IntMap.Entry> it_unimi_dsi_fastutil_objects_object2intmap_entry = (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry) objectiterator.next(); ++ Object2IntMap.Entry> it_unimi_dsi_fastutil_objects_object2intmap_entry = (Object2IntMap.Entry) objectiterator.next(); // Paper - decompile fix + Stat statistic = (Stat) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey(); + + ((JsonObject) map.computeIfAbsent(statistic.getType(), (statisticwrapper) -> { +diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +index 74eb1ed6b3fafeaca7e65a88a982d759d6836853..66ad412e4368a8615cc66a97ac442c572813a3dd 100644 +--- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java ++++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +@@ -18,11 +18,18 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + + public CrudeIncrementalIntIdentityHashBiMap(int size) { + size = (int) ((float) size / 0.8F); +- this.keys = (Object[]) (new Object[size]); ++ this.keys = (K[]) (new Object[size]); // Paper - decompile fix + this.values = new int[size]; +- this.byId = (Object[]) (new Object[size]); ++ this.byId = (K[]) (new Object[size]); // Paper - decompile fix + } + ++ // Paper start - decompile fix ++ @Override ++ public int a(K k) { ++ return getId(k); ++ } ++ // Paper end ++ + public int getId(@Nullable K entry) { + return this.getValue(this.indexOf(entry, this.hash(entry))); + } +@@ -56,9 +63,9 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + K[] ak = this.keys; + int[] aint = this.values; + +- this.keys = (Object[]) (new Object[newSize]); ++ this.keys = (K[]) (new Object[newSize]); // Paper - decompile fix + this.values = new int[newSize]; +- this.byId = (Object[]) (new Object[newSize]); ++ this.byId = (K[]) (new Object[newSize]); // Paper - decompile fix + this.nextId = 0; + this.size = 0; + +diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java +index be40fc388c501c311d661927025f1c572f3b3493..93813a508be1e1e600a8211f9822f2087328de70 100644 +--- a/src/main/java/net/minecraft/util/SortedArraySet.java ++++ b/src/main/java/net/minecraft/util/SortedArraySet.java +@@ -23,11 +23,11 @@ public class SortedArraySet extends AbstractSet { + } + + public static > SortedArraySet create(int initialCapacity) { +- return new SortedArraySet<>(initialCapacity, Comparator.naturalOrder()); ++ return new SortedArraySet<>(initialCapacity, (Comparator)Comparator.naturalOrder()); // Paper - decompile fix + } + + private static T[] castRawArray(Object[] array) { +- return (Object[]) array; ++ return (T[])array; // Paper - decompile fix + } + + private int findIndex(T object) { +@@ -101,7 +101,7 @@ public class SortedArraySet extends AbstractSet { + } + + public boolean remove(Object object) { +- int i = this.findIndex(object); ++ int i = this.findIndex((T)object); // Paper - decompile fix + + if (i >= 0) { + this.removeInternal(i); +@@ -116,7 +116,7 @@ public class SortedArraySet extends AbstractSet { + } + + public boolean contains(Object object) { +- int i = this.findIndex(object); ++ int i = this.findIndex((T)object); // Paper - decompile fix + + return i >= 0; + } +@@ -135,7 +135,7 @@ public class SortedArraySet extends AbstractSet { + + public U[] toArray(U[] au) { + if (au.length < this.size) { +- return (Object[]) Arrays.copyOf(this.contents, this.size, au.getClass()); ++ return (U[])Arrays.copyOf(this.contents, this.size, au.getClass()); // Paper - decompile fix + } else { + System.arraycopy(this.contents, 0, au, 0, this.size); + if (au.length > this.size) { +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index d9375f83de6d069f603f8a48cc5b02194e940052..03831adce7905916423d8c3834c42c90f3a1ca8f 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -55,7 +55,7 @@ public abstract class BlockableEventLoop implements Processo + return this.submitAsync(task); + } else { + task.run(); +- return CompletableFuture.completedFuture((Object) null); ++ return CompletableFuture.completedFuture(null); // Paper - decompile fix + } + } + +@@ -90,14 +90,14 @@ public abstract class BlockableEventLoop implements Processo + } + + protected boolean pollTask() { +- R r0 = (Runnable) this.pendingRunnables.peek(); ++ R r0 = this.pendingRunnables.peek(); // Paper - decompile fix + + if (r0 == null) { + return false; + } else if (this.blockingCount == 0 && !this.shouldRun(r0)) { + return false; + } else { +- this.doRunTask((Runnable) this.pendingRunnables.remove()); ++ this.doRunTask(this.pendingRunnables.remove()); // Paper - decompile fix + return true; + } + } +diff --git a/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java b/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java +index cc77767947e458e7205e616dce3bea8da09ca0cf..c763aa0c0cf49dd844af94a820103258b49021ae 100644 +--- a/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java ++++ b/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java +@@ -100,7 +100,7 @@ public class ProcessorMailbox implements ProcessorHandle, AutoCloseable, R + + public void run() { + try { +- this.pollUntil((i) -> { ++ this.pollUntil((int i) -> { // Paper - decompile fix + return i == 0; + }); + } finally { +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index 8f686f8608771d0a444dfd51dd4eabc90c6b2262..33a8604fa6c6431ccc5f61e484c163e09f1625a0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -191,9 +191,9 @@ public class PoiManager extends SectionStorage { + } + + private static boolean mayHavePoi(LevelChunkSection chunksection) { +- Set set = PoiType.ALL_STATES; ++ Set set = PoiType.ALL_STATES; // Paper - decompile error + +- set.getClass(); ++ //set.getClass(); // Paper - decompile error + return chunksection.maybeHas(set::contains); + } + +@@ -211,7 +211,7 @@ public class PoiManager extends SectionStorage { + SectionPos.aroundChunk(new ChunkPos(pos), Math.floorDiv(radius, 16)).map((sectionposition) -> { + return Pair.of(sectionposition, this.getOrLoad(sectionposition.asLong())); + }).filter((pair) -> { +- return !(Boolean) ((Optional) pair.getSecond()).map(PoiSection::a).orElse(false); ++ return !(Boolean) (pair.getSecond()).map(PoiSection::isValid).orElse(false); // Paper - decompile fix + }).map((pair) -> { + return ((SectionPos) pair.getFirst()).chunk(); + }).filter((chunkcoordintpair) -> { +@@ -257,13 +257,13 @@ public class PoiManager extends SectionStorage { + + public static enum Occupancy { + +- HAS_SPACE(PoiRecord::d), IS_OCCUPIED(PoiRecord::e), ANY((villageplacerecord) -> { ++ HAS_SPACE(PoiRecord::hasSpace), IS_OCCUPIED(PoiRecord::isOccupied), ANY((villageplacerecord) -> { + return true; + }); + + private final Predicate test; + +- private Occupancy(Predicate predicate) { ++ private Occupancy(Predicate predicate) { // Paper - decompile fix + this.test = predicate; + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +index 97317517a729877e307407ca9fab5fa58657fe38..a41f61daf6cbbb13d0b86cdbad8a4cae00368653 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +@@ -65,7 +65,7 @@ public class Vindicator extends AbstractIllager { + this.goalSelector.addGoal(2, new AbstractIllager.RaiderOpenDoorGoal(this)); + this.goalSelector.addGoal(3, new Raider.HoldGroundAttackGoal(this, 10.0F)); + this.goalSelector.addGoal(4, new Vindicator.VindicatorMeleeAttackGoal(this)); +- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).canUse()); ++ this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers(new Class[0])); // Paper - decompile fix + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); +diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +index ad4e181b24829980dc12f46807ec1c5226bd8e0c..fd1b84baae5f333c58dbbdcbfaa9198328f0961d 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java ++++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -47,12 +47,12 @@ import net.minecraft.world.level.saveddata.maps.MapItemSavedData; + + public class VillagerTrades { + +- public static final Map> TRADES = (Map) Util.make((Object) Maps.newHashMap(), (hashmap) -> { ++ public static final Map> TRADES = Util.make(Maps.newHashMap(), (hashmap) -> { // Paper - decompile fix + hashmap.put(VillagerProfession.FARMER, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.WHEAT, 20, 16, 2), new VillagerTrades.EmeraldForItems(Items.POTATO, 26, 16, 2), new VillagerTrades.EmeraldForItems(Items.CARROT, 22, 16, 2), new VillagerTrades.EmeraldForItems(Items.BEETROOT, 15, 16, 2), new VillagerTrades.ItemsForEmeralds(Items.BREAD, 1, 6, 16, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Blocks.PUMPKIN, 6, 12, 10), new VillagerTrades.ItemsForEmeralds(Items.PUMPKIN_PIE, 1, 4, 5), new VillagerTrades.ItemsForEmeralds(Items.APPLE, 1, 4, 16, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.COOKIE, 3, 18, 10), new VillagerTrades.EmeraldForItems(Blocks.MELON, 4, 12, 20)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Blocks.CAKE, 1, 1, 12, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.NIGHT_VISION, 100, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.JUMP, 160, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.WEAKNESS, 140, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.BLINDNESS, 120, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.POISON, 280, 15), new VillagerTrades.SuspisciousStewForEmerald(MobEffects.SATURATION, 7, 15)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.GOLDEN_CARROT, 3, 3, 30), new VillagerTrades.ItemsForEmeralds(Items.GLISTERING_MELON_SLICE, 4, 3, 30)}))); +- hashmap.put(VillagerProfession.FISHERMAN, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.STRING, 20, 16, 2), new VillagerTrades.EmeraldForItems(Items.COAL, 10, 16, 2), new VillagerTrades.ItemsAndEmeraldsToItems(Items.COD, 6, Items.COOKED_COD, 6, 16, 1), new VillagerTrades.ItemsForEmeralds(Items.COD_BUCKET, 3, 1, 16, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.COD, 15, 16, 10), new VillagerTrades.ItemsAndEmeraldsToItems(Items.SALMON, 6, Items.COOKED_SALMON, 6, 16, 5), new VillagerTrades.ItemsForEmeralds(Items.rn, 2, 1, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.SALMON, 13, 16, 20), new VillagerTrades.EnchantedItemForEmeralds(Items.FISHING_ROD, 3, 3, 10, 0.2F)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.TROPICAL_FISH, 6, 12, 30)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.PUFFERFISH, 4, 12, 30), new VillagerTrades.EmeraldsForVillagerTypeItem(1, 12, 30, ImmutableMap.builder().put(VillagerType.PLAINS, Items.OAK_BOAT).put(VillagerType.TAIGA, Items.SPRUCE_BOAT).put(VillagerType.SNOW, Items.SPRUCE_BOAT).put(VillagerType.DESERT, Items.JUNGLE_BOAT).put(VillagerType.JUNGLE, Items.JUNGLE_BOAT).put(VillagerType.SAVANNA, Items.ACACIA_BOAT).put(VillagerType.SWAMP, Items.DARK_OAK_BOAT).build())}))); ++ hashmap.put(VillagerProfession.FISHERMAN, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.STRING, 20, 16, 2), new VillagerTrades.EmeraldForItems(Items.COAL, 10, 16, 2), new VillagerTrades.ItemsAndEmeraldsToItems(Items.COD, 6, Items.COOKED_COD, 6, 16, 1), new VillagerTrades.ItemsForEmeralds(Items.COD_BUCKET, 3, 1, 16, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.COD, 15, 16, 10), new VillagerTrades.ItemsAndEmeraldsToItems(Items.SALMON, 6, Items.COOKED_SALMON, 6, 16, 5), new VillagerTrades.ItemsForEmeralds(Items.rn, 2, 1, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.SALMON, 13, 16, 20), new VillagerTrades.EnchantedItemForEmeralds(Items.FISHING_ROD, 3, 3, 10, 0.2F)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.TROPICAL_FISH, 6, 12, 30)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.PUFFERFISH, 4, 12, 30), new VillagerTrades.EmeraldsForVillagerTypeItem(1, 12, 30, ImmutableMap.builder().put(VillagerType.PLAINS, Items.OAK_BOAT).put(VillagerType.TAIGA, Items.SPRUCE_BOAT).put(VillagerType.SNOW, Items.SPRUCE_BOAT).put(VillagerType.DESERT, Items.JUNGLE_BOAT).put(VillagerType.JUNGLE, Items.JUNGLE_BOAT).put(VillagerType.SAVANNA, Items.ACACIA_BOAT).put(VillagerType.SWAMP, Items.DARK_OAK_BOAT).build())}))); // Paper - add to ImmutableMap..builder() + hashmap.put(VillagerProfession.SHEPHERD, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Blocks.WHITE_WOOL, 18, 16, 2), new VillagerTrades.EmeraldForItems(Blocks.BROWN_WOOL, 18, 16, 2), new VillagerTrades.EmeraldForItems(Blocks.BLACK_WOOL, 18, 16, 2), new VillagerTrades.EmeraldForItems(Blocks.GRAY_WOOL, 18, 16, 2), new VillagerTrades.ItemsForEmeralds(Items.SHEARS, 2, 1, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.WHITE_DYE, 12, 16, 10), new VillagerTrades.EmeraldForItems(Items.GRAY_DYE, 12, 16, 10), new VillagerTrades.EmeraldForItems(Items.BLACK_DYE, 12, 16, 10), new VillagerTrades.EmeraldForItems(Items.LIGHT_BLUE_DYE, 12, 16, 10), new VillagerTrades.EmeraldForItems(Items.LIME_DYE, 12, 16, 10), new VillagerTrades.ItemsForEmeralds(Blocks.WHITE_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.ORANGE_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.MAGENTA_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.YELLOW_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIME_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.PINK_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.CYAN_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.PURPLE_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BROWN_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.GREEN_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.RED_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BLACK_WOOL, 1, 1, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.WHITE_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.ORANGE_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.MAGENTA_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.YELLOW_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIME_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.PINK_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.CYAN_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.PURPLE_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BROWN_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.GREEN_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.RED_CARPET, 1, 4, 16, 5), new VillagerTrades.ItemsForEmeralds(Blocks.BLACK_CARPET, 1, 4, 16, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.YELLOW_DYE, 12, 16, 20), new VillagerTrades.EmeraldForItems(Items.LIGHT_GRAY_DYE, 12, 16, 20), new VillagerTrades.EmeraldForItems(Items.ORANGE_DYE, 12, 16, 20), new VillagerTrades.EmeraldForItems(Items.RED_DYE, 12, 16, 20), new VillagerTrades.EmeraldForItems(Items.PINK_DYE, 12, 16, 20), new VillagerTrades.ItemsForEmeralds(Blocks.WHITE_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.YELLOW_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.RED_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.BLACK_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.BLUE_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.BROWN_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.CYAN_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.GRAY_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.GREEN_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_BLUE_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.LIGHT_GRAY_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.LIME_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.MAGENTA_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.ORANGE_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.PINK_BED, 3, 1, 12, 10), new VillagerTrades.ItemsForEmeralds(Blocks.PURPLE_BED, 3, 1, 12, 10)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.BROWN_DYE, 12, 16, 30), new VillagerTrades.EmeraldForItems(Items.PURPLE_DYE, 12, 16, 30), new VillagerTrades.EmeraldForItems(Items.BLUE_DYE, 12, 16, 30), new VillagerTrades.EmeraldForItems(Items.GREEN_DYE, 12, 16, 30), new VillagerTrades.EmeraldForItems(Items.MAGENTA_DYE, 12, 16, 30), new VillagerTrades.EmeraldForItems(Items.CYAN_DYE, 12, 16, 30), new VillagerTrades.ItemsForEmeralds(Items.WHITE_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.LIGHT_BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.RED_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.PINK_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.GREEN_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.LIME_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.GRAY_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.BLACK_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.PURPLE_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.MAGENTA_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.CYAN_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.BROWN_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.YELLOW_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.ORANGE_BANNER, 3, 1, 12, 15), new VillagerTrades.ItemsForEmeralds(Items.LIGHT_GRAY_BANNER, 3, 1, 12, 15)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.PAINTING, 2, 3, 30)}))); + hashmap.put(VillagerProfession.FLETCHER, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.STICK, 32, 16, 2), new VillagerTrades.ItemsForEmeralds(Items.ARROW, 1, 16, 1), new VillagerTrades.ItemsAndEmeraldsToItems(Blocks.GRAVEL, 10, Items.FLINT, 10, 12, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.FLINT, 26, 12, 10), new VillagerTrades.ItemsForEmeralds(Items.BOW, 2, 1, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.STRING, 14, 16, 20), new VillagerTrades.ItemsForEmeralds(Items.CROSSBOW, 3, 1, 10)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.FEATHER, 24, 16, 30), new VillagerTrades.EnchantedItemForEmeralds(Items.BOW, 2, 3, 15)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.es, 8, 12, 30), new VillagerTrades.EnchantedItemForEmeralds(Items.CROSSBOW, 3, 3, 15), new VillagerTrades.TippedArrowForItemsAndEmeralds(Items.ARROW, 5, Items.TIPPED_ARROW, 5, 2, 12, 30)}))); +- hashmap.put(VillagerProfession.LIBRARIAN, toIntMap(ImmutableMap.builder().put(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.PAPER, 24, 16, 2), new VillagerTrades.EnchantBookForEmeralds(1), new VillagerTrades.ItemsForEmeralds(Blocks.BOOKSHELF, 9, 1, 12, 1)}).put(2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.BOOK, 4, 12, 10), new VillagerTrades.EnchantBookForEmeralds(5), new VillagerTrades.ItemsForEmeralds(Items.rk, 1, 1, 5)}).put(3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.INK_SAC, 5, 12, 20), new VillagerTrades.EnchantBookForEmeralds(10), new VillagerTrades.ItemsForEmeralds(Items.az, 1, 4, 10)}).put(4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.WRITABLE_BOOK, 2, 12, 30), new VillagerTrades.EnchantBookForEmeralds(15), new VillagerTrades.ItemsForEmeralds(Items.CLOCK, 5, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.COMPASS, 4, 1, 15)}).put(5, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.NAME_TAG, 20, 1, 30)}).build())); ++ hashmap.put(VillagerProfession.LIBRARIAN, toIntMap(ImmutableMap.builder().put(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.PAPER, 24, 16, 2), new VillagerTrades.EnchantBookForEmeralds(1), new VillagerTrades.ItemsForEmeralds(Blocks.BOOKSHELF, 9, 1, 12, 1)}).put(2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.BOOK, 4, 12, 10), new VillagerTrades.EnchantBookForEmeralds(5), new VillagerTrades.ItemsForEmeralds(Items.rk, 1, 1, 5)}).put(3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.INK_SAC, 5, 12, 20), new VillagerTrades.EnchantBookForEmeralds(10), new VillagerTrades.ItemsForEmeralds(Items.az, 1, 4, 10)}).put(4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.WRITABLE_BOOK, 2, 12, 30), new VillagerTrades.EnchantBookForEmeralds(15), new VillagerTrades.ItemsForEmeralds(Items.CLOCK, 5, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.COMPASS, 4, 1, 15)}).put(5, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.NAME_TAG, 20, 1, 30)}).build())); // Paper - add to ImmutableMap..builder() + hashmap.put(VillagerProfession.CARTOGRAPHER, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.PAPER, 24, 16, 2), new VillagerTrades.ItemsForEmeralds(Items.MAP, 7, 1, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.dP, 11, 16, 10), new VillagerTrades.TreasureMapForEmeralds(13, StructureFeature.OCEAN_MONUMENT, MapDecoration.Type.MONUMENT, 12, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.COMPASS, 1, 12, 20), new VillagerTrades.TreasureMapForEmeralds(14, StructureFeature.WOODLAND_MANSION, MapDecoration.Type.MANSION, 12, 10)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.ITEM_FRAME, 7, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.WHITE_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.BLUE_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.LIGHT_BLUE_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.RED_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.PINK_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.GREEN_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.LIME_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.GRAY_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.BLACK_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.PURPLE_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.MAGENTA_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.CYAN_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.BROWN_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.YELLOW_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.ORANGE_BANNER, 3, 1, 15), new VillagerTrades.ItemsForEmeralds(Items.LIGHT_GRAY_BANNER, 3, 1, 15)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.ItemsForEmeralds(Items.GLOBE_BANNER_PATTERN, 8, 1, 30)}))); + hashmap.put(VillagerProfession.CLERIC, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.ROTTEN_FLESH, 32, 16, 2), new VillagerTrades.ItemsForEmeralds(Items.REDSTONE, 1, 2, 1)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.GOLD_INGOT, 3, 12, 10), new VillagerTrades.ItemsForEmeralds(Items.LAPIS_LAZULI, 1, 1, 5)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.RABBIT_FOOT, 2, 12, 20), new VillagerTrades.ItemsForEmeralds(Blocks.GLOWSTONE, 4, 1, 12, 10)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.SCUTE, 4, 12, 30), new VillagerTrades.EmeraldForItems(Items.GLASS_BOTTLE, 9, 12, 30), new VillagerTrades.ItemsForEmeralds(Items.ENDER_PEARL, 5, 1, 15)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.NETHER_WART, 22, 12, 30), new VillagerTrades.ItemsForEmeralds(Items.EXPERIENCE_BOTTLE, 3, 1, 30)}))); + hashmap.put(VillagerProfession.ARMORER, toIntMap(ImmutableMap.of(1, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.COAL, 15, 16, 2), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.IRON_LEGGINGS), 7, 1, 12, 1, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.IRON_BOOTS), 4, 1, 12, 1, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.IRON_HELMET), 5, 1, 12, 1, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.IRON_CHESTPLATE), 9, 1, 12, 1, 0.2F)}, 2, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.IRON_INGOT, 4, 12, 10), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.rj), 36, 1, 12, 5, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.CHAINMAIL_BOOTS), 1, 1, 12, 5, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.CHAINMAIL_LEGGINGS), 3, 1, 12, 5, 0.2F)}, 3, new VillagerTrades.ItemListing[]{new VillagerTrades.EmeraldForItems(Items.LAVA_BUCKET, 1, 12, 20), new VillagerTrades.EmeraldForItems(Items.DIAMOND, 1, 12, 20), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.CHAINMAIL_HELMET), 1, 1, 12, 10, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.CHAINMAIL_CHESTPLATE), 4, 1, 12, 10, 0.2F), new VillagerTrades.ItemsForEmeralds(new ItemStack(Items.SHIELD), 5, 1, 12, 10, 0.2F)}, 4, new VillagerTrades.ItemListing[]{new VillagerTrades.EnchantedItemForEmeralds(Items.DIAMOND_LEGGINGS, 14, 3, 15, 0.2F), new VillagerTrades.EnchantedItemForEmeralds(Items.DIAMOND_BOOTS, 8, 3, 15, 0.2F)}, 5, new VillagerTrades.ItemListing[]{new VillagerTrades.EnchantedItemForEmeralds(Items.DIAMOND_HELMET, 8, 3, 30, 0.2F), new VillagerTrades.EnchantedItemForEmeralds(Items.DIAMOND_CHESTPLATE, 16, 3, 30, 0.2F)}))); +diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +index 86d7af20fad04405f95c71e078d41070abdc43ad..c4777997cfff364818fbaee70afd7c79099213fb 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +@@ -75,7 +75,7 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { + } + + this.recipes = (Map) map1.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry1) -> { +- return (entry1.getValue()); // CraftBukkit ++ return entry1.getValue(); // CraftBukkit // Paper - decompile fix - *shrugs internally* + })); + RecipeManager.LOGGER.info("Loaded {} recipes", map1.size()); + } +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 1276769b22ae21f0ff4271ecc72d4aa39ddff23f..7e7a58b9a9ececdcc37fc33b33703428eb1d5faf 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -167,22 +167,22 @@ public interface EntityGetter { + + @Nullable + default T getNearestEntity(Class entityClass, TargetingConditions targetPredicate, @Nullable LivingEntity entity, double x, double y, double z, AABB box) { +- return this.getNearestEntity(this.getEntitiesOfClass(entityClass, box, (Predicate) null), targetPredicate, entity, x, y, z); ++ return this.getNearestEntity(this.getEntitiesOfClass(entityClass, box, null), targetPredicate, entity, x, y, z); // Paper - decompile fix + } + + @Nullable + default T getNearestLoadedEntity(Class entityClass, TargetingConditions targetPredicate, @Nullable LivingEntity entity, double x, double y, double z, AABB box) { +- return this.getNearestEntity(this.getLoadedEntitiesOfClass(entityClass, box, (Predicate) null), targetPredicate, entity, x, y, z); ++ return this.getNearestEntity(this.getLoadedEntitiesOfClass(entityClass, box, null), targetPredicate, entity, x, y, z); // Paper - decompile fix + } + + @Nullable + default T getNearestEntity(List entityList, TargetingConditions targetPredicate, @Nullable LivingEntity entity, double x, double y, double z) { + double d3 = -1.0D; + T t0 = null; +- Iterator iterator = entityList.iterator(); ++ Iterator iterator = entityList.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t1 = (LivingEntity) iterator.next(); ++ T t1 = iterator.next(); // Paper - decompile fix + + if (targetPredicate.test(entity, t1)) { + double d4 = t1.distanceToSqr(x, y, z); +@@ -215,10 +215,10 @@ public interface EntityGetter { + default List getNearbyEntities(Class entityClass, TargetingConditions targetPredicate, LivingEntity targetingEntity, AABB box) { + List list = this.getEntitiesOfClass(entityClass, box, (Predicate) null); + List list1 = Lists.newArrayList(); +- Iterator iterator = list.iterator(); ++ Iterator iterator = list.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t0 = (LivingEntity) iterator.next(); ++ T t0 = iterator.next(); // Paper - decompile fix + + if (targetPredicate.test(targetingEntity, t0)) { + list1.add(t0); +diff --git a/src/main/java/net/minecraft/world/level/TickNextTickData.java b/src/main/java/net/minecraft/world/level/TickNextTickData.java +index c6e64c5182d564664464e26df27e6b5f7da418e6..d97e266b83bb331fcd4031046a5843d29ce53164 100644 +--- a/src/main/java/net/minecraft/world/level/TickNextTickData.java ++++ b/src/main/java/net/minecraft/world/level/TickNextTickData.java +@@ -38,13 +38,13 @@ public class TickNextTickData { + return this.pos.hashCode(); + } + +- public static Comparator> createTimeComparator() { ++ public static Comparator createTimeComparator() { // Paper - decompile fix + return Comparator.comparingLong((nextticklistentry) -> { +- return nextticklistentry.b; ++ return ((TickNextTickData) nextticklistentry).triggerTick; // Paper - decompile fix + }).thenComparing((nextticklistentry) -> { +- return nextticklistentry.c; ++ return ((TickNextTickData) nextticklistentry).priority; // Paper - decompile fix + }).thenComparingLong((nextticklistentry) -> { +- return nextticklistentry.f; ++ return ((TickNextTickData) nextticklistentry).c; // Paper - decompile fix + }); + } + +diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java +index 79be56c26b66351bcfcd96c9967f0ce91e40d5ce..ed83335175bb882741dfaef251ab30ce1590f74c 100644 +--- a/src/main/java/net/minecraft/world/level/biome/Biome.java ++++ b/src/main/java/net/minecraft/world/level/biome/Biome.java +@@ -49,36 +49,43 @@ import org.apache.logging.log4j.Logger; + public final class Biome { + + public static final Logger LOGGER = LogManager.getLogger(); ++ // Paper start ++ private static class dProxy extends Biome.ClimateSettings { ++ private dProxy(Precipitation precipitation, float temperature, TemperatureModifier temperatureModifier, float downfall) { ++ super(precipitation, temperature, temperatureModifier, downfall); ++ } ++ }; ++ // Paper end + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create((instance) -> { +- return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { +- return biomebase.j; ++ return instance.group(dProxy.CODEC.forGetter((biomebase) -> { // Paper ++ return biomebase.climateSettings; + }), Biome.BiomeCategory.CODEC.fieldOf("category").forGetter((biomebase) -> { +- return biomebase.o; ++ return biomebase.biomeCategory; + }), Codec.FLOAT.fieldOf("depth").forGetter((biomebase) -> { +- return biomebase.m; ++ return biomebase.depth; + }), Codec.FLOAT.fieldOf("scale").forGetter((biomebase) -> { +- return biomebase.n; ++ return biomebase.scale; + }), BiomeSpecialEffects.CODEC.fieldOf("effects").forGetter((biomebase) -> { +- return biomebase.p; ++ return biomebase.specialEffects; + }), BiomeGenerationSettings.CODEC.forGetter((biomebase) -> { +- return biomebase.k; ++ return biomebase.generationSettings; + }), MobSpawnSettings.CODEC.forGetter((biomebase) -> { +- return biomebase.l; ++ return biomebase.mobSettings; + })).apply(instance, Biome::new); + }); + public static final Codec NETWORK_CODEC = RecordCodecBuilder.create((instance) -> { +- return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { +- return biomebase.j; ++ return instance.group(dProxy.CODEC.forGetter((biomebase) -> { // Paper ++ return biomebase.climateSettings; + }), Biome.BiomeCategory.CODEC.fieldOf("category").forGetter((biomebase) -> { +- return biomebase.o; ++ return biomebase.biomeCategory; + }), Codec.FLOAT.fieldOf("depth").forGetter((biomebase) -> { +- return biomebase.m; ++ return biomebase.depth; + }), Codec.FLOAT.fieldOf("scale").forGetter((biomebase) -> { +- return biomebase.n; ++ return biomebase.scale; + }), BiomeSpecialEffects.CODEC.fieldOf("effects").forGetter((biomebase) -> { +- return biomebase.p; ++ return biomebase.specialEffects; + })).apply(instance, (biomebase_d, biomebase_geography, ofloat, ofloat1, biomefog) -> { +- return new BiomeBase(biomebase_d, biomebase_geography, ofloat, ofloat1, biomefog, BiomeSettingsGeneration.b, BiomeSettingsMobs.b); ++ return new Biome(biomebase_d, biomebase_geography, ofloat, ofloat1, biomefog, BiomeGenerationSettings.EMPTY, MobSpawnSettings.EMPTY); + }); + }); + public static final Codec> CODEC = RegistryFileCodec.a(Registry.BIOME_REGISTRY, Biome.DIRECT_CODEC); +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 94a02cb23a210ee0cc789db15853b6672ec673f4..73888713746e7ddd72ba9ac9d33d8e616eb3bd25 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -160,7 +160,7 @@ public class PistonMovingBlockEntity extends BlockEntity implements TickableBloc + private static void moveEntityByPiston(Direction enumdirection, Entity entity, double d0, Direction enumdirection1) { + PistonMovingBlockEntity.NOCLIP.set(enumdirection); + entity.move(MoverType.PISTON, new Vec3(d0 * (double) enumdirection1.getStepX(), d0 * (double) enumdirection1.getStepY(), d0 * (double) enumdirection1.getStepZ())); +- PistonMovingBlockEntity.NOCLIP.set((Object) null); ++ PistonMovingBlockEntity.NOCLIP.set(null); // Paper - decompile fix + } + + private void moveStuckEntities(float f) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +index 95ad396fc46a587d08b87943aa05dd72d35efd3a..60ce75c7f94c995d3753c40bc8d1ec09b4d37b1a 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +@@ -28,12 +28,12 @@ public abstract class StateHolder { + } else { + Property iblockstate = (Property) entry.getKey(); + +- return iblockstate.getName() + "=" + this.getName(iblockstate, (Comparable) entry.getValue()); ++ return iblockstate.getName() + "=" + this.getName((Property) iblockstate, (Comparable) entry.getValue()); // Paper - decompile fix + } + } + +- private > String getName(Property property, Comparable value) { +- return property.value(value); ++ private > String getName(Property property, T value) { // Paper - decompile error ++ return property.getName(value); + } + }; + protected final O owner; +@@ -48,11 +48,11 @@ public abstract class StateHolder { + } + + public > S cycle(Property property) { +- return this.setValue(property, (Comparable) findNextInCollection(property.getPossibleValues(), (Object) this.getValue(property))); ++ return this.setValue(property, findNextInCollection(property.getPossibleValues(), this.getValue(property))); // Paper - decompile error + } + + protected static T findNextInCollection(Collection values, T value) { +- Iterator iterator = values.iterator(); ++ Iterator iterator = values.iterator(); // Paper + + do { + if (!iterator.hasNext()) { +@@ -94,7 +94,7 @@ public abstract class StateHolder { + if (comparable == null) { + throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); + } else { +- return (Comparable) property.getValueClass().cast(comparable); ++ return property.getValueClass().cast(comparable); // Paper - decompile error + } + } + +@@ -110,7 +110,7 @@ public abstract class StateHolder { + if (comparable == null) { + throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); + } else if (comparable == value) { +- return this; ++ return (S) this; // Paper - decompile error + } else { + S s0 = this.neighbours.get(property, value); + +@@ -162,7 +162,7 @@ public abstract class StateHolder { + return codec.dispatch("Name", (iblockdataholder) -> { + return iblockdataholder.owner; + }, (object) -> { +- S s0 = (StateHolder) ownerToStateFunction.apply(object); ++ S s0 = ownerToStateFunction.apply(object); // Paper - decompile error + + return s0.getValues().isEmpty() ? Codec.unit(s0) : s0.propertiesCodec.fieldOf("Properties").codec(); + }); +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +index 9e0724c4bf06d207898e477e35412c09f3aa0f74..b5817645727f2af2785e0987ba824f431d4e9e32 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +@@ -21,10 +21,10 @@ public class EnumProperty & StringRepresentable> extends Prope + protected EnumProperty(String name, Class type, Collection values) { + super(name, type); + this.values = ImmutableSet.copyOf(values); +- Iterator iterator = values.iterator(); ++ Iterator iterator = values.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t0 = (Enum) iterator.next(); ++ T t0 = iterator.next(); // Paper - Decompile fix + String s1 = ((StringRepresentable) t0).getSerializedName(); + + if (this.names.containsKey(s1)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +index 5a16a0079bc297fb4572d2e6e9b07a9d1a53c906..8cc07c70fde81e44679f3ea7d9a4c6b2447885d4 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +@@ -17,12 +17,10 @@ public abstract class Property> { + private final Codec> valueCodec; + + protected Property(String name, Class type) { +- this.codec = Codec.STRING.comapFlatMap((s1) -> { +- return (DataResult) this.getValue(s1).map(DataResult::success).orElseGet(() -> { +- return DataResult.error("Unable to read property: " + this + " with value: " + s1); +- }); +- }, this::getName); +- this.valueCodec = this.codec.xmap(this::b, Property.clazz::b); ++ this.codec = Codec.STRING.comapFlatMap((s1) -> this.getValue(s1).map(DataResult::success).orElseGet(() -> { // Paper - decompile error ++ return DataResult.error("Unable to read property: " + this + " with value: " + s1); ++ }), this::getName); ++ this.valueCodec = this.codec.xmap(this::value, (Property.Value param) -> param.value()); // Paper - decompile fix + this.clazz = type; + this.name = name; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +index 977ac6db5fbdd001c306ee6aa396bb395384dd8f..7de765786b3504dcffab98bb0d9dac64b30b3325 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -89,7 +89,7 @@ public class IOWorker implements AutoCloseable { + return this.submitTask(() -> { + try { + this.storage.flush(); +- return Either.left((Object) null); ++ return Either.left(null); // Paper - decompile error + } catch (Exception exception) { + IOWorker.LOGGER.warn("Failed to synchronized chunks", exception); + return Either.right(exception); +@@ -123,13 +123,13 @@ public class IOWorker implements AutoCloseable { + } + + private void tellStorePending() { +- this.mailbox.tell((Object) (new StrictQueue.IntRunnable(IOWorker.Priority.LOW.ordinal(), this::storePendingChunk))); ++ this.mailbox.tell((new StrictQueue.IntRunnable(IOWorker.Priority.LOW.ordinal(), this::storePendingChunk))); // Paper - decompile error + } + + private void runStore(ChunkPos pos, IOWorker.PendingStore ioworker_a) { + try { + this.storage.write(pos, ioworker_a.data); +- ioworker_a.result.complete((Object) null); ++ ioworker_a.result.complete(null); // Paper - decompile fix + } catch (Exception exception) { + IOWorker.LOGGER.error("Failed to store chunk {}", pos, exception); + ioworker_a.result.completeExceptionally(exception); +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 217065b88178342159154490ffabe0fe7d32d7bf..2386ffeec60851ba192b89bc6fd7ffff9c56aff5 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -430,7 +430,7 @@ public class EndDragonFight { + } + } + +- worldgenendtrophy.configured((FeatureConfiguration) FeatureConfiguration.NONE).a(this.level, this.level.getChunkSource().getGenerator(), new Random(), this.portalLocation); ++ worldgenendtrophy.configured(FeatureConfiguration.NONE).place(this.level, this.level.getChunkSource().getGenerator(), new Random(), this.portalLocation); // Paper - decompile fix + } + + private EnderDragon createNewDragon() { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +index 418abfdc85de157d23807059670a2dfc964cbd5f..9f60abfe0a37e30c5528a1ca0546295b00598798 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +@@ -71,13 +71,13 @@ public abstract class StructureFeature { + public static final StructureFeature BASTION_REMNANT = register("Bastion_Remnant", new BastionFeature(JigsawConfiguration.CODEC), GenerationStep.Decoration.SURFACE_STRUCTURES); + public static final List> NOISE_AFFECTING_FEATURES = ImmutableList.of(StructureFeature.PILLAGER_OUTPOST, StructureFeature.VILLAGE, StructureFeature.NETHER_FOSSIL); + private static final ResourceLocation JIGSAW_RENAME = new ResourceLocation("jigsaw"); +- private static final Map RENAMES = ImmutableMap.builder().put(new ResourceLocation("nvi"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("pcp"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("bastionremnant"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("runtime"), StructureFeature.JIGSAW_RENAME).build(); ++ private static final Map RENAMES = ImmutableMap.builder().put(new ResourceLocation("nvi"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("pcp"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("bastionremnant"), StructureFeature.JIGSAW_RENAME).put(new ResourceLocation("runtime"), StructureFeature.JIGSAW_RENAME).build(); // Paper - decompile fix + private final Codec>> y; + + private static > F register(String name, F structureFeature, GenerationStep.Decoration step) { + StructureFeature.STRUCTURES_REGISTRY.put(name.toLowerCase(Locale.ROOT), structureFeature); + StructureFeature.STEP.put(structureFeature, step); +- return (StructureFeature) Registry.registerDefaulted(Registry.STRUCTURE_FEATURE, name.toLowerCase(Locale.ROOT), (Object) structureFeature); ++ return (F) Registry.>register(Registry.STRUCTURE_FEATURE, name.toLowerCase(Locale.ROOT), structureFeature); // Paper - decomp fix + } + + public StructureFeature(Codec codec) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +index cd2efdd5802605de5d3d636ce1b4a796e0c13310..c304637ae8f80c65b58e8ba8a27609b532bb1184 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +@@ -34,10 +34,10 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage= l) { +diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +index b106f2458361fc2a9168bbef8c9e5b35d8e359fb..60b7fdf9c092e8105d41f4af02a08651624f3eb9 100644 +--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -44,7 +44,7 @@ public class DimensionDataStorage { + if (t0 != null) { + return t0; + } else { +- T t1 = (SavedData) factory.get(); ++ T t1 = factory.get(); // Paper - decompile fix + + this.set(t1); + return t1; +@@ -53,7 +53,7 @@ public class DimensionDataStorage { + + @Nullable + public T get(Supplier factory, String id) { +- SavedData persistentbase = (SavedData) this.cache.get(id); ++ T persistentbase = (T) this.cache.get(id); // Paper - decompile fix + + if (persistentbase == null && !this.cache.containsKey(id)) { + persistentbase = this.readSavedData(factory, id); +@@ -69,7 +69,7 @@ public class DimensionDataStorage { + File file = this.getDataFile(id); + + if (file.exists()) { +- T t0 = (SavedData) factory.get(); ++ T t0 = factory.get(); // Paper - decompile fix + CompoundTag nbttagcompound = this.readTagFromDisk(id, SharedConstants.getCurrentVersion().getWorldVersion()); + + t0.load(nbttagcompound.getCompound("data")); +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +index 4093a4a1f924ac722d60599be9688a88d26a5c1a..802eb7fe690adae03c80db3fc0f72ea2788a3b2c 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +@@ -42,7 +42,7 @@ public abstract class LootPoolEntryContainer implements ComposableEntryContainer + + // CraftBukkit start + @Override +- public final void serialize(JsonObject json, T entry, JsonSerializationContext context) { ++ public void serialize(JsonObject json, T entry, JsonSerializationContext context) { // Paper - remove final + if (!org.apache.commons.lang3.ArrayUtils.isEmpty(entry.conditions)) { + json.add("conditions", context.serialize(entry.conditions)); + } +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java +index c942459e0a492dd9fab296ef60d272651d13f049..ceb5e5405ed20c8de954847bbb269109107a43fc 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java +@@ -132,7 +132,7 @@ public abstract class LootPoolSingletonContainer extends LootPoolEntryContainer + @Override + public T b(LootItemFunction.Builder lootitemfunction_a) { + this.functions.add(lootitemfunction_a.b()); +- return (LootPoolSingletonContainer.Builder) this.getThis(); ++ return this.getThis(); // Paper - decompile fix + } + + protected LootItemFunction[] getFunctions() { +@@ -141,12 +141,12 @@ public abstract class LootPoolSingletonContainer extends LootPoolEntryContainer + + public T setWeight(int weight) { + this.weight = weight; +- return (LootPoolSingletonContainer.Builder) this.getThis(); ++ return this.getThis(); // Paper - decompile fix + } + + public T setQuality(int quality) { + this.quality = quality; +- return (LootPoolSingletonContainer.Builder) this.getThis(); ++ return this.getThis(); // Paper - decompile fix + } + } + +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +index b77c921548ff55bab62bf37fa411ad1fd8d38f82..a3ce120b0da62f9be938c58c3414ce997f5d30ea 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +@@ -89,7 +89,7 @@ public class ExplorationMapFunction extends LootItemConditionalFunction { + public Serializer() {} + + public void serialize(JsonObject json, ExplorationMapFunction object, JsonSerializationContext context) { +- super.serialize(json, (LootItemConditionalFunction) object, context); ++ super.serialize(json, object, context); // Paper - decompile fix + if (!object.destination.equals(ExplorationMapFunction.DEFAULT_FEATURE)) { + json.add("destination", context.serialize(object.destination.getFeatureName())); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +index bc1798f130184d6b107d7a9ba972cab686534439..f0e74daa5bb9e88c028225e7c71deb04c481a7ac 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +@@ -38,7 +38,7 @@ public final class IndirectMerger implements IndexMerger { + double d1 = flag4 ? first.getDouble(i++) : second.getDouble(j++); + + if ((i != 0 && flag2 || flag4 || includeSecondOnly) && (j != 0 && flag3 || !flag4 || includeFirstOnly)) { +- if (d0 < d1 - 1.0E-7D) { ++ if (!(d0 >= d1 - 1.0E-7D)) { // Paper - decompile error - welcome to hell + this.firstIndices.add(i - 1); + this.secondIndices.add(j - 1); + this.result.add(d1); diff --git a/Remapped-Spigot-Server-Patches/0004-MC-Utils.patch b/Remapped-Spigot-Server-Patches/0004-MC-Utils.patch new file mode 100644 index 000000000..cccd5824c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0004-MC-Utils.patch @@ -0,0 +1,4859 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:55:47 -0400 +Subject: [PATCH] MC Utils + + +diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4029dc68cf35d63aa70c4a76c35bf65a7fc6358f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java +@@ -0,0 +1,68 @@ ++package com.destroystokyo.paper.util.concurrent; ++ ++import java.util.concurrent.atomic.AtomicLong; ++ ++/** ++ * copied from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/lock/WeakSeqLock.java ++ * @author Spottedleaf ++ */ ++public final class WeakSeqLock { ++ // TODO when the switch to J11 is made, nuke this class from orbit ++ ++ protected final AtomicLong lock = new AtomicLong(); ++ ++ public WeakSeqLock() { ++ //VarHandle.storeStoreFence(); // warn: usages must be checked to ensure this behaviour isn't needed ++ } ++ ++ public void acquireWrite() { ++ // must be release-type write ++ this.lock.lazySet(this.lock.get() + 1); ++ } ++ ++ public boolean canRead(final long read) { ++ return (read & 1) == 0; ++ } ++ ++ public boolean tryAcquireWrite() { ++ this.acquireWrite(); ++ return true; ++ } ++ ++ public void releaseWrite() { ++ // must be acquire-type write ++ final long lock = this.lock.get(); // volatile here acts as store-store ++ this.lock.lazySet(lock + 1); ++ } ++ ++ public void abortWrite() { ++ // must be acquire-type write ++ final long lock = this.lock.get(); // volatile here acts as store-store ++ this.lock.lazySet(lock ^ 1); ++ } ++ ++ public long acquireRead() { ++ int failures = 0; ++ long curr; ++ ++ for (curr = this.lock.get(); !this.canRead(curr); curr = this.lock.get()) { ++ // without j11, our only backoff is the yield() call... ++ ++ if (++failures > 5_000) { /* TODO determine a threshold */ ++ Thread.yield(); ++ } ++ /* Better waiting is beyond the scope of this lock; if it is needed the lock is being misused */ ++ } ++ ++ //VarHandle.loadLoadFence(); // volatile acts as the load-load barrier ++ return curr; ++ } ++ ++ public boolean tryReleaseRead(final long read) { ++ return this.lock.get() == read; // volatile acts as the load-load barrier ++ } ++ ++ public long getSequentialCounter() { ++ return this.lock.get(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java +new file mode 100644 +index 0000000000000000000000000000000000000000..59868f37d14bbc0ece0836095cdad148778995e6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java +@@ -0,0 +1,162 @@ ++package com.destroystokyo.paper.util.map; ++ ++import com.destroystokyo.paper.util.concurrent.WeakSeqLock; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++ ++/** ++ * @author Spottedleaf ++ */ ++public class QueuedChangesMapLong2Int { ++ ++ protected final Long2IntOpenHashMap updatingMap; ++ protected final Long2IntOpenHashMap visibleMap; ++ protected final Long2IntOpenHashMap queuedPuts; ++ protected final LongOpenHashSet queuedRemove; ++ ++ protected int queuedDefaultReturnValue; ++ ++ // we use a seqlock as writes are not common. ++ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); ++ ++ public QueuedChangesMapLong2Int() { ++ this(16, 0.75f); ++ } ++ ++ public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) { ++ this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor); ++ this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor); ++ this.queuedPuts = new Long2IntOpenHashMap(); ++ this.queuedRemove = new LongOpenHashSet(); ++ } ++ ++ public void queueDefaultReturnValue(final int dfl) { ++ this.queuedDefaultReturnValue = dfl; ++ this.updatingMap.defaultReturnValue(dfl); ++ } ++ ++ public int queueUpdate(final long k, final int v) { ++ this.queuedRemove.remove(k); ++ this.queuedPuts.put(k, v); ++ ++ return this.updatingMap.put(k, v); ++ } ++ ++ public int queueRemove(final long k) { ++ this.queuedPuts.remove(k); ++ this.queuedRemove.add(k); ++ ++ return this.updatingMap.remove(k); ++ } ++ ++ public int getUpdating(final long k) { ++ return this.updatingMap.get(k); ++ } ++ ++ public int getVisible(final long k) { ++ return this.visibleMap.get(k); ++ } ++ ++ public int getVisibleAsync(final long k) { ++ long readlock; ++ int ret = 0; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ try { ++ ret = this.visibleMap.get(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public boolean performUpdates() { ++ this.updatingMapSeqLock.acquireWrite(); ++ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); ++ this.updatingMapSeqLock.releaseWrite(); ++ ++ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { ++ return false; ++ } ++ ++ // update puts ++ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); ++ while (iterator0.hasNext()) { ++ final Long2IntMap.Entry entry = iterator0.next(); ++ final long key = entry.getLongKey(); ++ final int val = entry.getIntValue(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.put(key, val); ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedPuts.clear(); ++ ++ final LongIterator iterator1 = this.queuedRemove.iterator(); ++ while (iterator1.hasNext()) { ++ final long key = iterator1.nextLong(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.remove(key); ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedRemove.clear(); ++ ++ return true; ++ } ++ ++ public boolean performUpdatesLockMap() { ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); ++ ++ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { ++ return false; ++ } ++ ++ // update puts ++ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); ++ while (iterator0.hasNext()) { ++ final Long2IntMap.Entry entry = iterator0.next(); ++ final long key = entry.getLongKey(); ++ final int val = entry.getIntValue(); ++ ++ this.visibleMap.put(key, val); ++ } ++ ++ this.queuedPuts.clear(); ++ ++ final LongIterator iterator1 = this.queuedRemove.iterator(); ++ while (iterator1.hasNext()) { ++ final long key = iterator1.nextLong(); ++ ++ this.visibleMap.remove(key); ++ } ++ ++ this.queuedRemove.clear(); ++ ++ return true; ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7bab31a312463cc963d9621cdc543a281459bd32 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java +@@ -0,0 +1,202 @@ ++package com.destroystokyo.paper.util.map; ++ ++import com.destroystokyo.paper.util.concurrent.WeakSeqLock; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.List; ++ ++/** ++ * @author Spottedleaf ++ */ ++public class QueuedChangesMapLong2Object { ++ ++ protected static final Object REMOVED = new Object(); ++ ++ protected final Long2ObjectLinkedOpenHashMap updatingMap; ++ protected final Long2ObjectLinkedOpenHashMap visibleMap; ++ protected final Long2ObjectLinkedOpenHashMap queuedChanges; ++ ++ // we use a seqlock as writes are not common. ++ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); ++ ++ public QueuedChangesMapLong2Object() { ++ this(16, 0.75f); // dfl for fastutil ++ } ++ ++ public QueuedChangesMapLong2Object(final int capacity, final float loadFactor) { ++ this.updatingMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); ++ this.visibleMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); ++ this.queuedChanges = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ ++ public V queueUpdate(final long k, final V value) { ++ this.queuedChanges.put(k, value); ++ return this.updatingMap.put(k, value); ++ } ++ ++ public V queueRemove(final long k) { ++ this.queuedChanges.put(k, REMOVED); ++ return this.updatingMap.remove(k); ++ } ++ ++ public V getUpdating(final long k) { ++ return this.updatingMap.get(k); ++ } ++ ++ public boolean updatingContainsKey(final long k) { ++ return this.updatingMap.containsKey(k); ++ } ++ ++ public V getVisible(final long k) { ++ return this.visibleMap.get(k); ++ } ++ ++ public boolean visibleContainsKey(final long k) { ++ return this.visibleMap.containsKey(k); ++ } ++ ++ public V getVisibleAsync(final long k) { ++ long readlock; ++ V ret = null; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ++ try { ++ ret = this.visibleMap.get(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public boolean visibleContainsKeyAsync(final long k) { ++ long readlock; ++ boolean ret = false; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ++ try { ++ ret = this.visibleMap.containsKey(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public Long2ObjectLinkedOpenHashMap getVisibleMap() { ++ return this.visibleMap; ++ } ++ ++ public Long2ObjectLinkedOpenHashMap getUpdatingMap() { ++ return this.updatingMap; ++ } ++ ++ public int getVisibleSize() { ++ return this.visibleMap.size(); ++ } ++ ++ public int getVisibleSizeAsync() { ++ long readlock; ++ int ret; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ret = this.visibleMap.size(); ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map ++ public Collection getUpdatingValues() { ++ return this.updatingMap.values(); ++ } ++ ++ public List getUpdatingValuesCopy() { ++ return new ArrayList<>(this.updatingMap.values()); ++ } ++ ++ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map ++ public Collection getVisibleValues() { ++ return this.visibleMap.values(); ++ } ++ ++ public List getVisibleValuesCopy() { ++ return new ArrayList<>(this.visibleMap.values()); ++ } ++ ++ public boolean performUpdates() { ++ if (this.queuedChanges.isEmpty()) { ++ return false; ++ } ++ ++ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); ++ while (iterator.hasNext()) { ++ final Long2ObjectMap.Entry entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final Object val = entry.getValue(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ if (val == REMOVED) { ++ this.visibleMap.remove(key); ++ } else { ++ this.visibleMap.put(key, (V)val); ++ } ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedChanges.clear(); ++ return true; ++ } ++ ++ public boolean performUpdatesLockMap() { ++ if (this.queuedChanges.isEmpty()) { ++ return false; ++ } ++ ++ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); ++ ++ try { ++ this.updatingMapSeqLock.acquireWrite(); ++ ++ while (iterator.hasNext()) { ++ final Long2ObjectMap.Entry entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final Object val = entry.getValue(); ++ ++ if (val == REMOVED) { ++ this.visibleMap.remove(key); ++ } else { ++ this.visibleMap.put(key, (V)val); ++ } ++ } ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ ++ this.queuedChanges.clear(); ++ return true; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..554f4d4e63c1431721989e6f502a32ccc53a8807 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++// list with O(1) remove & contains ++/** ++ * @author Spottedleaf ++ */ ++public final class ChunkList implements Iterable { ++ ++ protected final Long2IntOpenHashMap chunkToIndex = new Long2IntOpenHashMap(2, 0.8f); ++ { ++ this.chunkToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ ++ protected static final LevelChunk[] EMPTY_LIST = new LevelChunk[0]; ++ ++ protected LevelChunk[] chunks = EMPTY_LIST; ++ protected int count; ++ ++ public int size() { ++ return this.count; ++ } ++ ++ public boolean contains(final LevelChunk chunk) { ++ return this.chunkToIndex.containsKey(chunk.coordinateKey); ++ } ++ ++ public boolean remove(final LevelChunk chunk) { ++ final int index = this.chunkToIndex.remove(chunk.coordinateKey); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entity at the end to this index ++ final int endIndex = --this.count; ++ final LevelChunk end = this.chunks[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.chunkToIndex.put(end.coordinateKey, index); // update index ++ } ++ this.chunks[index] = end; ++ this.chunks[endIndex] = null; ++ ++ return true; ++ } ++ ++ public boolean add(final LevelChunk chunk) { ++ final int count = this.count; ++ final int currIndex = this.chunkToIndex.putIfAbsent(chunk.coordinateKey, count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ LevelChunk[] list = this.chunks; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.chunks = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = chunk; ++ this.count = count + 1; ++ ++ return true; ++ } ++ ++ public LevelChunk getChecked(final int index) { ++ if (index < 0 || index >= this.count) { ++ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); ++ } ++ return this.chunks[index]; ++ } ++ ++ public LevelChunk getUnchecked(final int index) { ++ return this.chunks[index]; ++ } ++ ++ public LevelChunk[] getRawData() { ++ return this.chunks; ++ } ++ ++ public void clear() { ++ this.chunkToIndex.clear(); ++ Arrays.fill(this.chunks, 0, this.count, null); ++ this.count = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ LevelChunk lastRet; ++ int current; ++ ++ @Override ++ public boolean hasNext() { ++ return this.current < ChunkList.this.count; ++ } ++ ++ @Override ++ public LevelChunk next() { ++ if (this.current >= ChunkList.this.count) { ++ throw new NoSuchElementException(); ++ } ++ return this.lastRet = ChunkList.this.chunks[this.current++]; ++ } ++ ++ @Override ++ public void remove() { ++ final LevelChunk lastRet = this.lastRet; ++ ++ if (lastRet == null) { ++ throw new IllegalStateException(); ++ } ++ this.lastRet = null; ++ ++ ChunkList.this.remove(lastRet); ++ --this.current; ++ } ++ }; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0133ea6feb1ab88f021f66855669f58367e7420b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import net.minecraft.world.entity.Entity; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++// list with O(1) remove & contains ++/** ++ * @author Spottedleaf ++ */ ++public final class EntityList implements Iterable { ++ ++ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ { ++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ ++ protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ ++ protected Entity[] entities = EMPTY_LIST; ++ protected int count; ++ ++ public int size() { ++ return this.count; ++ } ++ ++ public boolean contains(final Entity entity) { ++ return this.entityToIndex.containsKey(entity.getId()); ++ } ++ ++ public boolean remove(final Entity entity) { ++ final int index = this.entityToIndex.remove(entity.getId()); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entity at the end to this index ++ final int endIndex = --this.count; ++ final Entity end = this.entities[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.entityToIndex.put(end.getId(), index); // update index ++ } ++ this.entities[index] = end; ++ this.entities[endIndex] = null; ++ ++ return true; ++ } ++ ++ public boolean add(final Entity entity) { ++ final int count = this.count; ++ final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ Entity[] list = this.entities; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = entity; ++ this.count = count + 1; ++ ++ return true; ++ } ++ ++ public Entity getChecked(final int index) { ++ if (index < 0 || index >= this.count) { ++ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); ++ } ++ return this.entities[index]; ++ } ++ ++ public Entity getUnchecked(final int index) { ++ return this.entities[index]; ++ } ++ ++ public Entity[] getRawData() { ++ return this.entities; ++ } ++ ++ public void clear() { ++ this.entityToIndex.clear(); ++ Arrays.fill(this.entities, 0, this.count, null); ++ this.count = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ Entity lastRet; ++ int current; ++ ++ @Override ++ public boolean hasNext() { ++ return this.current < EntityList.this.count; ++ } ++ ++ @Override ++ public Entity next() { ++ if (this.current >= EntityList.this.count) { ++ throw new NoSuchElementException(); ++ } ++ return this.lastRet = EntityList.this.entities[this.current++]; ++ } ++ ++ @Override ++ public void remove() { ++ final Entity lastRet = this.lastRet; ++ ++ if (lastRet == null) { ++ throw new IllegalStateException(); ++ } ++ this.lastRet = null; ++ ++ EntityList.this.remove(lastRet); ++ --this.current; ++ } ++ }; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..abe7f2f13ab713bf1cb0343059377ab7e1b48b6e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; ++import java.util.Arrays; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.GlobalPalette; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class IBlockDataList { ++ ++ static final GlobalPalette GLOBAL_PALETTE = (GlobalPalette) LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE; ++ ++ // map of location -> (index | (location << 16) | (palette id << 32)) ++ private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); ++ { ++ this.map.defaultReturnValue(Long.MAX_VALUE); ++ } ++ ++ private static final long[] EMPTY_LIST = new long[0]; ++ ++ private long[] byIndex = EMPTY_LIST; ++ private int size; ++ ++ public static int getLocationKey(final int x, final int y, final int z) { ++ return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); ++ } ++ ++ public static BlockState getBlockDataFromRaw(final long raw) { ++ return GLOBAL_PALETTE.getObject((int)(raw >>> 32)); ++ } ++ ++ public static int getIndexFromRaw(final long raw) { ++ return (int)(raw & 0xFFFF); ++ } ++ ++ public static int getLocationFromRaw(final long raw) { ++ return (int)((raw >>> 16) & 0xFFFF); ++ } ++ ++ public static long getRawFromValues(final int index, final int location, final BlockState data) { ++ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.getOrCreateIdFor(data)) << 32); ++ } ++ ++ public static long setIndexRawValues(final long value, final int index) { ++ return value & ~(0xFFFF) | (index); ++ } ++ ++ public long add(final int x, final int y, final int z, final BlockState data) { ++ return this.add(getLocationKey(x, y, z), data); ++ } ++ ++ public long add(final int location, final BlockState data) { ++ final long curr = this.map.get((short)location); ++ ++ if (curr == Long.MAX_VALUE) { ++ final int index = this.size++; ++ final long raw = getRawFromValues(index, location, data); ++ this.map.put((short)location, raw); ++ ++ if (index >= this.byIndex.length) { ++ this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); ++ } ++ ++ this.byIndex[index] = raw; ++ return raw; ++ } else { ++ final int index = getIndexFromRaw(curr); ++ final long raw = this.byIndex[index] = getRawFromValues(index, location, data); ++ ++ this.map.put((short)location, raw); ++ ++ return raw; ++ } ++ } ++ ++ public long remove(final int x, final int y, final int z) { ++ return this.remove(getLocationKey(x, y, z)); ++ } ++ ++ public long remove(final int location) { ++ final long ret = this.map.remove((short)location); ++ final int index = getIndexFromRaw(ret); ++ if (ret == Long.MAX_VALUE) { ++ return ret; ++ } ++ ++ // move the entry at the end to this index ++ final int endIndex = --this.size; ++ final long end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); ++ } ++ this.byIndex[index] = end; ++ this.byIndex[endIndex] = 0L; ++ ++ return ret; ++ } ++ ++ public int size() { ++ return this.size; ++ } ++ ++ public long getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public int getLocation(final int index) { ++ return getLocationFromRaw(this.getRaw(index)); ++ } ++ ++ public BlockState getData(final int index) { ++ return getBlockDataFromRaw(this.getRaw(index)); ++ } ++ ++ public void clear() { ++ this.size = 0; ++ this.map.clear(); ++ } ++ ++ public LongIterator getRawIterator() { ++ return this.map.values().iterator(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3b936f54b3fff418c265639ef223292ccc89356 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +@@ -0,0 +1,230 @@ ++package com.destroystokyo.paper.util.math; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class IntegerUtil { ++ ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static long trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ // copied from hacker's delight (signed division magic value) ++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt ++ public static long getDivisorNumbers(final int d) { ++ final int ad = IntegerUtil.branchlessAbs(d); ++ ++ if (ad < 2) { ++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); ++ } ++ ++ final int two31 = 0x80000000; ++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - t%ad; ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p - 32; ++ return ((long)magicNum << 32) | shift; ++ } ++ ++ public static int branchlessAbs(final int val) { ++ // -n = -1 ^ n + 1 ++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ public static long branchlessAbs(final long val) { ++ // -n = -1 ^ n + 1 ++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ //https://github.com/skeeto/hash-prospector for hash functions ++ ++ //score = ~590.47984224483832 ++ public static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ //score = ~310.01596637036749 ++ public static int hash1(int x) { ++ x ^= x >>> 15; ++ x *= 0x356aaaad; ++ x ^= x >>> 17; ++ return x; ++ } ++ ++ public static int hash2(int x) { ++ x ^= x >>> 16; ++ x *= 0x7feb352d; ++ x ^= x >>> 15; ++ x *= 0x846ca68b; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public static int hash3(int x) { ++ x ^= x >>> 17; ++ x *= 0xed5ad4bb; ++ x ^= x >>> 11; ++ x *= 0xac4c1b51; ++ x ^= x >>> 15; ++ x *= 0x31848bab; ++ x ^= x >>> 14; ++ return x; ++ } ++ ++ //score = ~365.79959673201887 ++ public static long hash1(long x) { ++ x ^= x >>> 27; ++ x *= 0xb24924b71d2d354bL; ++ x ^= x >>> 28; ++ return x; ++ } ++ ++ //h2 hash ++ public static long hash2(long x) { ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ return x; ++ } ++ ++ public static long hash3(long x) { ++ x ^= x >>> 45; ++ x *= 0xc161abe5704b6c79L; ++ x ^= x >>> 41; ++ x *= 0xe3e5389aedbc90f7L; ++ x ^= x >>> 56; ++ x *= 0x1f9aba75a52db073L; ++ x ^= x >>> 53; ++ return x; ++ } ++ ++ private IntegerUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c601f04b9c6dff76606763ea6f4a9a89b7e83203 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java +@@ -0,0 +1,453 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import com.destroystokyo.paper.util.math.IntegerUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.level.ChunkPos; ++import javax.annotation.Nullable; ++import java.util.Iterator; ++ ++/** @author Spottedleaf */ ++public abstract class AreaMap { ++ ++ /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */ ++ ++ protected final Object2LongOpenHashMap objectToLastCoordinate = new Object2LongOpenHashMap<>(); ++ protected final Object2IntOpenHashMap objectToViewDistance = new Object2IntOpenHashMap<>(); ++ ++ { ++ this.objectToViewDistance.defaultReturnValue(-1); ++ this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE); ++ } ++ ++ // we use linked for better iteration. ++ // map of: coordinate to set of objects in coordinate ++ protected final Long2ObjectOpenHashMap> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f); ++ protected final PooledLinkedHashSets pooledHashSets; ++ ++ protected final ChangeCallback addCallback; ++ protected final ChangeCallback removeCallback; ++ protected final ChangeSourceCallback changeSourceCallback; ++ ++ public AreaMap() { ++ this(new PooledLinkedHashSets<>()); ++ } ++ ++ // let users define a "global" or "shared" pooled sets if they wish ++ public AreaMap(final PooledLinkedHashSets pooledHashSets) { ++ this(pooledHashSets, null, null); ++ } ++ ++ public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback) { ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { ++ this.pooledHashSets = pooledHashSets; ++ this.addCallback = addCallback; ++ this.removeCallback = removeCallback; ++ this.changeSourceCallback = changeSourceCallback; ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { ++ return this.areaMap.get(key); ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkPos chunkPos) { ++ return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos)); ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { ++ return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ ++ // Long.MIN_VALUE indicates the object is not mapped ++ public final long getLastCoordinate(final E object) { ++ return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); ++ } ++ ++ // -1 indicates the object is not mapped ++ public final int getLastViewDistance(final E object) { ++ return this.objectToViewDistance.getOrDefault(object, -1); ++ } ++ ++ // returns the total number of mapped chunks ++ public final int size() { ++ return this.areaMap.size(); ++ } ++ ++ public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final long oldPos = this.objectToLastCoordinate.put(object, newPos); ++ ++ if (oldViewDistance == -1) { ++ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); ++ this.addObjectCallback(object, chunkX, chunkZ, viewDistance); ++ } else { ++ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); ++ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); ++ } ++ //this.validate(object, viewDistance); ++ } ++ ++ public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance); ++ if (oldViewDistance == -1) { ++ return false; ++ } else { ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final long oldPos = this.objectToLastCoordinate.put(object, newPos); ++ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); ++ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); ++ } ++ //this.validate(object, viewDistance); ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ if (newPosition != oldPosition && this.changeSourceCallback != null) { ++ this.changeSourceCallback.accept(Object, oldPosition, newPosition); ++ } ++ } ++ ++ public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); ++ if (oldViewDistance != -1) { ++ return false; ++ } ++ ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ this.objectToLastCoordinate.put(object, newPos); ++ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); ++ this.addObjectCallback(object, chunkX, chunkZ, viewDistance); ++ ++ //this.validate(object, viewDistance); ++ ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} ++ ++ public final boolean remove(final E object) { ++ final long position = this.objectToLastCoordinate.removeLong(object); ++ final int viewDistance = this.objectToViewDistance.removeInt(object); ++ ++ if (viewDistance == -1) { ++ return false; ++ } ++ ++ final int currentX = MCUtil.getCoordinateX(position); ++ final int currentZ = MCUtil.getCoordinateZ(position); ++ ++ this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance); ++ this.removeObjectCallback(object, currentX, currentZ, viewDistance); ++ //this.validate(object, -1); ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} ++ ++ protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final E object); ++ ++ // expensive op, only for debug ++ protected void validate(final E object, final int viewDistance) { ++ int entiesGot = 0; ++ int expectedEntries = (2 * viewDistance + 1); ++ expectedEntries *= expectedEntries; ++ if (viewDistance < 0) { ++ expectedEntries = 0; ++ } ++ ++ final long currPosition = this.objectToLastCoordinate.getLong(object); ++ ++ final int centerX = MCUtil.getCoordinateX(currPosition); ++ final int centerZ = MCUtil.getCoordinateZ(currPosition); ++ ++ for (Iterator>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ ++ final Long2ObjectLinkedOpenHashMap.Entry> entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); ++ ++ if (map.referenceCount == 0) { ++ throw new IllegalStateException("Invalid map"); ++ } ++ ++ if (map.contains(object)) { ++ ++entiesGot; ++ ++ final int chunkX = MCUtil.getCoordinateX(key); ++ final int chunkZ = MCUtil.getCoordinateZ(key); ++ ++ final int dist = Math.max(IntegerUtil.branchlessAbs(chunkX - centerX), IntegerUtil.branchlessAbs(chunkZ - centerZ)); ++ ++ if (dist > viewDistance) { ++ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); ++ } ++ } ++ } ++ ++ if (entiesGot != expectedEntries) { ++ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); ++ } ++ } ++ ++ private void addObjectTo(final E object, final int chunkX, final int chunkZ, final int currChunkX, ++ final int currChunkZ, final int prevChunkX, final int prevChunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty = this.getEmptySetFor(object); ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.putIfAbsent(key, empty); ++ ++ if (current != null) { ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWith(current, object); ++ if (next == current) { ++ throw new IllegalStateException("Expected different map: got " + next.toString()); ++ } ++ this.areaMap.put(key, next); ++ ++ current = next; ++ // fall through to callback ++ } else { ++ current = empty; ++ } ++ ++ if (this.addCallback != null) { ++ try { ++ this.addCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, current); ++ } catch (final Throwable ex) { ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ MinecraftServer.LOGGER.error("Add callback for map threw exception ", ex); ++ } ++ } ++ } ++ ++ private void removeObjectFrom(final E object, final int chunkX, final int chunkZ, final int currChunkX, ++ final int currChunkZ, final int prevChunkX, final int prevChunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.get(key); ++ ++ if (current == null) { ++ throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")"); ++ } ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWithout(current, object); ++ ++ if (next == current) { ++ throw new IllegalStateException("Current map [" + next.toString() + "] should have contained " + object + ", (" + chunkX + "," + chunkZ + ")"); ++ } ++ ++ if (next != null) { ++ this.areaMap.put(key, next); ++ } else { ++ this.areaMap.remove(key); ++ } ++ ++ if (this.removeCallback != null) { ++ try { ++ this.removeCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, next); ++ } catch (final Throwable ex) { ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ MinecraftServer.LOGGER.error("Remove callback for map threw exception ", ex); ++ } ++ } ++ } ++ ++ private void addObject(final E object, final int chunkX, final int chunkZ, final int prevChunkX, final int prevChunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.addObjectTo(object, x, z, chunkX, chunkZ, prevChunkX, prevChunkZ); ++ } ++ } ++ } ++ ++ private void removeObject(final E object, final int chunkX, final int chunkZ, final int currentChunkX, final int currentChunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.removeObjectFrom(object, x, z, currentChunkX, currentChunkZ, chunkX, chunkZ); ++ } ++ } ++ } ++ ++ /* math sign function except 0 returns 1 */ ++ protected static int sign(int val) { ++ return 1 | (val >> (Integer.SIZE - 1)); ++ } ++ ++ private void updateObject(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ final int toX = MCUtil.getCoordinateX(newPosition); ++ final int toZ = MCUtil.getCoordinateZ(newPosition); ++ final int fromX = MCUtil.getCoordinateX(oldPosition); ++ final int fromZ = MCUtil.getCoordinateZ(oldPosition); ++ ++ final int dx = toX - fromX; ++ final int dz = toZ - fromZ; ++ ++ final int totalX = IntegerUtil.branchlessAbs(fromX - toX); ++ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { ++ // teleported? ++ this.removeObject(object, fromX, fromZ, fromX, fromZ, oldViewDistance); ++ this.addObject(object, toX, toZ, fromX, fromZ, newViewDistance); ++ return; ++ } ++ ++ if (oldViewDistance != newViewDistance) { ++ // remove loop ++ ++ final int oldMinX = fromX - oldViewDistance; ++ final int oldMinZ = fromZ - oldViewDistance; ++ final int oldMaxX = fromX + oldViewDistance; ++ final int oldMaxZ = fromZ + oldViewDistance; ++ for (int currX = oldMinX; currX <= oldMaxX; ++currX) { ++ for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) { ++ ++ // only remove if we're outside the new view distance... ++ if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ // add loop ++ ++ final int newMinX = toX - newViewDistance; ++ final int newMinZ = toZ - newViewDistance; ++ final int newMaxX = toX + newViewDistance; ++ final int newMaxZ = toZ + newViewDistance; ++ for (int currX = newMinX; currX <= newMaxX; ++currX) { ++ for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) { ++ ++ // only add if we're outside the old view distance... ++ if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ return; ++ } ++ ++ // x axis is width ++ // z axis is height ++ // right refers to the x axis of where we moved ++ // top refers to the z axis of where we moved ++ ++ // same view distance ++ ++ // used for relative positioning ++ final int up = sign(dz); // 1 if dz >= 0, -1 otherwise ++ final int right = sign(dx); // 1 if dx >= 0, -1 otherwise ++ ++ // The area excluded by overlapping the two view distance squares creates four rectangles: ++ // Two on the left, and two on the right. The ones on the left we consider the "removed" section ++ // and on the right the "added" section. ++ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually ++ // exclusive to the regions they surround. ++ ++ // 4 points of the rectangle ++ int maxX; // exclusive ++ int minX; // inclusive ++ int maxZ; // exclusive ++ int minZ; // inclusive ++ ++ if (dx != 0) { ++ // handle right addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX + (oldViewDistance * right) + right; // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle up addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = toX - (oldViewDistance * right); // inclusive ++ maxZ = toZ + (oldViewDistance * up) + up; // exclusive ++ minZ = fromZ + (oldViewDistance * up) + up; // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dx != 0) { ++ // handle left removal ++ ++ maxX = toX - (oldViewDistance * right); // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle down removal ++ ++ maxX = fromX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = toZ - (oldViewDistance * up); // exclusive ++ minZ = fromZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface ChangeCallback { ++ ++ // if there is no previous position, then prevPos = Integer.MIN_VALUE ++ void accept(final E object, final int rangeX, final int rangeZ, final int currPosX, final int currPosZ, final int prevPosX, final int prevPosZ, ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState); ++ ++ } ++ ++ @FunctionalInterface ++ public static interface ChangeSourceCallback { ++ void accept(final E object, final long prevPos, final long newPos); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b8cb361767fbcf5f592db32a12186f0bd6373bd +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java +@@ -0,0 +1,175 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import com.destroystokyo.paper.util.math.IntegerUtil; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.ChunkPos; ++ ++/** @author Spottedleaf */ ++public abstract class DistanceTrackingAreaMap extends AreaMap { ++ ++ // use this map only if you need distance tracking, the tracking here is obviously going to hit harder. ++ ++ protected final Long2IntOpenHashMap chunkToNearestDistance = new Long2IntOpenHashMap(1024, 0.7f); ++ { ++ this.chunkToNearestDistance.defaultReturnValue(-1); ++ } ++ ++ protected final DistanceChangeCallback distanceChangeCallback; ++ ++ public DistanceTrackingAreaMap() { ++ this(new PooledLinkedHashSets<>()); ++ } ++ ++ // let users define a "global" or "shared" pooled sets if they wish ++ public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ this(pooledHashSets, null, null, null); ++ } ++ ++ public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, ++ final DistanceChangeCallback distanceChangeCallback) { ++ super(pooledHashSets, addCallback, removeCallback); ++ this.distanceChangeCallback = distanceChangeCallback; ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final long key) { ++ return this.chunkToNearestDistance.get(key); ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final ChunkPos chunkPos) { ++ return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkPos)); ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final int chunkX, final int chunkZ) { ++ return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ ++ protected final void recalculateDistance(final int chunkX, final int chunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state = this.areaMap.get(key); ++ if (state == null) { ++ final int oldDistance = this.chunkToNearestDistance.remove(key); ++ // nothing here. ++ if (oldDistance == -1) { ++ // nothing was here previously ++ return; ++ } ++ if (this.distanceChangeCallback != null) { ++ this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, -1, null); ++ } ++ return; ++ } ++ ++ int newDistance = Integer.MAX_VALUE; ++ ++ final Object[] rawData = state.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ final Object raw = rawData[i]; ++ ++ if (raw == null) { ++ continue; ++ } ++ ++ final E object = (E)raw; ++ final long location = this.objectToLastCoordinate.getLong(object); ++ ++ final int distance = Math.max(IntegerUtil.branchlessAbs(chunkX - MCUtil.getCoordinateX(location)), IntegerUtil.branchlessAbs(chunkZ - MCUtil.getCoordinateZ(location))); ++ ++ if (distance < newDistance) { ++ newDistance = distance; ++ } ++ } ++ ++ final int oldDistance = this.chunkToNearestDistance.put(key, newDistance); ++ ++ if (oldDistance != newDistance) { ++ if (this.distanceChangeCallback != null) { ++ this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, newDistance, state); ++ } ++ } ++ } ++ ++ @Override ++ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.recalculateDistance(x, z); ++ } ++ } ++ } ++ ++ @Override ++ protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.recalculateDistance(x, z); ++ } ++ } ++ } ++ ++ @Override ++ protected void updateObjectCallback(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ if (oldPosition == newPosition && newViewDistance == oldViewDistance) { ++ return; ++ } ++ ++ final int toX = MCUtil.getCoordinateX(newPosition); ++ final int toZ = MCUtil.getCoordinateZ(newPosition); ++ final int fromX = MCUtil.getCoordinateX(oldPosition); ++ final int fromZ = MCUtil.getCoordinateZ(oldPosition); ++ ++ final int totalX = IntegerUtil.branchlessAbs(fromX - toX); ++ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { ++ // teleported? ++ this.removeObjectCallback(object, fromX, fromZ, oldViewDistance); ++ this.addObjectCallback(object, toX, toZ, newViewDistance); ++ return; ++ } ++ ++ final int minX = Math.min(fromX - oldViewDistance, toX - newViewDistance); ++ final int maxX = Math.max(fromX + oldViewDistance, toX + newViewDistance); ++ final int minZ = Math.min(fromZ - oldViewDistance, toZ - newViewDistance); ++ final int maxZ = Math.max(fromZ + oldViewDistance, toZ + newViewDistance); ++ ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ final int distXOld = IntegerUtil.branchlessAbs(x - fromX); ++ final int distZOld = IntegerUtil.branchlessAbs(z - fromZ); ++ ++ if (Math.max(distXOld, distZOld) <= oldViewDistance) { ++ this.recalculateDistance(x, z); ++ continue; ++ } ++ ++ final int distXNew = IntegerUtil.branchlessAbs(x - toX); ++ final int distZNew = IntegerUtil.branchlessAbs(z - toZ); ++ ++ if (Math.max(distXNew, distZNew) <= newViewDistance) { ++ this.recalculateDistance(x, z); ++ continue; ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface DistanceChangeCallback { ++ ++ void accept(final int posX, final int posZ, final int oldNearestDistance, final int newNearestDistance, ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state); ++ ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..46954db7ecd35ac4018fdf476df7c8020d7ce6c8 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java +@@ -0,0 +1,32 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import net.minecraft.server.level.ServerPlayer; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class PlayerAreaMap extends AreaMap { ++ ++ public PlayerAreaMap() { ++ super(); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ super(pooledHashSets); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback) { ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { ++ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback); ++ } ++ ++ @Override ++ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final ServerPlayer player) { ++ return player.cachedSingleHashSet; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d05dcea15f7047b58736c7c0e07920a04d6c5abe +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java +@@ -0,0 +1,24 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import net.minecraft.server.level.ServerPlayer; ++ ++public class PlayerDistanceTrackingAreaMap extends DistanceTrackingAreaMap { ++ ++ public PlayerDistanceTrackingAreaMap() { ++ super(); ++ } ++ ++ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ super(pooledHashSets); ++ } ++ ++ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback, final DistanceChangeCallback distanceChangeCallback) { ++ super(pooledHashSets, addCallback, removeCallback, distanceChangeCallback); ++ } ++ ++ @Override ++ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final ServerPlayer player) { ++ return player.cachedSingleHashSet; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java +@@ -0,0 +1,287 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import java.lang.ref.WeakReference; ++ ++/** @author Spottedleaf */ ++public class PooledLinkedHashSets { ++ ++ /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */ ++ ++ // we really want to avoid that equals() check as much as possible... ++ protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f); ++ ++ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { ++ if (current.referenceCount == 0) { ++ throw new IllegalStateException("Cannot decrement reference count for " + current); ++ } ++ if (current.referenceCount == -1 || --current.referenceCount > 0) { ++ return; ++ } ++ ++ this.mapPool.remove(current); ++ return; ++ } ++ ++ public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { ++ final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); ++ ++ if (cached != null) { ++ decrementReferenceCount(current); ++ ++ if (cached.referenceCount == 0) { ++ // bring the map back from the dead ++ PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); ++ if (contending != null) { ++ // a map already exists with the elements we want ++ if (contending.referenceCount != -1) { ++ ++contending.referenceCount; ++ } ++ current.updateAddCache(object, contending); ++ return contending; ++ } ++ ++ cached.referenceCount = 1; ++ } else if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ return cached; ++ } ++ ++ if (!current.add(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.remove(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.remove(object); ++ } ++ ++ current.updateAddCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ // rets null if current.size() == 1 ++ public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { ++ if (current.set.size() == 1) { ++ decrementReferenceCount(current); ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); ++ ++ if (cached != null) { ++ decrementReferenceCount(current); ++ ++ if (cached.referenceCount == 0) { ++ // bring the map back from the dead ++ PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); ++ if (contending != null) { ++ // a map already exists with the elements we want ++ if (contending.referenceCount != -1) { ++ ++contending.referenceCount; ++ } ++ current.updateRemoveCache(object, contending); ++ return contending; ++ } ++ ++ cached.referenceCount = 1; ++ } else if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ return cached; ++ } ++ ++ if (!current.remove(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.add(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.add(object); ++ } ++ ++ current.updateRemoveCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ static final class RawSetObjectLinkedOpenHashSet extends ObjectOpenHashSet { ++ ++ public RawSetObjectLinkedOpenHashSet() { ++ super(); ++ } ++ ++ public RawSetObjectLinkedOpenHashSet(final int capacity) { ++ super(capacity); ++ } ++ ++ public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) { ++ super(capacity, loadFactor); ++ } ++ ++ @Override ++ public RawSetObjectLinkedOpenHashSet clone() { ++ return (RawSetObjectLinkedOpenHashSet)super.clone(); ++ } ++ ++ public E[] getRawSet() { ++ return this.key; ++ } ++ } ++ ++ public static final class PooledObjectLinkedOpenHashSet { ++ ++ private static final WeakReference NULL_REFERENCE = new WeakReference<>(null); ++ ++ final RawSetObjectLinkedOpenHashSet set; ++ int referenceCount; // -1 if special ++ int hash; // optimize hashcode ++ ++ // add cache ++ WeakReference lastAddObject = NULL_REFERENCE; ++ WeakReference> lastAddMap = NULL_REFERENCE; ++ ++ // remove cache ++ WeakReference lastRemoveObject = NULL_REFERENCE; ++ WeakReference> lastRemoveMap = NULL_REFERENCE; ++ ++ public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets pooledSets) { ++ this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final E single) { ++ this((PooledLinkedHashSets)null); ++ this.referenceCount = -1; ++ this.add(single); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { ++ this.set = other.set.clone(); ++ this.hash = other.hash; ++ } ++ ++ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java ++ // generated by https://github.com/skeeto/hash-prospector ++ private static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ PooledObjectLinkedOpenHashSet getAddCache(final E element) { ++ final E currentAdd = this.lastAddObject.get(); ++ ++ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { ++ return null; ++ } ++ ++ return this.lastAddMap.get(); ++ } ++ ++ PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { ++ final E currentRemove = this.lastRemoveObject.get(); ++ ++ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { ++ return null; ++ } ++ ++ return this.lastRemoveMap.get(); ++ } ++ ++ void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastAddObject = new WeakReference<>(element); ++ this.lastAddMap = new WeakReference<>(map); ++ } ++ ++ void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastRemoveObject = new WeakReference<>(element); ++ this.lastRemoveMap = new WeakReference<>(map); ++ } ++ ++ boolean add(final E element) { ++ boolean added = this.set.add(element); ++ ++ if (added) { ++ this.hash += hash0(element.hashCode()); ++ } ++ ++ return added; ++ } ++ ++ boolean remove(Object element) { ++ boolean removed = this.set.remove(element); ++ ++ if (removed) { ++ this.hash -= hash0(element.hashCode()); ++ } ++ ++ return removed; ++ } ++ ++ public boolean contains(final Object element) { ++ return this.set.contains(element); ++ } ++ ++ public E[] getBackingSet() { ++ return this.set.getRawSet(); ++ } ++ ++ public int size() { ++ return this.set.size(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object other) { ++ if (!(other instanceof PooledObjectLinkedOpenHashSet)) { ++ return false; ++ } ++ if (this.referenceCount == 0) { ++ return other == this; ++ } else { ++ if (other == this) { ++ // Unfortunately we are never equal to our own instance while in use! ++ return false; ++ } ++ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + ++ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d0c77068e9a53d1b8bbad0f3f6b420d6bc85f8c8 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java +@@ -0,0 +1,85 @@ ++package com.destroystokyo.paper.util.pooled; ++ ++import net.minecraft.server.MCUtil; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++import java.util.ArrayDeque; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++ ++public final class PooledObjects { ++ ++ /** ++ * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool. ++ */ ++ public class AutoReleased { ++ private final E object; ++ private final Runnable cleaner; ++ ++ public AutoReleased(E object, Runnable cleaner) { ++ this.object = object; ++ this.cleaner = cleaner; ++ } ++ ++ public final E getObject() { ++ return object; ++ } ++ ++ public final Runnable getCleaner() { ++ return cleaner; ++ } ++ } ++ ++ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024); ++ ++ private final Supplier creator; ++ private final Consumer releaser; ++ private final int maxPoolSize; ++ private final ArrayDeque queue; ++ ++ public PooledObjects(final Supplier creator, int maxPoolSize) { ++ this(creator, maxPoolSize, null); ++ } ++ public PooledObjects(final Supplier creator, int maxPoolSize, Consumer releaser) { ++ if (creator == null) { ++ throw new NullPointerException("Creator must not be null"); ++ } ++ if (maxPoolSize <= 0) { ++ throw new IllegalArgumentException("Max pool size must be greater-than 0"); ++ } ++ ++ this.queue = new ArrayDeque<>(maxPoolSize); ++ this.maxPoolSize = maxPoolSize; ++ this.creator = creator; ++ this.releaser = releaser; ++ } ++ ++ public AutoReleased acquireCleaner(Object holder) { ++ return acquireCleaner(holder, this::release); ++ } ++ ++ public AutoReleased acquireCleaner(Object holder, Consumer releaser) { ++ E resource = acquire(); ++ Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser); ++ return new AutoReleased(resource, cleaner); ++ } ++ ++ public final E acquire() { ++ E value; ++ synchronized (queue) { ++ value = this.queue.pollLast(); ++ } ++ return value != null ? value : this.creator.get(); ++ } ++ ++ public final void release(final E value) { ++ if (this.releaser != null) { ++ this.releaser.accept(value); ++ } ++ synchronized (this.queue) { ++ if (queue.size() < this.maxPoolSize) { ++ this.queue.addLast(value); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +@@ -0,0 +1,67 @@ ++package com.destroystokyo.paper.util.set; ++ ++import java.util.Collection; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class OptimizedSmallEnumSet> { ++ ++ private final Class enumClass; ++ private long backingSet; ++ ++ public OptimizedSmallEnumSet(final Class clazz) { ++ if (clazz == null) { ++ throw new IllegalArgumentException("Null class"); ++ } ++ if (!clazz.isEnum()) { ++ throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); ++ } ++ this.enumClass = clazz; ++ } ++ ++ public boolean addUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev | key; ++ ++ return (prev & key) == 0; ++ } ++ ++ public boolean removeUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev & ~key; ++ ++ return (prev & key) != 0; ++ } ++ ++ public void clear() { ++ this.backingSet = 0L; ++ } ++ ++ public int size() { ++ return Long.bitCount(this.backingSet); ++ } ++ ++ public void addAllUnchecked(final Collection enums) { ++ for (final E element : enums) { ++ if (element == null) { ++ throw new NullPointerException("Null element"); ++ } ++ this.backingSet |= (1L << element.ordinal()); ++ } ++ } ++ ++ public long getBackingSet() { ++ return this.backingSet; ++ } ++ ++ public boolean hasCommonElements(final OptimizedSmallEnumSet other) { ++ return (other.backingSet & this.backingSet) != 0; ++ } ++} +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 8c378d3f3138953b3b22b289fecdb6b40a09ab63..67fa685f4b8de3eae1431c0de399c246678b542a 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -78,7 +78,7 @@ public class Util { + } + + public static long getNanos() { +- return Util.timeSource.getAsLong(); ++ return System.nanoTime(); // Paper + } + + public static long getEpochMillis() { +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 88147a1f25cf2fd549412b653b8f0eb5c60bb55d..6a58059a05e16d96894b67a544c2f595d9546c78 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -105,6 +105,7 @@ public class BlockPos extends Vec3i { + return x == 0.0D && y == 0.0D && z == 0.0D ? this : new BlockPos((double) this.getX() + x, (double) this.getY() + y, (double) this.getZ() + z); + } + ++ public final BlockPos add(int i, int j, int k) {return offset(i, j, k);} // Paper - OBFHELPER + public BlockPos offset(int x, int y, int z) { + return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.getX() + x, this.getY() + y, this.getZ() + z); + } +@@ -436,6 +437,7 @@ public class BlockPos extends Vec3i { + return super.rotate(rotation).immutable(); + } + ++ public final BlockPos.MutableBlockPos setValues(int i, int j, int k) { return set(i, j, k);} // Paper - OBFHELPER + public BlockPos.MutableBlockPos set(int x, int y, int z) { + this.setX(x); + this.setY(y); +@@ -443,6 +445,7 @@ public class BlockPos extends Vec3i { + return this; + } + ++ public final BlockPos.MutableBlockPos setValues(double d0, double d1, double d2) { return set(d0, d1, d2);} // Paper - OBFHELPER + public BlockPos.MutableBlockPos set(double x, double y, double z) { + return this.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + } +@@ -496,20 +499,21 @@ public class BlockPos extends Vec3i { + } + } + ++ /* // Paper start - comment out useless overrides @Override + @Override +- public void setX(int x) { +- super.setX(x); ++ public void o(int i) { ++ super.o(i); + } + + @Override +- public void setY(int y) { +- super.setY(y); ++ public void p(int i) { ++ super.p(i); + } + +- @Override +- public void setZ(int z) { +- super.setZ(z); ++ public void q(int i) { ++ super.q(i); + } ++ */ // Paper end + + @Override + public BlockPos immutable() { +diff --git a/src/main/java/net/minecraft/core/IdMapper.java b/src/main/java/net/minecraft/core/IdMapper.java +index 424c6cacc2e7c7b1c9d0b92fe198237033a3fcbd..e7358721e9d78bc9cbbfc3e71ce927ea4b82ce7c 100644 +--- a/src/main/java/net/minecraft/core/IdMapper.java ++++ b/src/main/java/net/minecraft/core/IdMapper.java +@@ -64,6 +64,7 @@ public class IdMapper implements IdMap { + return Iterators.filter(this.idToT.iterator(), Predicates.notNull()); + } + ++ public int size() { return this.size(); } // Paper - OBFHELPER + public int size() { + return this.tToId.size(); + } +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index 9bd2120bdcfe204184eb9a9e1daa5e3338665e51..3e79b274b8e0406a3cbdd94c7cec091b583109ca 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -18,9 +18,9 @@ public class Vec3i implements Comparable { + return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); + }); + public static final Vec3i ZERO = new Vec3i(0, 0, 0); +- private int x; +- private int y; +- private int z; ++ private int x;public final void setX(final int x) { this.x = x; } // Paper - OBFHELPER ++ private int y;public final void setY(final int y) { this.y = y; } // Paper - OBFHELPER ++ private int z;public final void setZ(final int z) { this.z = z; } // Paper - OBFHELPER + + public Vec3i(int x, int y, int z) { + this.x = x; +@@ -64,15 +64,15 @@ public class Vec3i implements Comparable { + return this.z; + } + +- protected void setX(int x) { ++ public void setX(int x) { // Paper - protected -> public + this.x = x; + } + +- protected void setY(int y) { ++ public void setY(int y) { // Paper - protected -> public + this.y = y; + } + +- protected void setZ(int z) { ++ public void setZ(int z) { // Paper - protected -> public + this.z = z; + } + +@@ -108,6 +108,7 @@ public class Vec3i implements Comparable { + return this.distSqr(pos.x(), pos.y(), pos.z(), true) < distance * distance; + } + ++ public final double distanceSquared(Vec3i baseblockposition) { return distSqr(baseblockposition); } // Paper - OBFHELPER + public double distSqr(Vec3i vec) { + return this.distSqr((double) vec.getX(), (double) vec.getY(), (double) vec.getZ(), true); + } +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index 077383bd9af79851351eba50e7d7ea31cc106cad..4c8f249e45e5deb7628997d4dbd9dab613ac5241 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -76,7 +76,7 @@ public class CompoundTag implements Tag { + return "TAG_Compound"; + } + }; +- private final Map tags; ++ public final Map tags; // Paper + + protected CompoundTag(Map tags) { + this.tags = tags; +@@ -139,10 +139,16 @@ public class CompoundTag implements Tag { + this.tags.put(key, LongTag.valueOf(value)); + } + ++ public void setUUID(String prefix, UUID uuid) { putUUID(prefix, uuid); } // Paper - OBFHELPER + public void putUUID(String key, UUID value) { + this.tags.put(key, NbtUtils.createUUID(value)); + } + ++ ++ /** ++ * You must use {@link #hasUUID(String)} before or else it will throw an NPE. ++ */ ++ public UUID getUUID(String prefix) { return getUUID(prefix); } // Paper - OBFHELPER + public UUID getUUID(String key) { + return NbtUtils.loadUUID(this.get(key)); + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 8ce3a74821a0e540423a2e8e67be640b8b876035..92c5c5bbcfe364475578b6a0eddfaa85858ace8a 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -168,6 +168,7 @@ public class Connection extends SimpleChannelInboundHandler> { + + } + ++ private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.sendPacket(packet, genericFutureListener); } // Paper - OBFHELPER + private void sendPacket(Packet packet, @Nullable GenericFutureListener> callback) { + ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); + ConnectionProtocol enumprotocol1 = (ConnectionProtocol) this.channel.attr(Connection.ATTRIBUTE_PROTOCOL).get(); +@@ -208,6 +209,7 @@ public class Connection extends SimpleChannelInboundHandler> { + + } + ++ private void sendPacketQueue() { this.flushQueue(); } // Paper - OBFHELPER + private void flushQueue() { + if (this.channel != null && this.channel.isOpen()) { + Queue queue = this.queue; +@@ -344,9 +346,9 @@ public class Connection extends SimpleChannelInboundHandler> { + + static class PacketHolder { + +- private final Packet packet; ++ private final Packet packet; private final Packet getPacket() { return this.packet; } // Paper - OBFHELPER + @Nullable +- private final GenericFutureListener> listener; ++ private final GenericFutureListener> listener; private final GenericFutureListener> getGenericFutureListener() { return this.listener; } // Paper - OBFHELPER + + public PacketHolder(Packet packet, @Nullable GenericFutureListener> callback) { + this.packet = packet; +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index e0dc41a8f408b7fa0b8554833ea4d09e7e604913..50f14acb062c2f90266279dbd1945a3297396f0b 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -48,6 +48,7 @@ public class FriendlyByteBuf extends ByteBuf { + this.source = bytebuf; + } + ++ public static int countBytes(int i) { return FriendlyByteBuf.getVarIntSize(i); } // Paper - OBFHELPER + public static int getVarIntSize(int i) { + for (int j = 1; j < 5; ++j) { + if ((i & -1 << j * 7) == 0) { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 7af36e5a889d04f6e80c80f7335bf149a4b5d224..14fa1371e52b9af5a7550a9aa144fa406b754046 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -44,6 +44,7 @@ public class PacketEncoder extends MessageToByteEncoder> { + packet.write(packetdataserializer); + } catch (Throwable throwable) { + PacketEncoder.LOGGER.error(throwable); ++ throwable.printStackTrace(); // Paper - WHAT WAS IT? WHO DID THIS TO YOU? WHAT DID YOU SEE? + if (packet.isSkippable()) { + throw new SkipPacketException(throwable); + } else { +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +index bda189ab2b3b934e6bf9fd11da5d95bd9b37ba70..e5d4363edb8c494d2db69d2e0223a2db1519f64b 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -28,7 +28,7 @@ public class ClientboundLevelChunkPacket implements Packet blockEntitiesTags; + private boolean fullChunk; + +@@ -140,6 +140,7 @@ public class ClientboundLevelChunkPacket implements Packet { + +- private ItemStack book; ++ private ItemStack book; public ItemStack getBook() { return book; } // Paper - OBFHELPER + private boolean signing; + private int slot; + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d29fe67b7d39e368a873368a6be16042429e9209 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -0,0 +1,510 @@ ++package net.minecraft.server; ++ ++import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.ClipContext; ++import net.minecraft.world.level.Level; ++import org.apache.commons.lang.exception.ExceptionUtils; ++import org.bukkit.Location; ++import org.bukkit.block.BlockFace; ++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.List; ++import java.util.Queue; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.BiConsumer; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++ ++public final class MCUtil { ++ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( ++ 0, 2, 60L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build() ++ ); ++ public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor( ++ 1, 1, 0L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build() ++ ); ++ ++ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); ++ ++ ++ public static Runnable once(Runnable run) { ++ AtomicBoolean ran = new AtomicBoolean(false); ++ return () -> { ++ if (ran.compareAndSet(false, true)) { ++ run.run(); ++ } ++ }; ++ } ++ ++ public static Runnable once(List list, Consumer cb) { ++ return once(() -> { ++ list.forEach(cb); ++ }); ++ } ++ ++ private static Runnable makeCleanerCallback(Runnable run) { ++ return once(() -> cleanerExecutor.execute(run)); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param run ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, Runnable run) { ++ // Wrap callback in its own method above or the lambda will leak object ++ Runnable cleaner = makeCleanerCallback(run); ++ co.aikar.cleaner.Cleaner.register(obj, cleaner); ++ return cleaner; ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param list ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerListCleaner(Object obj, List list, Consumer cleaner) { ++ return registerCleaner(obj, () -> { ++ list.forEach(cleaner); ++ list.clear(); ++ }); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param resource ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer cleaner) { ++ return registerCleaner(obj, () -> cleaner.accept(resource)); ++ } ++ ++ public static List getSpiralOutChunks(BlockPos blockposition, int radius) { ++ List list = com.google.common.collect.Lists.newArrayList(); ++ ++ list.add(new ChunkPos(blockposition.getX() >> 4, blockposition.getZ() >> 4)); ++ for (int r = 1; r <= radius; r++) { ++ int x = -r; ++ int z = r; ++ ++ // Iterates the edge of half of the box; then negates for other half. ++ while (x <= r && z > -r) { ++ list.add(new ChunkPos((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4)); ++ list.add(new ChunkPos((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4)); ++ ++ if (x < r) { ++ x++; ++ } else { ++ z--; ++ } ++ } ++ } ++ return list; ++ } ++ ++ public static int fastFloor(double x) { ++ int truncated = (int)x; ++ return x < (double)truncated ? truncated - 1 : truncated; ++ } ++ ++ public static int fastFloor(float x) { ++ int truncated = (int)x; ++ return x < (double)truncated ? truncated - 1 : truncated; ++ } ++ ++ public static float normalizeYaw(float f) { ++ float f1 = f % 360.0F; ++ ++ if (f1 >= 180.0F) { ++ f1 -= 360.0F; ++ } ++ ++ if (f1 < -180.0F) { ++ f1 += 360.0F; ++ } ++ ++ return f1; ++ } ++ ++ /** ++ * 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 long getCoordinateKey(final BlockPos blockPos) { ++ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final Entity entity) { ++ return ((long)(MCUtil.fastFloor(entity.getZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.getX()) >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final ChunkPos pair) { ++ return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getCoordinateX(final long key) { ++ return (int)key; ++ } ++ ++ public static int getCoordinateZ(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ public static int getChunkCoordinate(final double coordinate) { ++ return MCUtil.fastFloor(coordinate) >> 4; ++ } ++ ++ public static int getBlockCoordinate(final double coordinate) { ++ return MCUtil.fastFloor(coordinate); ++ } ++ ++ public static long getBlockKey(final int x, final int y, final int z) { ++ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); ++ } ++ ++ public static long getBlockKey(final BlockPos pos) { ++ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); ++ } ++ ++ public static long getBlockKey(final Entity entity) { ++ return getBlockKey(getBlockCoordinate(entity.getX()), getBlockCoordinate(entity.getY()), getBlockCoordinate(entity.getZ())); ++ } ++ ++ // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable ++ public static void mergeSortedSets(final java.util.function.Consumer consumer, final java.util.Comparator comparator, final java.util.SortedSet...sets) { ++ final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); ++ // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimic what mergesort does. ++ for (java.util.SortedSet set : sets) { ++ if (set != null) { ++ all.addAll(set); ++ } ++ } ++ all.forEach(consumer); ++ } ++ ++ private MCUtil() {} ++ ++ public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> { ++ if (!isMainThread()) { ++ MinecraftServer.getServer().execute(run); ++ } else { ++ run.run(); ++ } ++ }; ++ ++ public static CompletableFuture ensureMain(CompletableFuture future) { ++ return future.thenApplyAsync(r -> r, MAIN_EXECUTOR); ++ } ++ ++ public static void thenOnMain(CompletableFuture future, Consumer consumer) { ++ future.thenAcceptAsync(consumer, MAIN_EXECUTOR); ++ } ++ public static void thenOnMain(CompletableFuture future, BiConsumer consumer) { ++ future.whenCompleteAsync(consumer, MAIN_EXECUTOR); ++ } ++ ++ public static boolean isMainThread() { ++ return MinecraftServer.getServer().isSameThread(); ++ } ++ ++ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable) { ++ return scheduleTask(ticks, runnable, null); ++ } ++ ++ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable, String taskName) { ++ return MinecraftServer.getServer().server.getScheduler().scheduleInternalTask(runnable, ticks, taskName); ++ } ++ ++ public static void processQueue() { ++ Runnable runnable; ++ Queue processQueue = getProcessQueue(); ++ while ((runnable = processQueue.poll()) != null) { ++ try { ++ runnable.run(); ++ } catch (Exception e) { ++ MinecraftServer.LOGGER.error("Error executing task", e); ++ } ++ } ++ } ++ public static T processQueueWhileWaiting(CompletableFuture future) { ++ try { ++ if (isMainThread()) { ++ while (!future.isDone()) { ++ try { ++ return future.get(1, TimeUnit.MILLISECONDS); ++ } catch (TimeoutException ignored) { ++ processQueue(); ++ } ++ } ++ } ++ return future.get(); ++ } catch (Exception e) { ++ throw new RuntimeException(e); ++ } ++ } ++ ++ public static void ensureMain(Runnable run) { ++ ensureMain(null, run); ++ } ++ /** ++ * Ensures the target code is running on the main thread ++ * @param reason ++ * @param run ++ * @return ++ */ ++ public static void ensureMain(String reason, Runnable run) { ++ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { ++ if (reason != null) { ++ new IllegalStateException("Asynchronous " + reason + "!").printStackTrace(); ++ } ++ getProcessQueue().add(run); ++ return; ++ } ++ run.run(); ++ } ++ ++ private static Queue getProcessQueue() { ++ return MinecraftServer.getServer().processQueue; ++ } ++ ++ public static T ensureMain(Supplier run) { ++ return ensureMain(null, run); ++ } ++ /** ++ * Ensures the target code is running on the main thread ++ * @param reason ++ * @param run ++ * @param ++ * @return ++ */ ++ public static T ensureMain(String reason, Supplier run) { ++ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { ++ if (reason != null) { ++ new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace(); ++ } ++ Waitable wait = new Waitable() { ++ @Override ++ protected T evaluate() { ++ return run.get(); ++ } ++ }; ++ getProcessQueue().add(wait); ++ try { ++ return wait.get(); ++ } catch (InterruptedException | ExecutionException e) { ++ e.printStackTrace(); ++ } ++ return null; ++ } ++ return run.get(); ++ } ++ ++ /** ++ * Calculates distance between 2 entities ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distance(Entity e1, Entity e2) { ++ return Math.sqrt(distanceSq(e1, e2)); ++ } ++ ++ ++ /** ++ * Calculates distance between 2 block positions ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distance(BlockPos e1, BlockPos e2) { ++ return Math.sqrt(distanceSq(e1, e2)); ++ } ++ ++ /** ++ * Gets the distance between 2 positions ++ * @param x1 ++ * @param y1 ++ * @param z1 ++ * @param x2 ++ * @param y2 ++ * @param z2 ++ * @return ++ */ ++ 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)); ++ } ++ ++ /** ++ * Get's the distance squared between 2 entities ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distanceSq(Entity e1, Entity e2) { ++ return distanceSq(e1.getX(),e1.getY(),e1.getZ(), e2.getX(),e2.getY(),e2.getZ()); ++ } ++ ++ /** ++ * Gets the distance sqaured between 2 block positions ++ * @param pos1 ++ * @param pos2 ++ * @return ++ */ ++ public static double distanceSq(BlockPos pos1, BlockPos pos2) { ++ return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ()); ++ } ++ ++ /** ++ * Gets the distance squared between 2 positions ++ * @param x1 ++ * @param y1 ++ * @param z1 ++ * @param x2 ++ * @param y2 ++ * @param z2 ++ * @return ++ */ ++ 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); ++ } ++ ++ /** ++ * Converts a NMS World/BlockPosition to Bukkit Location ++ * @param world ++ * @param x ++ * @param y ++ * @param z ++ * @return ++ */ ++ public static Location toLocation(Level world, double x, double y, double z) { ++ return new Location(world.getWorld(), x, y, z); ++ } ++ ++ /** ++ * Converts a NMS World/BlockPosition to Bukkit Location ++ * @param world ++ * @param pos ++ * @return ++ */ ++ public static Location toLocation(Level world, BlockPos pos) { ++ return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ /** ++ * Converts an NMS entity's current location to a Bukkit Location ++ * @param entity ++ * @return ++ */ ++ public static Location toLocation(Entity entity) { ++ return new Location(entity.getCommandSenderWorld().getWorld(), entity.getX(), entity.getY(), entity.getZ()); ++ } ++ ++ public static org.bukkit.block.Block toBukkitBlock(Level world, BlockPos pos) { ++ return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ public static BlockPos toBlockPosition(Location loc) { ++ return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); ++ } ++ ++ public static boolean isEdgeOfChunk(BlockPos pos) { ++ final int modX = pos.getX() & 15; ++ final int modZ = pos.getZ() & 15; ++ return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15); ++ } ++ ++ /** ++ * Posts a task to be executed asynchronously ++ * @param run ++ */ ++ public static void scheduleAsyncTask(Runnable run) { ++ asyncExecutor.execute(run); ++ } ++ ++ @Nonnull ++ public static ServerLevel getNMSWorld(@Nonnull org.bukkit.World world) { ++ return ((CraftWorld) world).getHandle(); ++ } ++ ++ public static ServerLevel getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { ++ return getNMSWorld(entity.getWorld()); ++ } ++ ++ public static ClipContext.Fluid getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) { ++ if (fluidMode == TargetBlockInfo.FluidMode.NEVER) { ++ return ClipContext.Fluid.NONE; ++ } ++ if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) { ++ return ClipContext.Fluid.SOURCE_ONLY; ++ } ++ if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) { ++ return ClipContext.Fluid.ANY; ++ } ++ return null; ++ } ++ ++ public static BlockFace toBukkitBlockFace(Direction enumDirection) { ++ switch (enumDirection) { ++ case DOWN: ++ return BlockFace.DOWN; ++ case UP: ++ return BlockFace.UP; ++ case NORTH: ++ return BlockFace.NORTH; ++ case SOUTH: ++ return BlockFace.SOUTH; ++ case WEST: ++ return BlockFace.WEST; ++ case EAST: ++ return BlockFace.EAST; ++ default: ++ return null; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 37a51dee4cd37844e80fdd5c9853947201151dfc..2406879e76a110e96a4753e66366432a4bc52d9b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -882,6 +882,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop CHUNK_STATUSES = ChunkStatus.getStatusList(); + private static final ChunkHolder.FullChunkStatus[] FULL_CHUNK_STATUSES = ChunkHolder.FullChunkStatus.values(); + private final AtomicReferenceArray>> futures; +- private volatile CompletableFuture> fullChunkFuture; +- private volatile CompletableFuture> tickingChunkFuture; +- private volatile CompletableFuture> entityTickingChunkFuture; ++ private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage + private CompletableFuture chunkToSave; + public int oldTicketLevel; + private int ticketLevel; +@@ -63,6 +63,8 @@ public class ChunkHolder { + private boolean wasAccessibleSinceLastSave; + private boolean resendLight; + ++ private final ChunkMap chunkMap; // Paper ++ + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +@@ -78,10 +80,49 @@ public class ChunkHolder { + this.ticketLevel = this.oldTicketLevel; + this.queueLevel = this.oldTicketLevel; + this.setTicketLevel(level); ++ this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper ++ } ++ ++ // Paper start ++ @Nullable ++ public final LevelChunk getEntityTickingChunk() { ++ CompletableFuture> completablefuture = this.entityTickingChunkFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ @Nullable ++ public final LevelChunk getTickingChunk() { ++ CompletableFuture> completablefuture = this.tickingChunkFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ @Nullable ++ public final LevelChunk getFullReadyChunk() { ++ CompletableFuture> completablefuture = this.fullChunkFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ public final boolean isEntityTickingReady() { ++ return this.isEntityTickingReady; ++ } ++ ++ public final boolean isTickingReady() { ++ return this.isTickingReady; ++ } ++ ++ public final boolean isFullChunkReady() { ++ return this.isFullChunkReady; + } ++ // Paper end + + // CraftBukkit start +- public LevelChunk getFullChunk() { ++ public final LevelChunk getFullChunk() { // Paper - final for inline + if (!getFullChunkStatus(this.oldTicketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks + return this.getFullChunkUnchecked(); + } +@@ -92,6 +133,14 @@ public class ChunkHolder { + return (either == null) ? null : (LevelChunk) either.left().orElse(null); + } + // CraftBukkit end ++ // Paper start - "real" get full chunk immediately ++ public final LevelChunk getFullChunkIfCached() { ++ // Note: Copied from above without ticket level check ++ CompletableFuture> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); ++ Either either = (Either) statusFuture.getNow(null); ++ return either == null ? null : (LevelChunk) either.left().orElse(null); ++ } ++ // Paper end + + public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { + CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex()); +@@ -103,20 +152,23 @@ public class ChunkHolder { + return getStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; + } + +- public CompletableFuture> getTickingChunkFuture() { ++ public final CompletableFuture> getTickingFuture() { return this.getTickingChunkFuture(); } // Paper - OBFHELPER ++ public final CompletableFuture> getTickingChunkFuture() { // Paper - final for inline + return this.tickingChunkFuture; + } + +- public CompletableFuture> getEntityTickingChunkFuture() { ++ public final CompletableFuture> getEntityTickingFuture() { return this.getEntityTickingChunkFuture(); } // Paper - OBFHELPER ++ public final CompletableFuture> getEntityTickingChunkFuture() { // Paper - final for inline + return this.entityTickingChunkFuture; + } + +- public CompletableFuture> getFullChunkFuture() { ++ public final CompletableFuture> getFullChunkFuture() { return this.getFullChunkFuture(); } // Paper - OBFHELPER ++ public final CompletableFuture> getFullChunkFuture() { // Paper - final for inline + return this.fullChunkFuture; + } + + @Nullable +- public LevelChunk getTickingChunk() { ++ public final LevelChunk getTickingChunk() { // Paper - final for inline + CompletableFuture> completablefuture = this.getTickingChunkFuture(); + Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error + +@@ -141,7 +193,7 @@ public class ChunkHolder { + return null; + } + +- public CompletableFuture getChunkToSave() { ++ public final CompletableFuture getChunkToSave() { // Paper - final for inline + return this.chunkToSave; + } + +@@ -282,11 +334,11 @@ public class ChunkHolder { + }); + } + +- public ChunkPos getPos() { ++ public final ChunkPos getPos() { // Paper - final for inline + return this.pos; + } + +- public int getTicketLevel() { ++ public final int getTicketLevel() { // Paper - final for inline + return this.ticketLevel; + } + +@@ -357,13 +409,27 @@ public class ChunkHolder { + + this.wasAccessibleSinceLastSave |= flag3; + if (!flag2 && flag3) { +- this.fullChunkFuture = chunkStorage.unpackTicks(this); ++ // Paper start - cache ticking ready status ++ int expectCreateCount = ++this.fullChunkCreateCount; ++ this.fullChunkFuture = chunkStorage.unpackTicks(this); this.fullChunkFuture.thenAccept((either) -> { ++ if (either.left().isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ LevelChunk fullChunk = either.left().get(); ++ ChunkHolder.this.isFullChunkReady = true; ++ fullChunk.playerChunk = ChunkHolder.this; ++ ++ ++ } ++ }); ++ // Paper end + this.updateChunkToSave(this.fullChunkFuture); + } + + if (flag2 && !flag3) { + completablefuture = this.fullChunkFuture; + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; ++ ++this.fullChunkCreateCount; // Paper - cache ticking ready status ++ this.isFullChunkReady = false; // Paper - cache ticking ready status + this.updateChunkToSave(((CompletableFuture>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error + chunkStorage.getClass(); + return either1.ifLeft(chunkStorage::packTicks); +@@ -374,12 +440,24 @@ public class ChunkHolder { + boolean flag5 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.TICKING); + + if (!flag4 && flag5) { +- this.tickingChunkFuture = chunkStorage.postProcess(this); ++ // Paper start - cache ticking ready status ++ this.tickingChunkFuture = chunkStorage.postProcess(this); this.tickingChunkFuture.thenAccept((either) -> { ++ if (either.left().isPresent()) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ LevelChunk tickingChunk = either.left().get(); ++ ChunkHolder.this.isTickingReady = true; ++ ++ ++ ++ ++ } ++ }); ++ // Paper end + this.updateChunkToSave(this.tickingChunkFuture); + } + + if (flag4 && !flag5) { +- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); ++ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage + this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } + +@@ -391,12 +469,24 @@ public class ChunkHolder { + throw (IllegalStateException) Util.pauseInIde((Throwable) (new IllegalStateException())); + } + +- this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); ++ // Paper start - cache ticking ready status ++ this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); this.entityTickingChunkFuture.thenAccept((either) -> { ++ if (either.left().isPresent()) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ LevelChunk entityTickingChunk = either.left().get(); ++ ChunkHolder.this.isEntityTickingReady = true; ++ ++ ++ ++ ++ } ++ }); ++ // Paper end + this.updateChunkToSave(this.entityTickingChunkFuture); + } + + if (flag6 && !flag7) { +- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); ++ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index aaa56d121cdb66066e869b7cbdc2c863974d51b0..d9d76d2ee43adc2b46d49f7fc3d9aef6af95e465 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -54,6 +54,7 @@ import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; + import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; + import net.minecraft.network.protocol.game.DebugPackets; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.progress.ChunkProgressListener; + import net.minecraft.util.CsvOutput; + import net.minecraft.util.Mth; +@@ -144,6 +145,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }; + // CraftBukkit end + ++ // Paper start - distance maps ++ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); ++ ++ void addPlayerToDistanceMaps(ServerPlayer player) { ++ int chunkX = MCUtil.getChunkCoordinate(player.getX()); ++ int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); ++ // Note: players need to be explicitly added to distance maps before they can be updated ++ } ++ ++ void removePlayerFromDistanceMaps(ServerPlayer player) { ++ ++ } ++ ++ void updateMaps(ServerPlayer player) { ++ int chunkX = MCUtil.getChunkCoordinate(player.getX()); ++ int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); ++ // Note: players need to be explicitly added to distance maps before they can be updated ++ } ++ // Paper end ++ + public ChunkMap(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager definedstructuremanager, Executor workerExecutor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.getDimensionPath(worldserver.dimension()), "region"), dataFixer, flag); + this.visibleChunkMap = this.updatingChunkMap.clone(); +@@ -233,6 +254,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }; + } + ++ // Paper start ++ public final int getEffectiveViewDistance() { ++ // TODO this needs to be checked on update ++ // Mojang currently sets it to +1 of the configured view distance. So subtract one to get the one we really want. ++ return this.viewDistance - 1; ++ } ++ // Paper end ++ + private CompletableFuture, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkPos centerChunk, int margin, IntFunction distanceToStatus) { + List>> list = Lists.newArrayList(); + int j = centerChunk.x; +@@ -951,6 +980,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (!flag1) { + this.distanceManager.addPlayer(SectionPos.of((Entity) player), player); + } ++ this.addPlayerToDistanceMaps(player); // Paper - distance maps + } else { + SectionPos sectionposition = player.getLastSectionPos(); + +@@ -958,6 +988,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (!flag2) { + this.distanceManager.removePlayer(sectionposition, player); + } ++ this.removePlayerFromDistanceMaps(player); // Paper - distance maps + } + + for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { +@@ -1068,6 +1099,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + ++ this.updateMaps(player); // Paper - distance maps ++ + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 7cc5070f70a4f740add9d971385ceaa4d44275a2..ef336645f3bd7a86129ad1dff7e1a15dc93d1e3e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -42,6 +42,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.level.storage.LevelStorageSource; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + + public class ServerChunkCache extends ChunkSource { + +@@ -49,7 +50,7 @@ public class ServerChunkCache extends ChunkSource { + private final DistanceManager distanceManager; + public final ChunkGenerator generator; + private final ServerLevel level; +- private final Thread mainThread; ++ public final Thread mainThread; // Paper - private -> public + private final ThreadedLevelLightEngine lightEngine; + private final ServerChunkCache.MainThreadExecutor mainThreadProcessor; + public final ChunkMap chunkMap; +@@ -62,6 +63,158 @@ public class ServerChunkCache extends ChunkSource { + private final ChunkAccess[] lastChunk = new ChunkAccess[4]; + @Nullable + private NaturalSpawner.SpawnState lastSpawnState; ++ // Paper start ++ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); ++ final Long2ObjectOpenHashMap loadedChunkMap = new Long2ObjectOpenHashMap<>(8192, 0.5f); ++ ++ private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; ++ ++ private static int getChunkCacheKey(int x, int z) { ++ return x & 3 | ((z & 3) << 2); ++ } ++ ++ public void addLoadedChunk(LevelChunk chunk) { ++ this.loadedChunkMapSeqLock.acquireWrite(); ++ try { ++ this.loadedChunkMap.put(chunk.coordinateKey, chunk); ++ } finally { ++ this.loadedChunkMapSeqLock.releaseWrite(); ++ } ++ ++ // rewrite cache if we have to ++ // we do this since we also cache null chunks ++ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); ++ ++ this.lastLoadedChunks[cacheKey] = chunk; ++ } ++ ++ public void removeLoadedChunk(LevelChunk chunk) { ++ this.loadedChunkMapSeqLock.acquireWrite(); ++ try { ++ this.loadedChunkMap.remove(chunk.coordinateKey); ++ } finally { ++ this.loadedChunkMapSeqLock.releaseWrite(); ++ } ++ ++ // rewrite cache if we have to ++ // we do this since we also cache null chunks ++ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); ++ ++ LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; ++ if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { ++ this.lastLoadedChunks[cacheKey] = null; ++ } ++ } ++ ++ public final LevelChunk getChunkAtIfLoadedMainThread(int x, int z) { ++ int cacheKey = getChunkCacheKey(x, z); ++ ++ LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; ++ if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) { ++ return this.lastLoadedChunks[cacheKey]; ++ } ++ ++ long chunkKey = ChunkPos.asLong(x, z); ++ ++ cachedChunk = this.loadedChunkMap.get(chunkKey); ++ // Skipping a null check to avoid extra instructions to improve inline capability ++ this.lastLoadedChunks[cacheKey] = cachedChunk; ++ return cachedChunk; ++ } ++ ++ public final LevelChunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { ++ return this.loadedChunkMap.get(ChunkPos.asLong(x, z)); ++ } ++ ++ public final LevelChunk getChunkAtMainThread(int x, int z) { ++ LevelChunk ret = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ret != null) { ++ return ret; ++ } ++ return (LevelChunk)this.getChunk(x, z, ChunkStatus.FULL, true); ++ } ++ ++ private long chunkFutureAwaitCounter; ++ ++ public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.mainThread) { ++ this.mainThreadProcessor.execute(() -> { ++ ServerChunkCache.this.getEntityTickingChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 31, ChunkHolder::getEntityTickingFuture, onLoad); ++ } ++ ++ public void getTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.mainThread) { ++ this.mainThreadProcessor.execute(() -> { ++ ServerChunkCache.this.getTickingChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 32, ChunkHolder::getTickingFuture, onLoad); ++ } ++ ++ public void getFullChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.mainThread) { ++ this.mainThreadProcessor.execute(() -> { ++ ServerChunkCache.this.getFullChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 33, ChunkHolder::getFullChunkFuture, onLoad); ++ } ++ ++ private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, Function>> futureGet, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.mainThread) { ++ throw new IllegalStateException(); ++ } ++ ChunkPos chunkPos = new ChunkPos(x, z); ++ Long identifier = this.chunkFutureAwaitCounter++; ++ this.distanceManager.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ this.runDistanceManagerUpdates(); ++ ++ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()); ++ ++ if (chunk == null) { ++ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.level.getWorld().getName() + "'"); ++ } ++ ++ CompletableFuture> future = futureGet.apply(chunk); ++ ++ future.whenCompleteAsync((either, throwable) -> { ++ try { ++ if (throwable != null) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", throwable); ++ } else if (either.right().isPresent()) { ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "': " + either.right().get().toString()); ++ } ++ ++ try { ++ if (onLoad != null) { ++ chunkMap.callbackExecutor.execute(() -> { ++ onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. ++ }); ++ } ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", thr); ++ return; ++ } ++ } finally { ++ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. ++ ServerChunkCache.this.distanceManager.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ ServerChunkCache.this.distanceManager.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ } ++ }, this.mainThreadProcessor); ++ } ++ // Paper end + + public ServerChunkCache(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager structureManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, boolean flag, ChunkProgressListener worldloadlistener, Supplier supplier) { + this.level = worldserver; +@@ -123,6 +276,49 @@ public class ServerChunkCache extends ChunkSource { + this.lastChunk[0] = chunk; + } + ++ // Paper start - "real" get chunk if loaded ++ // Note: Partially copied from the getChunkAt method below ++ @Nullable ++ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) { ++ long k = ChunkPos.asLong(x, z); ++ ++ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe ++ ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k); ++ if (playerChunk == null) { ++ return null; ++ } ++ ++ return playerChunk.getFullChunkIfCached(); ++ } ++ ++ @Nullable ++ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { ++ long k = ChunkPos.asLong(x, z); ++ ++ if (Thread.currentThread() == this.mainThread) { ++ return this.getChunkAtIfLoadedMainThread(x, z); ++ } ++ ++ LevelChunk ret = null; ++ long readlock; ++ do { ++ readlock = this.loadedChunkMapSeqLock.acquireRead(); ++ try { ++ ret = this.loadedChunkMap.get(k); ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // re-try, this means a CME occurred... ++ continue; ++ } ++ } while (!this.loadedChunkMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ // Paper end ++ + @Nullable + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { +@@ -406,10 +602,9 @@ public class ServerChunkCache extends ChunkSource { + + this.lastSpawnState = spawnercreature_d; + this.level.getProfiler().pop(); +- List list = Lists.newArrayList(this.chunkMap.getChunks()); +- +- Collections.shuffle(list); +- list.forEach((playerchunk) -> { ++ //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper ++ //Collections.shuffle(list); // Paper ++ this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no... + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + + if (optional.isPresent()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 23506a8903ce64fbfe849bb94e589bdbb6e61a74..34ed8f0d348e7bc2339660ebc6490057ba9ef214 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.longs.LongSets; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.ObjectIterator; + import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + import java.io.BufferedWriter; +@@ -163,7 +164,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + private final Map entitiesByUuid = Maps.newHashMap(); + private final Queue toAddAfterTick = Queues.newArrayDeque(); + private final List players = Lists.newArrayList(); +- private final ServerChunkCache chunkSource; ++ public final ServerChunkCache chunkSource; // Paper - public + boolean tickingEntities; + private final MinecraftServer server; + public final PrimaryLevelData worldDataServer; // CraftBukkit - type +@@ -1682,7 +1683,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + ObjectIterator objectiterator = spawnercreature_d.getMobCategoryCounts().object2IntEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.objects.Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry) objectiterator.next(); ++ Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (Object2IntMap.Entry) objectiterator.next(); // Paper - decompile fix + + bufferedwriter.write(String.format("spawn_count.%s: %d\n", ((MobCategory) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey()).getName(), it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue())); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d62c74318ac3bb139570fe2b5f12cdb6e900d70d..8d7fa186b0b47688f2822038b46c33b3f32d28ae 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -221,6 +221,8 @@ public class ServerPlayer extends Player implements ContainerListener { + public Integer clientViewDistance; + // CraftBukkit end + ++ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper ++ + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); + this.respawnDimension = Level.OVERWORLD; +@@ -233,6 +235,8 @@ public class ServerPlayer extends Player implements ContainerListener { + this.fudgeSpawnLocation(world); + this.textFilter = server.createTextFilterForPlayer(this); + ++ this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper ++ + // CraftBukkit start + this.displayName = this.getScoreboardName(); + this.canPickUpLoot = true; +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 34dd10908101a6709e389bfa1d1c2a6599e8b102..cf3ced15c9a87e7a4dbccba17c57a7b32b77566c 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -25,6 +25,7 @@ public class TicketType { + public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public static final TicketType PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit ++ public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper + + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 0a9f76b7aef5c12879a408954384b4e70ac5b484..fdc56d602ef0bf3c50842f3081a3e6523b9765b0 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -141,6 +141,26 @@ public class WorldGenRegion implements WorldGenLevel { + return chunkX >= this.firstPos.x && chunkX <= this.lastPos.x && chunkZ >= this.firstPos.z && chunkZ <= this.lastPos.z; + } + ++ // Paper start - if loaded util ++ @Nullable ++ @Override ++ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return this.getChunk(x, z, ChunkStatus.FULL, false); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getBlockState(blockposition); ++ } ++ ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluidState(blockposition); ++ } ++ // Paper end ++ + @Override + public BlockState getBlockState(BlockPos pos) { + return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4).getBlockState(pos); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8766e79c1ed7a8557ef5fd91e8f3c65c3467efcd..35f3940cebb00ee29da54b1ee148ee931fa11636 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -218,9 +218,9 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private final MinecraftServer server; + public ServerPlayer player; + private int tickCount; +- private long keepAliveTime; +- private boolean keepAlivePending; +- private long keepAliveChallenge; ++ private long keepAliveTime; private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER ++ private boolean keepAlivePending; private void setPendingPing(boolean isPending) { this.keepAlivePending = isPending;}; private boolean isPendingPing() { return this.keepAlivePending;}; // Paper - OBFHELPER ++ private long keepAliveChallenge; private void setKeepAliveID(long keepAliveID) { this.keepAliveChallenge = keepAliveID;}; private long getKeepAliveID() {return this.keepAliveChallenge; }; // Paper - OBFHELPER + // CraftBukkit start - multithreaded fields + private volatile int chatSpamTickCount; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(ServerGamePacketListenerImpl.class, "chatThrottle"); +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index 497e23e4ebf0d8b4e1a90ea3909608cdc066265f..97bde5f8402452e59b0da94edfe1b970cdb86748 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -84,6 +84,7 @@ public class BitStorage { + return (int) (k >> l & this.mask); + } + ++ public final long[] getDataBits() { return this.getRaw(); } // Paper - OBFHELPER + public long[] getRaw() { + return this.data; + } +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 03831adce7905916423d8c3834c42c90f3a1ca8f..e48fcfe2e4ff151258ae1d84cc0995d2cd54e9a6 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -68,6 +68,15 @@ public abstract class BlockableEventLoop implements Processo + + } + ++ // Paper start ++ public void scheduleOnMain(Runnable r0) { ++ // postToMainThread does not work the same as older versions of mc ++ // This method is actually used to create a TickTask, which can then be posted onto main ++ this.addTask(this.wrapRunnable(r0)); ++ } ++ // Paper end ++ ++ public final void addTask(R r0) { tell(r0); }; // Paper - OBFHELPER + public void tell(R r0) { + this.pendingRunnables.add(r0); + LockSupport.unpark(this.getRunningThread()); +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index ff482d0349c18d0d1ba902ea0d10611b1ca4e588..102298d57cf3143092d04ab1d5d0d69b28d696ea 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -3,6 +3,7 @@ package net.minecraft.world.entity; + import com.google.common.collect.ImmutableSet; + import java.util.Optional; + import java.util.Set; // Paper ++import java.util.Map; // Paper + import java.util.UUID; + import java.util.function.Function; + import java.util.stream.Stream; +@@ -441,8 +442,8 @@ public class EntityType { + return this.dimensions.height; + } + +- @Nullable +- public T create(Level world) { ++ public T create(Level world) { return this.create(world); } // Paper - OBFHELPER ++ @Nullable public T create(Level world) { // Paper - OBFHELPER + return this.factory.create(this, world); + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 3245c0d7310285dd423c5c1d44c26fb8c3d7bf7f..9e5dde73bd65bf4e51352c628fba024c36e29ef1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -228,6 +228,7 @@ public abstract class LivingEntity extends Entity { + public boolean collides = true; + public Set collidableExemptions = new HashSet<>(); + public boolean canPickUpLoot; ++ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper + + @Override + public float getBukkitYaw() { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index f268b7e6f89068267ea2f07f7505e0dc69968b73..99cb4dc1a1009d4a29e651c94d21babcc61388ed 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -223,6 +223,7 @@ public abstract class Mob extends LivingEntity { + return this.target; + } + ++ public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper + public void setTarget(@Nullable LivingEntity target) { + // CraftBukkit start - fire event + setGoalTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index d090ffcf08e32a08d4b815b79ed58fc00bc26fd0..920ae9af8985705a0ada7da5b7085a1ed8ca7f27 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -12,6 +12,8 @@ import org.bukkit.event.entity.EntityUnleashEvent; + + public abstract class PathfinderMob extends Mob { + ++ public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper ++ + protected PathfinderMob(EntityType type, Level world) { + super(type, world); + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java +index fa886a89195312acfe53605169216ce95642ba87..407b7168b7e8d4408824039c06d02792d3c7e534 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java +@@ -27,6 +27,7 @@ import net.minecraft.world.level.ServerLevelAccessor; + + public abstract class Monster extends PathfinderMob implements Enemy { + ++ public org.bukkit.craftbukkit.entity.CraftMonster getBukkitMonster() { return (org.bukkit.craftbukkit.entity.CraftMonster) super.getBukkitEntity(); } // Paper + protected Monster(EntityType type, Level world) { + super(type, world); + this.xpReward = 5; +diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java +index c0e0a8deabf3dffcc974f9e5bea2332d0d6e57a5..7d06838c4c8eef7ebff64ae094cd12404b9edd53 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java ++++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java +@@ -37,7 +37,7 @@ public class Inventory implements Container, Nameable { + public final NonNullList items; + public final NonNullList armor; + public final NonNullList offhand; +- private final List> compartments; ++ private final List> compartments; public final List> getComponents() { return compartments; } // Paper - OBFHELPER + public int selected; + public final Player player; + private ItemStack carried; +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index da0fabafad0aa6c124abf52f8da68c73b7264fe9..2a6a6e291efbd7cc8fed6532f18321bd141e1306 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -101,7 +101,7 @@ public final class ItemStack { + })).apply(instance, ItemStack::new); + }); + private static final Logger LOGGER = LogManager.getLogger(); +- public static final ItemStack EMPTY = new ItemStack((Item) null); ++ public static final ItemStack EMPTY = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = EMPTY; // Paper - OBFHELPER + public static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = (DecimalFormat) Util.make((new DecimalFormat("#.##")), (decimalformat) -> { // CraftBukkit - decompile error + decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); + }); +@@ -658,6 +658,24 @@ public final class ItemStack { + return this.tag != null ? this.tag.getList("Enchantments", 10) : new ListTag(); + } + ++ // Paper start - (this is just a good no conflict location) ++ public org.bukkit.inventory.ItemStack asBukkitMirror() { ++ return CraftItemStack.asCraftMirror(this); ++ } ++ public org.bukkit.inventory.ItemStack asBukkitCopy() { ++ return CraftItemStack.asCraftMirror(this.copy()); ++ } ++ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) { ++ return CraftItemStack.asNMSCopy(itemstack); ++ } ++ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack; ++ public org.bukkit.inventory.ItemStack getBukkitStack() { ++ if (bukkitStack == null || bukkitStack.getHandle() != this) { ++ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); ++ } ++ return bukkitStack; ++ } ++ // Paper end + public void setTag(@Nullable CompoundTag tag) { + this.tag = tag; + if (this.getItem().canBeDepleted()) { +@@ -756,6 +774,7 @@ public final class ItemStack { + return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; + } + ++ public void getOrCreateTagAndSet(String s, Tag nbtbase) { addTagElement(s, nbtbase);} // Paper - OBFHELPER + public void addTagElement(String key, Tag tag) { + this.getOrCreateTag().put(key, tag); + } +@@ -841,6 +860,7 @@ public final class ItemStack { + // CraftBukkit start + @Deprecated + public void setItem(Item item) { ++ this.bukkitStack = null; // Paper + this.item = item; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java b/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java +index e38c38c7d7679f267283a5ac6245e2261829aa05..2285a0cf6a35d06de77f94ad8a14ae91c1a7929f 100644 +--- a/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java ++++ b/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java +@@ -121,6 +121,7 @@ public class PotionUtils { + return compound == null ? Potions.EMPTY : Potion.byName(compound.getString("Potion")); + } + ++ public static ItemStack addPotionToItemStack(ItemStack itemstack, Potion potionregistry) { return setPotion(itemstack, potionregistry); } // Paper - OBFHELPER + public static ItemStack setPotion(ItemStack stack, Potion potion) { + ResourceLocation minecraftkey = Registry.POTION.getKey(potion); + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 3bbee667743d9a249c1513ea426e7a51d9185c4c..2feb187f62be5cf5d354a1e806087417cc189ab1 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -8,9 +8,11 @@ import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.util.Mth; ++import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.level.material.Material; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; +@@ -22,6 +24,19 @@ public interface BlockGetter { + BlockEntity getBlockEntity(BlockPos pos); + + BlockState getBlockState(BlockPos pos); ++ // Paper start - if loaded util ++ BlockState getTypeIfLoaded(BlockPos blockposition); ++ default Material getMaterialIfLoaded(BlockPos blockposition) { ++ BlockState type = this.getTypeIfLoaded(blockposition); ++ return type == null ? null : type.getMaterial(); ++ } ++ ++ default Block getBlockIfLoaded(BlockPos blockposition) { ++ BlockState type = this.getTypeIfLoaded(blockposition); ++ return type == null ? null : type.getBlock(); ++ } ++ FluidState getFluidIfLoaded(BlockPos blockposition); ++ // Paper end + + FluidState getFluidState(BlockPos pos); + +diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +index 0caf067f9d888f9769db1503284d444e97c60c9c..7ccf830146c252cff8e22553d293e02d4b53dad8 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkPos.java ++++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +@@ -12,27 +12,32 @@ public class ChunkPos { + public static final long INVALID_CHUNK_POS = asLong(1875016, 1875016); + public final int x; + public final int z; ++ public final long longKey; // Paper + + public ChunkPos(int x, int z) { + this.x = x; + this.z = z; ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public ChunkPos(BlockPos pos) { + this.x = pos.getX() >> 4; + this.z = pos.getZ() >> 4; ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public ChunkPos(long pos) { + this.x = (int) pos; + this.z = (int) (pos >> 32); ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public long toLong() { +- return asLong(this.x, this.z); ++ return longKey; // Paper + } + +- public static long asLong(int chunkX, int chunkZ) { ++ public static long pair(final BlockPos pos) { return asLong(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER ++ public static long asLong(int chunkX, int chunkZ) { + return (long) chunkX & 4294967295L | ((long) chunkZ & 4294967295L) << 32; + } + +diff --git a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java +index 82c14e1ac66792137ab9e647c868a698c1ec9929..2b4f9849d668dede4d1f7d10f3a6ec9ef7a6d828 100644 +--- a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java +@@ -20,6 +20,18 @@ public enum EmptyBlockGetter implements BlockGetter { + return null; + } + ++ // Paper start - If loaded util ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return this.getFluidState(blockposition); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ return this.getBlockState(blockposition); ++ } ++ // Paper end ++ + @Override + public BlockState getBlockState(BlockPos pos) { + return Blocks.AIR.defaultBlockState(); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index f08de81dcc4acd5a3e44407b431ce827a19b2e9c..5cc4c2668df72f83fb1526f4586b71d2ae0103dc 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -74,6 +74,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.block.CapturedBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockState; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.event.block.BlockPhysicsEvent; + // CraftBukkit end +@@ -255,17 +256,50 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return y < 0 || y >= 256; + } + +- public LevelChunk getChunkAt(BlockPos pos) { ++ public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline + return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4); + } + + @Override +- public LevelChunk getChunk(int chunkX, int chunkZ) { +- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL); ++ public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline ++ return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump ++ } ++ ++ // Paper start - if loaded ++ @Nullable ++ @Override ++ public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z); + } + + @Override +- public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { ++ public final BlockState getTypeIfLoaded(BlockPos blockposition) { ++ // CraftBukkit start - tree generation ++ if (captureTreeGeneration) { ++ CraftBlockState previous = capturedBlockStates.get(blockposition); ++ if (previous != null) { ++ return previous.getHandle(); ++ } ++ } ++ // CraftBukkit end ++ if (!isInWorldBounds(blockposition)) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getBlockState(blockposition); ++ } ++ ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getFluidState(blockposition); ++ } ++ // Paper end ++ ++ @Override ++ public final ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { // Paper - final for inline + ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); + + if (ichunkaccess == null && create) { +@@ -276,7 +310,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + @Override +- public boolean setBlock(BlockPos pos, BlockState state, int flags) { ++ public final boolean setBlock(BlockPos pos, BlockState state, int flags) { // Paper - final for inline + return this.setBlock(pos, state, flags, 512); + } + +@@ -422,8 +456,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {} + +- @Override +- public boolean removeBlock(BlockPos pos, boolean move) { ++ public boolean setAir(BlockPos blockposition) { return this.removeBlock(blockposition, false); } // Paper - OBFHELPER ++ public boolean setAir(BlockPos blockposition, boolean moved) { return this.removeBlock(blockposition, moved); } // Paper - OBFHELPER ++ @Override public boolean removeBlock(BlockPos pos, boolean move) { // Paper - OBFHELPER + FluidState fluid = this.getFluidState(pos); + + return this.setBlock(pos, fluid.createLegacyBlock(), 3 | (move ? 64 : 0)); +@@ -569,7 +604,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + if (isOutsideBuildHeight(pos)) { + return Blocks.VOID_AIR.defaultBlockState(); + } else { +- LevelChunk chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4); ++ LevelChunk chunk = (LevelChunk) this.getChunkSource().getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine + + return chunk.getBlockState(pos); + } +diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java +index b6877b78bf6ecf2069e59028f8910826c8b4eafe..6e4a53291ee87fac6d81d4ab9746906b6ae1b453 100644 +--- a/src/main/java/net/minecraft/world/level/LevelReader.java ++++ b/src/main/java/net/minecraft/world/level/LevelReader.java +@@ -18,6 +18,7 @@ import net.minecraft.world.phys.AABB; + + public interface LevelReader extends BlockAndTintGetter, CollisionGetter, BiomeManager.NoiseBiomeSource { + ++ @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) + @Nullable + ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); + +diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java +index f1d759d87291f469bde3b433031a6e7c6a19fbf2..6db3f4efa6ea4a09aad7684a3b7cc7479fad2f5c 100644 +--- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java ++++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java +@@ -4,6 +4,7 @@ import java.util.function.Predicate; + import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.BlockEntity; +@@ -23,7 +24,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { + protected final int centerZ; + protected final ChunkAccess[][] chunks; + protected boolean allEmpty; +- protected final Level level; ++ protected final Level level; protected final Level getWorld() { return level; } // Paper - OBFHELPER + + public PathNavigationRegion(Level world, BlockPos minPos, BlockPos maxPos) { + this.level = world; +@@ -42,7 +43,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { + + for (k = this.centerX; k <= i; ++k) { + for (l = this.centerZ; l <= j; ++l) { +- this.chunks[k - this.centerX][l - this.centerZ] = ichunkprovider.getChunkNow(k, l); ++ this.chunks[k - this.centerX][l - this.centerZ] = ((ServerLevel)world).getChunkSource().getChunkAtIfLoadedMainThreadNoCache(k, l); // Paper + } + } + +@@ -67,7 +68,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { + int k = i - this.centerX; + int l = j - this.centerZ; + +- if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { ++ if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { // Paper - if this changes, update getChunkIfLoaded below + ChunkAccess ichunkaccess = this.chunks[k][l]; + + return (ChunkAccess) (ichunkaccess != null ? ichunkaccess : new EmptyLevelChunk(this.level, new ChunkPos(i, j))); +@@ -86,6 +87,29 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { + return this.getChunk(chunkX, chunkZ); + } + ++ // Paper start - if loaded util ++ private ChunkAccess getChunkIfLoaded(int x, int z) { ++ int k = x - this.centerX; ++ int l = z - this.centerZ; ++ ++ if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { ++ return this.chunks[k][l]; ++ } ++ return null; ++ } ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluidState(blockposition); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getBlockState(blockposition); ++ } ++ // Paper end ++ + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index c9713d1ab6498c09790503f673b31b5ef30ce4f3..e6928557a79f51302975f2832ec911c2692eaaeb 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -687,6 +687,7 @@ public abstract class BlockBehaviour { + return this.cache != null ? this.cache.isCollisionShapeFullBlock : Block.isShapeFullBlock(this.getCollisionShape(world, pos)); + } + ++ public final BlockState getBlockData() { return asState(); } // Paper - OBFHELPER + protected abstract BlockState asState(); + + public boolean requiresCorrectToolForDrops() { +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index fe1c10e5eeb434cd24e94b3247abbf5f73fce9cc..31f17956b3b031d1a47bda4d282554c8a7853097 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -47,6 +47,7 @@ public class WorldBorder { + return this.getDistanceToBorder(entity.getX(), entity.getZ()); + } + ++ public final VoxelShape asVoxelShape(){ return getCollisionShape();} // Paper - OBFHELPER + public VoxelShape getCollisionShape() { + return this.extent.getCollisionShape(); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 5a91d74f0acf393dbf098719b0924a4c00cf7128..e2c5a17aa72d1a5412d76881187d4d9ad1763297 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -59,7 +59,7 @@ public class LevelChunk implements ChunkAccess { + + private static final Logger LOGGER = LogManager.getLogger(); + @Nullable +- public static final LevelChunkSection EMPTY_SECTION = null; ++ public static final LevelChunkSection EMPTY_SECTION = null; public static final LevelChunkSection EMPTY_CHUNK_SECTION = EMPTY_SECTION; // Paper - OBFHELPER + private final LevelChunkSection[] sections; + private ChunkBiomeContainer biomes; + private final Map pendingBlockEntities; +@@ -82,7 +82,7 @@ public class LevelChunk implements ChunkAccess { + private Supplier fullStatus; + @Nullable + private Consumer postLoad; +- private final ChunkPos chunkPos; ++ private final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key + private volatile boolean isLightCorrect; + + public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes) { +@@ -99,7 +99,8 @@ public class LevelChunk implements ChunkAccess { + this.postProcessing = new ShortList[16]; + this.entitySlices = (List[]) (new List[16]); // Spigot + this.world = (ServerLevel) world; // CraftBukkit - type +- this.chunkPos = pos; ++ this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field look ups ++ this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key + this.upgradeData = upgradeData; + Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); + int j = aheightmap_type.length; +@@ -145,6 +146,110 @@ public class LevelChunk implements ChunkAccess { + public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); + // CraftBukkit end + ++ // Paper start ++ public final com.destroystokyo.paper.util.maplist.EntityList entities = new com.destroystokyo.paper.util.maplist.EntityList(); ++ public ChunkHolder playerChunk; ++ ++ static final int NEIGHBOUR_CACHE_RADIUS = 3; ++ public static int getNeighbourCacheRadius() { ++ return NEIGHBOUR_CACHE_RADIUS; ++ } ++ ++ boolean loadedTicketLevel; ++ private long neighbourChunksLoadedBitset; ++ private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; ++ ++ private static int getNeighbourIndex(final int relativeX, final int relativeZ) { ++ // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) ++ // optimised variant of the above by moving some of the ops to compile time ++ return relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))); ++ } ++ ++ public final LevelChunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) { ++ return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)]; ++ } ++ ++ public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) { ++ return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0; ++ } ++ ++ public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final LevelChunk chunk) { ++ if (chunk == null) { ++ throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.chunkPos); ++ } ++ final long before = this.neighbourChunksLoadedBitset; ++ final int index = getNeighbourIndex(relativeX, relativeZ); ++ this.loadedNeighbourChunks[index] = chunk; ++ this.neighbourChunksLoadedBitset |= (1L << index); ++ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); ++ } ++ ++ public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) { ++ final long before = this.neighbourChunksLoadedBitset; ++ final int index = getNeighbourIndex(relativeX, relativeZ); ++ this.loadedNeighbourChunks[index] = null; ++ this.neighbourChunksLoadedBitset &= ~(1L << index); ++ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); ++ } ++ ++ public final void resetNeighbours() { ++ final long before = this.neighbourChunksLoadedBitset; ++ this.neighbourChunksLoadedBitset = 0L; ++ java.util.Arrays.fill(this.loadedNeighbourChunks, null); ++ this.onNeighbourChange(before, 0L); ++ } ++ ++ protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { ++ ++ } ++ ++ public final boolean isAnyNeighborsLoaded() { ++ return neighbourChunksLoadedBitset != 0; ++ } ++ public final boolean areNeighboursLoaded(final int radius) { ++ return LevelChunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius); ++ } ++ ++ public static boolean areNeighboursLoaded(final long bitset, final int radius) { ++ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) ++ switch (radius) { ++ case 0: { ++ return (bitset & (1L << getNeighbourIndex(0, 0))) != 0; ++ } ++ case 1: { ++ long mask = 0L; ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ mask |= (1L << getNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ case 2: { ++ long mask = 0L; ++ for (int dx = -2; dx <= 2; ++dx) { ++ for (int dz = -2; dz <= 2; ++dz) { ++ mask |= (1L << getNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ case 3: { ++ long mask = 0L; ++ for (int dx = -3; dx <= 3; ++dx) { ++ for (int dz = -3; dz <= 3; ++dz) { ++ mask |= (1L << getNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ ++ default: ++ throw new IllegalArgumentException("Radius not recognized: " + radius); ++ } ++ } ++ // Paper end ++ + public LevelChunk(Level world, ProtoChunk protoChunk) { + this(world, protoChunk.getPos(), protoChunk.getBiomes(), protoChunk.getUpgradeData(), protoChunk.getBlockTicks(), protoChunk.getLiquidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), (Consumer) null); + Iterator iterator = protoChunk.getEntities().iterator(); +@@ -250,6 +355,18 @@ public class LevelChunk implements ChunkAccess { + } + } + ++ // Paper start - If loaded util ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return this.getFluidState(blockposition); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ return this.getBlockState(blockposition); ++ } ++ // Paper end ++ + @Override + public FluidState getFluidState(BlockPos pos) { + return this.getFluidState(pos.getX(), pos.getY(), pos.getZ()); +@@ -390,6 +507,7 @@ public class LevelChunk implements ChunkAccess { + entity.xChunk = this.chunkPos.x; + entity.yChunk = k; + entity.zChunk = this.chunkPos.z; ++ this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); + } + +@@ -413,6 +531,7 @@ public class LevelChunk implements ChunkAccess { + } + + this.entitySlices[section].remove(entity); ++ this.entities.remove(entity); // Paper + } + + @Override +@@ -434,6 +553,7 @@ public class LevelChunk implements ChunkAccess { + return this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + } + ++ @Nullable public final BlockEntity getTileEntityImmediately(BlockPos pos) { return this.getBlockEntity(pos, EntityCreationType.IMMEDIATE); } // Paper - OBFHELPER + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { + // CraftBukkit start +@@ -545,7 +665,25 @@ public class LevelChunk implements ChunkAccess { + + // CraftBukkit start + public void loadCallback() { ++ // Paper start - neighbour cache ++ int chunkX = this.chunkPos.x; ++ int chunkZ = this.chunkPos.z; ++ ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { ++ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { ++ LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); ++ if (neighbour != null) { ++ neighbour.setNeighbourLoaded(-dx, -dz, this); ++ // should be in cached already ++ this.setNeighbourLoaded(dx, dz, neighbour); ++ } ++ } ++ } ++ this.setNeighbourLoaded(0, 0, this); ++ this.loadedTicketLevel = true; ++ // Paper end - neighbour cache + org.bukkit.Server server = this.world.getCraftServer(); ++ ((ServerLevel)this.world).getChunkSource().addLoadedChunk(this); // Paper + if (server != null) { + /* + * If it's a new world, the first few chunks are generated inside +@@ -584,6 +722,22 @@ public class LevelChunk implements ChunkAccess { + server.getPluginManager().callEvent(unloadEvent); + // note: saving can be prevented, but not forced if no saving is actually required + this.mustNotSave = !unloadEvent.isSaveChunk(); ++ ((ServerLevel)this.world).getChunkSource().removeLoadedChunk(this); // Paper ++ // Paper start - neighbour cache ++ int chunkX = this.chunkPos.x; ++ int chunkZ = this.chunkPos.z; ++ ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { ++ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { ++ LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); ++ if (neighbour != null) { ++ neighbour.setNeighbourUnloaded(-dx, -dz); ++ } ++ } ++ } ++ this.loadedTicketLevel = false; ++ this.resetNeighbours(); ++ // Paper end + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 8a4fca9f4882e65b831dd3f82f242e1113859fe0..b54d82e0f41a03c91e0de8df8249a91da3c04d0e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -139,6 +139,7 @@ public class LevelChunkSection { + return this.states; + } + ++ public void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER + public void write(FriendlyByteBuf packetdataserializer) { + packetdataserializer.writeShort(this.nonEmptyBlockCount); + this.states.write(packetdataserializer); +diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java +index dcc030f4801b4c6e1fe5b8c2718ddfd7ba6bb248..78c56e1e2af50e923fb0b07c6ddd860c4aa77195 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Palette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java +@@ -7,10 +7,12 @@ import net.minecraft.network.FriendlyByteBuf; + + public interface Palette { + ++ default int getOrCreateIdFor(T object) { return this.idFor(object); } // Paper - OBFHELPER + int idFor(T object); + + boolean maybeHas(Predicate predicate); + ++ @Nullable default T getObject(int dataBits) { return this.valueFor(dataBits); } // Paper - OBFHELPER + @Nullable + T valueFor(int index); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index f14f89f8916c832feaa3887bd28a5cf6b2f6ff1d..d4db27421736f665739436c1ac4d3c6d5cae95cd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -19,7 +19,7 @@ import net.minecraft.util.Mth; + + public class PalettedContainer implements PaletteResize { + +- private final Palette globalPalette; ++ private final Palette globalPalette; private final Palette getDataPaletteGlobal() { return this.globalPalette; } // Paper - OBFHELPER + private final PaletteResize dummyPaletteResize = (i, object) -> { + return 0; + }; +@@ -27,9 +27,9 @@ public class PalettedContainer implements PaletteResize { + private final Function reader; + private final Function writer; + private final T defaultValue; +- protected BitStorage storage; +- private Palette palette; +- private int bits; ++ protected BitStorage storage; public final BitStorage getDataBits() { return this.storage; } // Paper - OBFHELPER ++ private Palette palette; private Palette getDataPalette() { return this.palette; } // Paper - OBFHELPER ++ private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER + private final ReentrantLock lock = new ReentrantLock(); + + public void acquire() { +@@ -64,6 +64,7 @@ public class PalettedContainer implements PaletteResize { + return y << 8 | z << 4 | x; + } + ++ private void initialize(int bitsPerObject) { this.setBits(bitsPerObject); } // Paper - OBFHELPER + private void setBits(int size) { + if (size != this.bits) { + this.bits = size; +@@ -141,6 +142,7 @@ public class PalettedContainer implements PaletteResize { + return t0 == null ? this.defaultValue : t0; + } + ++ public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER + public void write(FriendlyByteBuf buf) { + this.acquire(); + buf.writeByte(this.bits); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index cecf8a68f215d85e84fba157930f6987ffd21e50..7cd3f89004b0a64772fc3dfbdd132ba5a850b63e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -99,6 +99,18 @@ public class ProtoChunk implements ChunkAccess { + + } + ++ // Paper start - If loaded util ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return this.getFluidState(blockposition); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ return this.getBlockState(blockposition); ++ } ++ // Paper end ++ + @Override + public BlockState getBlockState(BlockPos pos) { + int i = pos.getY(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +index 7de765786b3504dcffab98bb0d9dac64b30b3325..5bd34b136f2892f541ba686debca19e0a4eef0be 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -27,7 +27,7 @@ public class IOWorker implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + private final AtomicBoolean shutdownRequested = new AtomicBoolean(); + private final ProcessorMailbox mailbox; +- private final RegionFileStorage storage; ++ private final RegionFileStorage storage;public RegionFileStorage getRegionFileCache() { return storage; } // Paper - OBFHELPER + private final Map pendingWrites = Maps.newLinkedHashMap(); + + protected IOWorker(File file, boolean flag, String s) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index aa3d6db08e4d744cc94de71d0f8dceb99948e2ab..60f410a4f838048bbfd2cde52caa7c4c9434b0ba 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -112,6 +112,7 @@ public class RegionFile implements AutoCloseable { + return this.externalFileDir.resolve(s); + } + ++ @Nullable public synchronized DataInputStream getReadStream(ChunkPos chunkCoordIntPair) throws IOException { return getChunkDataInputStream(chunkCoordIntPair);} // Paper - OBFHELPER + @Nullable + public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { + int i = this.getOffset(pos); +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index 022fafff8b476f8bc1830bf5494760b0fef65297..983d0495ec35128ca3ef68566ada065bc4b21efc 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -194,10 +194,12 @@ public class AABB { + return this.move(vec3d.x, vec3d.y, vec3d.z); + } + ++ public final boolean intersects(AABB axisalignedbb) { return this.intersects(axisalignedbb); } // Paper - OBFHELPER + public boolean intersects(AABB box) { + return this.intersects(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); + } + ++ public final boolean intersects(double d0, double d1, double d2, double d3, double d4, double d5) { return intersects(d0, d1, d2, d3, d4, d5); } // Paper - OBFHELPER + public boolean intersects(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ; + } +@@ -210,6 +212,7 @@ public class AABB { + return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY && z >= this.minZ && z < this.maxZ; + } + ++ public final double getAverageSideLength(){return getSize();} // Paper - OBFHELPER + public double getSize() { + double d0 = this.getXsize(); + double d1 = this.getYsize(); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index eb07309be171ccadcae21f4096c44d2b700d22b3..2371b52b450e2b43fa9b9549a91f853c702a9dc0 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -31,10 +31,12 @@ public final class Shapes { + public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); + ++ public static final VoxelShape empty() {return empty();} // Paper - OBFHELPER + public static VoxelShape empty() { + return Shapes.EMPTY; + } + ++ public static final VoxelShape fullCube() {return block();} // Paper - OBFHELPER + public static VoxelShape block() { + return Shapes.BLOCK; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index c34f63eaf3deca4623ca4dfbee863771014847ba..01df5263d77771a296ca091a0feec620e6e37229 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -85,6 +85,7 @@ public final class CraftItemStack extends ItemStack { + } + + net.minecraft.world.item.ItemStack handle; ++ public net.minecraft.world.item.ItemStack getHandle() { return handle; } // Paper + + /** + * Mirror +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 9ad17c560c8d99a396543ab9f97c34de648f6544..4bf48f77f3f7cd62a91590543f5af441c8268029 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -43,6 +43,7 @@ import org.bukkit.scheduler.BukkitWorker; + */ + public class CraftScheduler implements BukkitScheduler { + ++ static Plugin MINECRAFT = new MinecraftInternalPlugin(); + /** + * Counter for IDs. Order doesn't matter, only uniqueness. + */ +@@ -177,6 +178,11 @@ public class CraftScheduler implements BukkitScheduler { + runTaskTimer(plugin, (Object) task, delay, period); + } + ++ public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) { ++ final CraftTask task = new CraftTask(run, nextId(), taskName); ++ return handle(task, delay); ++ } ++ + public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) { + validate(plugin, runnable); + if (delay < 0L) { +@@ -400,13 +406,20 @@ public class CraftScheduler implements BukkitScheduler { + task.run(); + task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { +- task.getOwner().getLogger().log( ++ // Paper start ++ String msg = String.format( ++ "Task #%s for %s generated an exception", ++ task.getTaskId(), ++ task.getOwner().getDescription().getFullName()); ++ if (task.getOwner() == MINECRAFT) { ++ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); ++ } else { ++ task.getOwner().getLogger().log( + Level.WARNING, +- String.format( +- "Task #%s for %s generated an exception", +- task.getTaskId(), +- task.getOwner().getDescription().getFullName()), ++ msg, + throwable); ++ } ++ // Paper end + } finally { + currentTask = null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 3c7066192ea4c05c101404bb56cbc839771f4200..09aa6809c5400ce8548ac902908b750ce7c964ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -39,6 +39,21 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + CraftTask(final Object task) { + this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } ++ // Paper start ++ public String taskName = null; ++ boolean internal = false; ++ CraftTask(final Object task, int id, String taskName) { ++ this.rTask = (Runnable) task; ++ this.cTask = null; ++ this.plugin = CraftScheduler.MINECRAFT; ++ this.taskName = taskName; ++ this.internal = true; ++ this.id = id; ++ this.period = CraftTask.NO_REPEATING; ++ this.taskName = taskName; ++ this.timings = null; // Will be changed in later patch ++ } ++ // Paper end + + CraftTask(final Plugin plugin, final Object task, final int id, final long period) { + this.plugin = plugin; +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +new file mode 100644 +index 0000000000000000000000000000000000000000..49dc0c441b9dd7e7745cf15ced67f383ebee1f99 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +@@ -0,0 +1,132 @@ ++package org.bukkit.craftbukkit.scheduler; ++ ++ ++import org.bukkit.Server; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.configuration.file.FileConfiguration; ++import org.bukkit.generator.ChunkGenerator; ++import org.bukkit.plugin.PluginBase; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.PluginLogger; ++ ++import java.io.File; ++import java.io.InputStream; ++import java.util.List; ++ ++public class MinecraftInternalPlugin extends PluginBase { ++ private boolean enabled = true; ++ ++ private final String pluginName; ++ private PluginDescriptionFile pdf; ++ ++ public MinecraftInternalPlugin() { ++ this.pluginName = "Minecraft"; ++ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); ++ } ++ ++ public void setEnabled(boolean enabled) { ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public File getDataFolder() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginDescriptionFile getDescription() { ++ return pdf; ++ } ++ ++ @Override ++ public FileConfiguration getConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public InputStream getResource(String filename) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveDefaultConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveResource(String resourcePath, boolean replace) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void reloadConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLogger getLogger() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLoader getPluginLoader() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public Server getServer() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ @Override ++ public void onDisable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void onLoad() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void onEnable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isNaggable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void setNaggable(boolean canNag) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index 33cd17c415ae19bc9028934257b396907995cb9a..40a2ad3e180cc50a755f44a8ff6d8261734bf733 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -168,7 +168,23 @@ public class DummyGeneratorAccess implements LevelAccessor { + public FluidState getFluidState(BlockPos pos) { + throw new UnsupportedOperationException("Not supported yet."); + } ++ // Paper start - if loaded util ++ @javax.annotation.Nullable ++ @Override ++ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ @Override ++ public BlockState getTypeIfLoaded(BlockPos blockposition) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } + ++ @Override ++ public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Paper end + @Override + public WorldBorder getWorldBorder() { + throw new UnsupportedOperationException("Not supported yet."); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +index 1aec70a1f1a9d8fd2cd06bde4033e19e769ab331..f72c13bedaa6fa45e26f5dcad564835bdd4af61f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +@@ -17,7 +17,7 @@ import java.util.RandomAccess; + public class UnsafeList extends AbstractList implements List, RandomAccess, Cloneable, Serializable { + private static final long serialVersionUID = 8683452581112892191L; + +- private transient Object[] data; ++ private transient Object[] data; public final Object[] getRawDataArray() { return this.data; } // Paper - expose for raw get + private int size; + private int initialCapacity; + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 8d6f4d76d6f04a322a98faecaca6b1b69c5f49d6..dc11dab14624ca25e78bf0b919ecf461e0be430d 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -118,7 +118,11 @@ public class SpigotConfig + } + } + } +- ++ // Paper start ++ SpigotConfig.save(); ++ } ++ public static void save() { ++ // Paper end + try + { + config.save( CONFIG_FILE ); diff --git a/Remapped-Spigot-Server-Patches/0005-Paper-Metrics.patch b/Remapped-Spigot-Server-Patches/0005-Paper-Metrics.patch new file mode 100644 index 000000000..82a9c9a2d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0005-Paper-Metrics.patch @@ -0,0 +1,735 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 24 Mar 2017 23:56:01 -0500 +Subject: [PATCH] Paper Metrics + +Removes Spigot's mcstats metrics in favor of a system using bStats + +To disable for privacy or other reasons go to the bStats folder in your plugins folder +and edit the config.yml file present there. + +Please keep in mind the data collected is anonymous and collection should have no +tangible effect on server performance. The data is used to allow the authors of +PaperMC to track version and platform usage so that we can make better management +decisions on behalf of the project. + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b9e689d57705965721b5c55bc45d36657f360e4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -0,0 +1,670 @@ ++package com.destroystokyo.paper; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.plugin.Plugin; ++ ++import org.json.simple.JSONArray; ++import org.json.simple.JSONObject; ++ ++import javax.net.ssl.HttpsURLConnection; ++import java.io.ByteArrayOutputStream; ++import java.io.DataOutputStream; ++import java.io.File; ++import java.io.IOException; ++import java.net.URL; ++import java.util.*; ++import java.util.concurrent.Callable; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ScheduledExecutorService; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import java.util.zip.GZIPOutputStream; ++ ++/** ++ * bStats collects some data for plugin authors. ++ * ++ * Check out https://bStats.org/ to learn more about bStats! ++ */ ++public class Metrics { ++ ++ // Executor service for requests ++ // We use an executor service because the Bukkit scheduler is affected by server lags ++ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); ++ ++ // The version of this bStats class ++ public static final int B_STATS_VERSION = 1; ++ ++ // The url to which the data is sent ++ private static final String URL = "https://bStats.org/submitData/server-implementation"; ++ ++ // Should failed requests be logged? ++ private static boolean logFailedRequests = false; ++ ++ // The logger for the failed requests ++ private static Logger logger = Logger.getLogger("bStats"); ++ ++ // The name of the server software ++ private final String name; ++ ++ // The uuid of the server ++ private final String serverUUID; ++ ++ // A list with all custom charts ++ private final List charts = new ArrayList<>(); ++ ++ /** ++ * Class constructor. ++ * ++ * @param name The name of the server software. ++ * @param serverUUID The uuid of the server. ++ * @param logFailedRequests Whether failed requests should be logged or not. ++ * @param logger The logger for the failed requests. ++ */ ++ public Metrics(String name, String serverUUID, boolean logFailedRequests, Logger logger) { ++ this.name = name; ++ this.serverUUID = serverUUID; ++ Metrics.logFailedRequests = logFailedRequests; ++ Metrics.logger = logger; ++ ++ // Start submitting the data ++ startSubmitting(); ++ } ++ ++ /** ++ * Adds a custom chart. ++ * ++ * @param chart The chart to add. ++ */ ++ public void addCustomChart(CustomChart chart) { ++ if (chart == null) { ++ throw new IllegalArgumentException("Chart cannot be null!"); ++ } ++ charts.add(chart); ++ } ++ ++ /** ++ * Starts the Scheduler which submits our data every 30 minutes. ++ */ ++ private void startSubmitting() { ++ final Runnable submitTask = this::submitData; ++ ++ // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the ++ // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. ++ // WARNING: You must not modify any part of this Metrics class, including the submit delay or frequency! ++ // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! ++ long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); ++ long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); ++ scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); ++ scheduler.scheduleAtFixedRate(submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); ++ } ++ ++ /** ++ * Gets the plugin specific data. ++ * ++ * @return The plugin specific data. ++ */ ++ private JSONObject getPluginData() { ++ JSONObject data = new JSONObject(); ++ ++ data.put("pluginName", name); // Append the name of the server software ++ JSONArray customCharts = new JSONArray(); ++ for (CustomChart customChart : charts) { ++ // Add the data of the custom charts ++ JSONObject chart = customChart.getRequestJsonObject(); ++ if (chart == null) { // If the chart is null, we skip it ++ continue; ++ } ++ customCharts.add(chart); ++ } ++ data.put("customCharts", customCharts); ++ ++ return data; ++ } ++ ++ /** ++ * Gets the server specific data. ++ * ++ * @return The server specific data. ++ */ ++ private JSONObject getServerData() { ++ // OS specific data ++ String osName = System.getProperty("os.name"); ++ String osArch = System.getProperty("os.arch"); ++ String osVersion = System.getProperty("os.version"); ++ int coreCount = Runtime.getRuntime().availableProcessors(); ++ ++ JSONObject data = new JSONObject(); ++ ++ data.put("serverUUID", serverUUID); ++ ++ data.put("osName", osName); ++ data.put("osArch", osArch); ++ data.put("osVersion", osVersion); ++ data.put("coreCount", coreCount); ++ ++ return data; ++ } ++ ++ /** ++ * Collects the data and sends it afterwards. ++ */ ++ private void submitData() { ++ final JSONObject data = getServerData(); ++ ++ JSONArray pluginData = new JSONArray(); ++ pluginData.add(getPluginData()); ++ data.put("plugins", pluginData); ++ ++ try { ++ // We are still in the Thread of the timer, so nothing get blocked :) ++ sendData(data); ++ } catch (Exception e) { ++ // Something went wrong! :( ++ if (logFailedRequests) { ++ logger.log(Level.WARNING, "Could not submit stats of " + name, e); ++ } ++ } ++ } ++ ++ /** ++ * Sends the data to the bStats server. ++ * ++ * @param data The data to send. ++ * @throws Exception If the request failed. ++ */ ++ private static void sendData(JSONObject data) throws Exception { ++ if (data == null) { ++ throw new IllegalArgumentException("Data cannot be null!"); ++ } ++ HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); ++ ++ // Compress the data to save bandwidth ++ byte[] compressedData = compress(data.toString()); ++ ++ // Add headers ++ connection.setRequestMethod("POST"); ++ connection.addRequestProperty("Accept", "application/json"); ++ connection.addRequestProperty("Connection", "close"); ++ connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request ++ connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); ++ connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format ++ connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); ++ ++ // Send data ++ connection.setDoOutput(true); ++ DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); ++ outputStream.write(compressedData); ++ outputStream.flush(); ++ outputStream.close(); ++ ++ connection.getInputStream().close(); // We don't care about the response - Just send our data :) ++ } ++ ++ /** ++ * Gzips the given String. ++ * ++ * @param str The string to gzip. ++ * @return The gzipped String. ++ * @throws IOException If the compression failed. ++ */ ++ private static byte[] compress(final String str) throws IOException { ++ if (str == null) { ++ return null; ++ } ++ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ++ GZIPOutputStream gzip = new GZIPOutputStream(outputStream); ++ gzip.write(str.getBytes("UTF-8")); ++ gzip.close(); ++ return outputStream.toByteArray(); ++ } ++ ++ /** ++ * Represents a custom chart. ++ */ ++ public static abstract class CustomChart { ++ ++ // The id of the chart ++ final String chartId; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ */ ++ CustomChart(String chartId) { ++ if (chartId == null || chartId.isEmpty()) { ++ throw new IllegalArgumentException("ChartId cannot be null or empty!"); ++ } ++ this.chartId = chartId; ++ } ++ ++ private JSONObject getRequestJsonObject() { ++ JSONObject chart = new JSONObject(); ++ chart.put("chartId", chartId); ++ try { ++ JSONObject data = getChartData(); ++ if (data == null) { ++ // If the data is null we don't send the chart. ++ return null; ++ } ++ chart.put("data", data); ++ } catch (Throwable t) { ++ if (logFailedRequests) { ++ logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); ++ } ++ return null; ++ } ++ return chart; ++ } ++ ++ protected abstract JSONObject getChartData() throws Exception; ++ ++ } ++ ++ /** ++ * Represents a custom simple pie. ++ */ ++ public static class SimplePie extends CustomChart { ++ ++ private final Callable callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SimplePie(String chartId, Callable callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ String value = callable.call(); ++ if (value == null || value.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("value", value); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom advanced pie. ++ */ ++ public static class AdvancedPie extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public AdvancedPie(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue() == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ values.put(entry.getKey(), entry.getValue()); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom drilldown pie. ++ */ ++ public static class DrilldownPie extends CustomChart { ++ ++ private final Callable>> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public DrilldownPie(String chartId, Callable>> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ public JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map> map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean reallyAllSkipped = true; ++ for (Map.Entry> entryValues : map.entrySet()) { ++ JSONObject value = new JSONObject(); ++ boolean allSkipped = true; ++ for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { ++ value.put(valueEntry.getKey(), valueEntry.getValue()); ++ allSkipped = false; ++ } ++ if (!allSkipped) { ++ reallyAllSkipped = false; ++ values.put(entryValues.getKey(), value); ++ } ++ } ++ if (reallyAllSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom single line chart. ++ */ ++ public static class SingleLineChart extends CustomChart { ++ ++ private final Callable callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SingleLineChart(String chartId, Callable callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ int value = callable.call(); ++ if (value == 0) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("value", value); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom multi line chart. ++ */ ++ public static class MultiLineChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public MultiLineChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue() == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ values.put(entry.getKey(), entry.getValue()); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom simple bar chart. ++ */ ++ public static class SimpleBarChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SimpleBarChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ for (Map.Entry entry : map.entrySet()) { ++ JSONArray categoryValues = new JSONArray(); ++ categoryValues.add(entry.getValue()); ++ values.put(entry.getKey(), categoryValues); ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom advanced bar chart. ++ */ ++ public static class AdvancedBarChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public AdvancedBarChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue().length == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ JSONArray categoryValues = new JSONArray(); ++ for (int categoryValue : entry.getValue()) { ++ categoryValues.add(categoryValue); ++ } ++ values.put(entry.getKey(), categoryValues); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ static class PaperMetrics { ++ static void startMetrics() { ++ // Get the config file ++ File configFile = new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats"), "config.yml"); ++ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); ++ ++ // Check if the config file exists ++ if (!config.isSet("serverUuid")) { ++ ++ // Add default values ++ config.addDefault("enabled", true); ++ // Every server gets it's unique random id. ++ config.addDefault("serverUuid", UUID.randomUUID().toString()); ++ // Should failed request be logged? ++ config.addDefault("logFailedRequests", false); ++ ++ // Inform the server owners about bStats ++ config.options().header( ++ "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + ++ "To honor their work, you should not disable it.\n" + ++ "This has nearly no effect on the server performance!\n" + ++ "Check out https://bStats.org/ to learn more :)" ++ ).copyDefaults(true); ++ try { ++ config.save(configFile); ++ } catch (IOException ignored) { ++ } ++ } ++ // Load the data ++ String serverUUID = config.getString("serverUuid"); ++ boolean logFailedRequests = config.getBoolean("logFailedRequests", false); ++ // Only start Metrics, if it's enabled in the config ++ if (config.getBoolean("enabled", true)) { ++ Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); ++ ++ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { ++ String minecraftVersion = Bukkit.getVersion(); ++ minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1); ++ return minecraftVersion; ++ })); ++ ++ metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); ++ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() || PaperConfig.isProxyOnlineMode() ? "online" : "offline")); ++ metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); ++ ++ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { ++ Map> map = new HashMap<>(); ++ String javaVersion = System.getProperty("java.version"); ++ Map entry = new HashMap<>(); ++ entry.put(javaVersion, 1); ++ ++ // http://openjdk.java.net/jeps/223 ++ // Java decided to change their versioning scheme and in doing so modified the java.version system ++ // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier ++ // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+ ++ String majorVersion = javaVersion.split("\\.")[0]; ++ String release; ++ ++ int indexOf = javaVersion.lastIndexOf('.'); ++ ++ if (majorVersion.equals("1")) { ++ release = "Java " + javaVersion.substring(0, indexOf); ++ } else { ++ // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it ++ // valid strings for the major may potentially include values such as -ea to deannotate a pre release ++ Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); ++ if (versionMatcher.find()) { ++ majorVersion = versionMatcher.group(0); ++ } ++ release = "Java " + majorVersion; ++ } ++ map.put(release, entry); ++ ++ return map; ++ })); ++ ++ metrics.addCustomChart(new Metrics.DrilldownPie("legacy_plugins", () -> { ++ Map> map = new HashMap<>(); ++ ++ // count legacy plugins ++ int legacy = 0; ++ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { ++ if (CraftMagicNumbers.isLegacy(plugin.getDescription())) { ++ legacy++; ++ } ++ } ++ ++ // insert real value as lower dimension ++ Map entry = new HashMap<>(); ++ entry.put(String.valueOf(legacy), 1); ++ ++ // create buckets as higher dimension ++ if (legacy == 0) { ++ map.put("0 \uD83D\uDE0E", entry); // :sunglasses: ++ } else if (legacy <= 5) { ++ map.put("1-5", entry); ++ } else if (legacy <= 10) { ++ map.put("6-10", entry); ++ } else if (legacy <= 25) { ++ map.put("11-25", entry); ++ } else if (legacy <= 50) { ++ map.put("26-50", entry); ++ } else { ++ map.put("50+ \uD83D\uDE2D", entry); // :cry: ++ } ++ ++ return map; ++ })); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 2c0514892d3993bef57ecf677cf8bb0fbe0216e4..da922f395f0fff0881ead893c900c5b2623f48f0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -42,6 +42,7 @@ public class PaperConfig { + private static boolean verbose; + private static boolean fatalError; + /*========================================================================*/ ++ private static boolean metricsStarted; + + public static void init(File configFile) { + CONFIG_FILE = configFile; +@@ -84,6 +85,11 @@ public class PaperConfig { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); + } ++ ++ if (!metricsStarted) { ++ Metrics.PaperMetrics.startMetrics(); ++ metricsStarted = true; ++ } + } + + static void readConfig(Class clazz, Object instance) { +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index dc11dab14624ca25e78bf0b919ecf461e0be430d..0083f979933d4a9035efb992ab0a2f250a56a979 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -83,6 +83,7 @@ public class SpigotConfig + MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() ); + } + ++ /* // Paper - Replace with our own + if ( metrics == null ) + { + try +@@ -94,6 +95,7 @@ public class SpigotConfig + Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex ); + } + } ++ */ // Paper end + } + + static void readConfig(Class clazz, Object instance) diff --git a/Remapped-Spigot-Server-Patches/0006-Add-MinecraftKey-Information-to-Objects.patch b/Remapped-Spigot-Server-Patches/0006-Add-MinecraftKey-Information-to-Objects.patch new file mode 100644 index 000000000..91f564fb1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0006-Add-MinecraftKey-Information-to-Objects.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 01:40:13 -0400 +Subject: [PATCH] Add MinecraftKey Information to Objects + +Stores the reference to the objects respective MinecraftKey + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index d05eeaa711a09bb121b530654821894e795ff4ea..e95b91cefb0374bd5bb57cc090f5ecd566d7a618 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -208,7 +208,7 @@ public class PaperCommand extends Command { + + Collection entities = world.entitiesById.values(); + entities.forEach(e -> { +- ResourceLocation key = new ResourceLocation(""); // TODO: update in next patch ++ ResourceLocation key = e.getMinecraftKey(); + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkPos chunk = new ChunkPos(e.xChunk, e.zChunk); +diff --git a/src/main/java/net/minecraft/server/KeyedObject.java b/src/main/java/net/minecraft/server/KeyedObject.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3c9933050ca0a7453ba7950cb3cf4cc8b5b7081d +--- /dev/null ++++ b/src/main/java/net/minecraft/server/KeyedObject.java +@@ -0,0 +1,11 @@ ++package net.minecraft.server; ++ ++import net.minecraft.resources.ResourceLocation; ++ ++public interface KeyedObject { ++ ResourceLocation getMinecraftKey(); ++ default String getMinecraftKeyString() { ++ ResourceLocation key = getMinecraftKey(); ++ return key != null ? key.toString() : null; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c7008fcbd4d805fe0e743f1d4ad948dcd86ceae3..48c9d2b7d56832ebd13749a394b8b715f0b1704d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -135,7 +135,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; + import org.bukkit.plugin.PluginManager; + // CraftBukkit end + +-public abstract class Entity implements Nameable, CommandSource { ++public abstract class Entity implements Nameable, CommandSource, net.minecraft.server.KeyedObject { // Paper + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; +@@ -1761,12 +1761,31 @@ public abstract class Entity implements Nameable, CommandSource { + return true; + } + ++ // Paper start ++ private ResourceLocation entityKey; ++ private String entityKeyString; ++ ++ @Override ++ public ResourceLocation getMinecraftKey() { ++ if (entityKey == null) { ++ this.entityKey = EntityType.getKey(this.getType()); ++ this.entityKeyString = this.entityKey != null ? this.entityKey.toString() : null; ++ } ++ return entityKey; ++ } ++ ++ @Override ++ public String getMinecraftKeyString() { ++ getMinecraftKey(); // Try to load if it doesn't exists. see: https://github.com/PaperMC/Paper/issues/1280 ++ return entityKeyString; ++ } + @Nullable + public final String getEncodeId() { + EntityType entitytypes = this.getType(); + ResourceLocation minecraftkey = EntityType.getKey(entitytypes); + +- return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null; ++ return entitytypes != null && entitytypes.isPersistable() ? getMinecraftKeyString() : null; ++ // Paper end + } + + protected abstract void readAdditionalSaveData(CompoundTag tag); +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 102298d57cf3143092d04ab1d5d0d69b28d696ea..2cb86de4bfc87a709f0cfa2c4e550d8e7928a3f0 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -384,6 +384,7 @@ public class EntityType { + } + } + ++ public boolean isPersistable() { return canSerialize(); } // Paper - OBFHELPER + public boolean canSerialize() { + return this.serialize; + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 8928a1ae51d24fd15aaae93bc8ea573548f2b012..846fc0f36377337630b2ec2a5f7a5a54c39c2965 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -23,7 +23,7 @@ import org.bukkit.inventory.InventoryHolder; + + import org.spigotmc.CustomTimingsHandler; // Spigot + +-public abstract class BlockEntity { ++public abstract class BlockEntity implements net.minecraft.server.KeyedObject { // Paper + + public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot + // CraftBukkit start - data containers +@@ -31,7 +31,7 @@ public abstract class BlockEntity { + public CraftPersistentDataContainer persistentDataContainer; + // CraftBukkit end + private static final Logger LOGGER = LogManager.getLogger(); +- private final BlockEntityType type; ++ private final BlockEntityType type; public BlockEntityType getTileEntityType() { return type; } // Paper - OBFHELPER + @Nullable + protected Level level; + protected BlockPos worldPosition; +@@ -45,6 +45,26 @@ public abstract class BlockEntity { + this.type = type; + } + ++ // Paper start ++ private String tileEntityKeyString = null; ++ private ResourceLocation tileEntityKey = null; ++ ++ @Override ++ public ResourceLocation getMinecraftKey() { ++ if (tileEntityKey == null) { ++ tileEntityKey = BlockEntityType.getKey(this.getTileEntityType()); ++ tileEntityKeyString = tileEntityKey != null ? tileEntityKey.toString() : null; ++ } ++ return tileEntityKey; ++ } ++ ++ @Override ++ public String getMinecraftKeyString() { ++ getMinecraftKey(); // Try to load if it doesn't exists. ++ return tileEntityKeyString; ++ } ++ // Paper end ++ + @Nullable + public Level getLevel() { + return this.level; diff --git a/Remapped-Spigot-Server-Patches/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch b/Remapped-Spigot-Server-Patches/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch new file mode 100644 index 000000000..11ed8248c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 02:10:36 -0400 +Subject: [PATCH] Store reference to current Chunk for Entity and Block + Entities + +This enables us a fast reference to the entities current chunk instead +of having to look it up by hashmap lookups. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 48c9d2b7d56832ebd13749a394b8b715f0b1704d..b633f6b3a36b793e6dbc1b8b554bfba74c719570 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -260,7 +260,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean isChunkLoaded() { +- return level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); ++ return getCurrentChunk() != null; + } + // CraftBukkit end + +@@ -1762,6 +1762,23 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + // Paper start ++ public java.lang.ref.WeakReference currentChunk = null; ++ ++ public void setCurrentChunk(net.minecraft.world.level.chunk.LevelChunk chunk) { ++ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; ++ } ++ /** ++ * Returns the entities current registered chunk. If the entity is not added to a chunk yet, it will return null ++ */ ++ public net.minecraft.world.level.chunk.LevelChunk getCurrentChunk() { ++ final net.minecraft.world.level.chunk.LevelChunk chunk = currentChunk != null ? currentChunk.get() : null; ++ if (chunk != null && chunk.loaded) { ++ return chunk; ++ } ++ ++ return !inChunk ? null : ((ServerLevel)level).getChunkSource().getChunkAtIfLoadedMainThreadNoCache(xChunk, zChunk); ++ } ++ + private ResourceLocation entityKey; + private String entityKeyString; + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 846fc0f36377337630b2ec2a5f7a5a54c39c2965..bb60c9da9f3ba0d5c5bad22512675ccb841a60e5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -11,6 +11,7 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Mirror; + import net.minecraft.world.level.block.Rotation; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.util.Supplier; +@@ -63,6 +64,15 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + getMinecraftKey(); // Try to load if it doesn't exists. + return tileEntityKeyString; + } ++ ++ private java.lang.ref.WeakReference currentChunk = null; ++ public LevelChunk getCurrentChunk() { ++ final LevelChunk chunk = currentChunk != null ? currentChunk.get() : null; ++ return chunk != null && chunk.loaded ? chunk : null; ++ } ++ public void setCurrentChunk(LevelChunk chunk) { ++ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; ++ } + // Paper end + + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index e2c5a17aa72d1a5412d76881187d4d9ad1763297..ae08fcce66d50d7f61bc3bd4a0e2547d56f53e82 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -89,11 +89,36 @@ public class LevelChunk implements ChunkAccess { + this(world, pos, biomes, UpgradeData.EMPTY, EmptyTickList.empty(), EmptyTickList.empty(), 0L, (LevelChunkSection[]) null, (Consumer) null); + } + ++ // Paper start ++ private class TileEntityHashMap extends java.util.HashMap { ++ @Override ++ public BlockEntity put(BlockPos key, BlockEntity value) { ++ BlockEntity replaced = super.put(key, value); ++ if (replaced != null) { ++ replaced.setCurrentChunk(null); ++ } ++ if (value != null) { ++ value.setCurrentChunk(LevelChunk.this); ++ } ++ return replaced; ++ } ++ ++ @Override ++ public BlockEntity remove(Object key) { ++ BlockEntity removed = super.remove(key); ++ if (removed != null) { ++ removed.setCurrentChunk(null); ++ } ++ return removed; ++ } ++ } ++ // Paper end ++ + public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList blockTickScheduler, TickList fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer loadToWorldConsumer) { + this.sections = new LevelChunkSection[16]; + this.pendingBlockEntities = Maps.newHashMap(); + this.heightmaps = Maps.newEnumMap(Heightmap.Types.class); +- this.blockEntities = Maps.newHashMap(); ++ this.blockEntities = new TileEntityHashMap(); // Paper + this.structureStarts = Maps.newHashMap(); + this.structuresRefences = Maps.newHashMap(); + this.postProcessing = new ShortList[16]; +@@ -504,6 +529,7 @@ public class LevelChunk implements ChunkAccess { + } + + entity.inChunk = true; ++ entity.setCurrentChunk(this); // Paper + entity.xChunk = this.chunkPos.x; + entity.yChunk = k; + entity.zChunk = this.chunkPos.z; +@@ -516,6 +542,7 @@ public class LevelChunk implements ChunkAccess { + ((Heightmap) this.heightmaps.get(type)).setRawData(heightmap); + } + ++ public final void removeEntity(Entity entity) { this.removeEntity(entity); } // Paper - OBFHELPER + public void removeEntity(Entity entity) { + this.removeEntity(entity, entity.yChunk); + } +@@ -530,7 +557,12 @@ public class LevelChunk implements ChunkAccess { + section = this.entitySlices.length - 1; + } + +- this.entitySlices[section].remove(entity); ++ // Paper start ++ if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null); ++ if (!this.entitySlices[section].remove(entity)) { ++ return; ++ } ++ // Paper end + this.entities.remove(entity); // Paper + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 080d3292e03c5a179b9eb89da1550718d263f817..eb61c803cf74c5ca2c51d5027a02ed3db6b53096 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -145,6 +145,7 @@ import net.minecraft.world.entity.vehicle.MinecartHopper; + import net.minecraft.world.entity.vehicle.MinecartSpawner; + import net.minecraft.world.entity.vehicle.MinecartTNT; + import net.minecraft.world.phys.AABB; ++import org.bukkit.Chunk; // Paper + import org.bukkit.EntityEffect; + import org.bukkit.Location; + import org.bukkit.Server; +@@ -186,6 +187,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.entity = entity; + } + ++ @Override ++ public Chunk getChunk() { ++ net.minecraft.world.level.chunk.LevelChunk currentChunk = entity.getCurrentChunk(); ++ return currentChunk != null ? currentChunk.bukkitChunk : getLocation().getChunk(); ++ } ++ + public static CraftEntity getEntity(CraftServer server, Entity entity) { + /* + * Order is *EXTREMELY* important -- keep it right! =D diff --git a/Remapped-Spigot-Server-Patches/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch b/Remapped-Spigot-Server-Patches/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch new file mode 100644 index 000000000..ca1271ce7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 02:13:59 -0400 +Subject: [PATCH] Store counts for each Entity/Block Entity Type + +Opens door for future patches to optimize performance + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index ae08fcce66d50d7f61bc3bd4a0e2547d56f53e82..00ce55c17980da87a3834f952475a766543506b0 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -90,15 +90,19 @@ public class LevelChunk implements ChunkAccess { + } + + // Paper start ++ public final co.aikar.util.Counter entityCounts = new co.aikar.util.Counter<>(); ++ public final co.aikar.util.Counter tileEntityCounts = new co.aikar.util.Counter<>(); + private class TileEntityHashMap extends java.util.HashMap { + @Override + public BlockEntity put(BlockPos key, BlockEntity value) { + BlockEntity replaced = super.put(key, value); + if (replaced != null) { + replaced.setCurrentChunk(null); ++ tileEntityCounts.decrement(replaced.getMinecraftKeyString()); + } + if (value != null) { + value.setCurrentChunk(LevelChunk.this); ++ tileEntityCounts.increment(value.getMinecraftKeyString()); + } + return replaced; + } +@@ -108,6 +112,7 @@ public class LevelChunk implements ChunkAccess { + BlockEntity removed = super.remove(key); + if (removed != null) { + removed.setCurrentChunk(null); ++ tileEntityCounts.decrement(removed.getMinecraftKeyString()); + } + return removed; + } +@@ -528,6 +533,7 @@ public class LevelChunk implements ChunkAccess { + k = this.entitySlices.length - 1; + } + ++ if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper + entity.inChunk = true; + entity.setCurrentChunk(this); // Paper + entity.xChunk = this.chunkPos.x; +@@ -562,6 +568,7 @@ public class LevelChunk implements ChunkAccess { + if (!this.entitySlices[section].remove(entity)) { + return; + } ++ entityCounts.decrement(entity.getMinecraftKeyString()); + // Paper end + this.entities.remove(entity); // Paper + } diff --git a/Remapped-Spigot-Server-Patches/0009-Timings-v2.patch b/Remapped-Spigot-Server-Patches/0009-Timings-v2.patch new file mode 100644 index 000000000..dc683eaee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0009-Timings-v2.patch @@ -0,0 +1,2330 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 04:00:11 -0600 +Subject: [PATCH] Timings v2 + + +diff --git a/src/main/java/SpigotTimings.java b/src/main/java/SpigotTimings.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +new file mode 100644 +index 0000000000000000000000000000000000000000..be3a62f543a5fec4739c14821fe5a443c1fa3f5b +--- /dev/null ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -0,0 +1,152 @@ ++package co.aikar.timings; ++ ++import Timing; ++import com.google.common.collect.MapMaker; ++import net.minecraft.commands.CommandFunction; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.scheduler.BukkitTask; ++ ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++ ++import java.util.Map; ++ ++// TODO: Re-implement missing timers ++public final class MinecraftTimings { ++ ++ public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep"); ++ public static final Timing playerListTimer = Timings.ofSafe("Player List"); ++ public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions"); ++ public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); ++ public static final Timing tickablesTimer = Timings.ofSafe("Tickables"); ++ public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler"); ++ public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler"); ++ public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending"); ++ public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing"); ++ public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick"); ++ public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update"); ++ public static final Timing serverCommandTimer = Timings.ofSafe("Server Command"); ++ public static final Timing savePlayers = Timings.ofSafe("Save Players"); ++ ++ public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity"); ++ public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity"); ++ public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing"); ++ public static final Timing scheduledBlocksTimer = Timings.ofSafe("## Scheduled Blocks"); ++ public static final Timing structureGenerationTimer = Timings.ofSafe("Structure Generation"); ++ ++ public static final Timing processQueueTimer = Timings.ofSafe("processQueue"); ++ public static final Timing processTasksTimer = Timings.ofSafe("processTasks"); ++ ++ public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand"); ++ ++ public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck"); ++ ++ public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); ++ public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); ++ ++ private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); ++ ++ private MinecraftTimings() {} ++ ++ public static Timing getInternalTaskName(String taskName) { ++ return Timings.ofSafe(taskName); ++ } ++ ++ /** ++ * Gets a timer associated with a plugins tasks. ++ * @param bukkitTask ++ * @param period ++ * @return ++ */ ++ public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) { ++ if (!bukkitTask.isSync()) { ++ return NullTimingHandler.NULL; ++ } ++ Plugin plugin; ++ ++ CraftTask craftTask = (CraftTask) bukkitTask; ++ ++ final Class taskClass = craftTask.getTaskClass(); ++ if (bukkitTask.getOwner() != null) { ++ plugin = bukkitTask.getOwner(); ++ } else { ++ plugin = TimingsManager.getPluginByClassloader(taskClass); ++ } ++ ++ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> { ++ try { ++ String clsName = !clazz.isMemberClass() ++ ? clazz.getName() ++ : clazz.getCanonicalName(); ++ if (clsName != null && clsName.contains("$Lambda$")) { ++ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1"); ++ } ++ return clsName != null ? clsName : "UnknownTask"; ++ } catch (Throwable ex) { ++ new Exception("Error occurred detecting class name", ex).printStackTrace(); ++ return "MangledClassFile"; ++ } ++ }); ++ ++ StringBuilder name = new StringBuilder(64); ++ name.append("Task: ").append(taskname); ++ if (period > 0) { ++ name.append(" (interval:").append(period).append(")"); ++ } else { ++ name.append(" (Single)"); ++ } ++ ++ if (plugin == null) { ++ return Timings.ofSafe(null, name.toString()); ++ } ++ ++ return Timings.ofSafe(plugin, name.toString()); ++ } ++ ++ /** ++ * Get a named timer for the specified entity type to track type specific timings. ++ * @param entityType ++ * @return ++ */ ++ public static Timing getEntityTimings(String entityType, String type) { ++ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); ++ } ++ ++ /** ++ * Get a named timer for the specified tile entity type to track type specific timings. ++ * @param entity ++ * @return ++ */ ++ public static Timing getTileEntityTimings(BlockEntity entity) { ++ String entityType = entity.getClass().getName(); ++ return Timings.ofSafe("Minecraft", "## tickTileEntity - " + entityType, tickTileEntityTimer); ++ } ++ public static Timing getCancelTasksTimer() { ++ return Timings.ofSafe("Cancel Tasks"); ++ } ++ public static Timing getCancelTasksTimer(Plugin plugin) { ++ return Timings.ofSafe(plugin, "Cancel Tasks"); ++ } ++ ++ public static void stopServer() { ++ TimingsManager.stopServer(); ++ } ++ ++ public static Timing getBlockTiming(Block block) { ++ return Timings.ofSafe("## Scheduled Block: " + block.toString(), scheduledBlocksTimer); ++ } ++/* ++ public static Timing getStructureTiming(StructureGenerator structureGenerator) { ++ return Timings.ofSafe("Structure Generator - " + structureGenerator.getName(), structureGenerationTimer); ++ }*/ ++ ++ public static Timing getPacketTiming(Packet packet) { ++ return Timings.ofSafe("## Packet - " + packet.getClass().getName(), packetProcessTimer); ++ } ++ ++ public static Timing getCommandFunctionTiming(CommandFunction function) { ++ return Timings.ofSafe("Command Function - " + function.getMinecraftKey().toString()); ++ } ++} +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94adf0275a2e7093c152cc3b8b0a5747b3a13a86 +--- /dev/null ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -0,0 +1,380 @@ ++/* ++ * This file is licensed under the MIT License (MIT). ++ * ++ * Copyright (c) 2014 Daniel Ennis ++ * ++ * 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 co.aikar.timings; ++ ++import com.google.common.collect.Sets; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang.StringUtils; ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.Material; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.MemorySection; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.entity.EntityType; ++import org.json.simple.JSONObject; ++import org.json.simple.JSONValue; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.lang.management.ManagementFactory; ++import java.lang.management.OperatingSystemMXBean; ++import java.lang.management.RuntimeMXBean; ++import java.net.HttpURLConnection; ++import java.net.InetAddress; ++import java.net.URL; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.logging.Level; ++import java.util.zip.GZIPOutputStream; ++ ++import static co.aikar.timings.TimingsManager.HISTORY; ++import static co.aikar.util.JSONUtil.appendObjectData; ++import static co.aikar.util.JSONUtil.createObject; ++import static co.aikar.util.JSONUtil.pair; ++import static co.aikar.util.JSONUtil.toArray; ++import static co.aikar.util.JSONUtil.toArrayMapper; ++import static co.aikar.util.JSONUtil.toObjectMapper; ++ ++import TimingHistory; ++import TimingsReportListener; ++ ++@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) ++public class TimingsExport extends Thread { ++ ++ private final TimingsReportListener listeners; ++ private final Map out; ++ private final TimingHistory[] history; ++ private static long lastReport = 0; ++ ++ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) { ++ super("Timings paste thread"); ++ this.listeners = listeners; ++ this.out = out; ++ this.history = history; ++ } ++ ++ /** ++ * Checks if any pending reports are being requested, and builds one if needed. ++ */ ++ public static void reportTimings() { ++ if (Timings.requestingReport.isEmpty()) { ++ return; ++ } ++ TimingsReportListener listeners = new TimingsReportListener(Timings.requestingReport); ++ listeners.addConsoleIfNeeded(); ++ ++ Timings.requestingReport.clear(); ++ long now = System.currentTimeMillis(); ++ final long lastReportDiff = now - lastReport; ++ if (lastReportDiff < 60000) { ++ listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)"); ++ listeners.done(); ++ return; ++ } ++ final long lastStartDiff = now - TimingsManager.timingStart; ++ if (lastStartDiff < 180000) { ++ listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)"); ++ listeners.done(); ++ return; ++ } ++ listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report..."); ++ lastReport = now; ++ Map parent = createObject( ++ // Get some basic system details about the server ++ pair("version", Bukkit.getVersion()), ++ pair("maxplayers", Bukkit.getMaxPlayers()), ++ pair("start", TimingsManager.timingStart / 1000), ++ pair("end", System.currentTimeMillis() / 1000), ++ pair("online-mode", Bukkit.getServer().getOnlineMode()), ++ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), ++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedIds(), pack -> { ++ // Don't feel like obf helper'ing these, non fatal if its temp missed. ++ return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true))); ++ })) ++ ); ++ if (!TimingsManager.privacy) { ++ appendObjectData(parent, ++ pair("server", Bukkit.getUnsafe().getTimingsServerName()), ++ pair("motd", Bukkit.getServer().getMotd()), ++ pair("icon", Bukkit.getServer().getServerIcon().getData()) ++ ); ++ } ++ ++ final Runtime runtime = Runtime.getRuntime(); ++ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); ++ ++ OperatingSystemMXBean osInfo = ManagementFactory.getOperatingSystemMXBean(); ++ ++ parent.put("system", createObject( ++ pair("timingcost", getCost()), ++ pair("loadavg", osInfo.getSystemLoadAverage()), ++ pair("name", System.getProperty("os.name")), ++ pair("version", System.getProperty("os.version")), ++ pair("jvmversion", System.getProperty("java.version")), ++ pair("arch", System.getProperty("os.arch")), ++ pair("maxmem", runtime.maxMemory()), ++ pair("memory", createObject( ++ pair("heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString()), ++ pair("nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString()), ++ pair("finalizing", ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount()) ++ )), ++ pair("cpu", runtime.availableProcessors()), ++ pair("runtime", runtimeBean.getUptime()), ++ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), ++ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())))) ++ ) ++ ); ++ ++ parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getAllLevels(), world -> { ++ if (world.getWorldData().getName().equals("worldeditregentempworld")) return null; ++ return pair(world.getWorldData().getName(), createObject( ++ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { ++ return pair(rule, world.getWorld().getGameRuleValue(rule)); ++ })), ++ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()) ++ )); ++ })); ++ ++ Set tileEntityTypeSet = Sets.newHashSet(); ++ Set entityTypeSet = Sets.newHashSet(); ++ ++ int size = HISTORY.size(); ++ TimingHistory[] history = new TimingHistory[size + 1]; ++ int i = 0; ++ for (TimingHistory timingHistory : HISTORY) { ++ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet); ++ entityTypeSet.addAll(timingHistory.entityTypeSet); ++ history[i++] = timingHistory; ++ } ++ ++ history[i] = new TimingHistory(); // Current snapshot ++ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet); ++ entityTypeSet.addAll(history[i].entityTypeSet); ++ ++ ++ Map handlers = createObject(); ++ Map groupData; ++ synchronized (TimingIdentifier.GROUP_MAP) { ++ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { ++ synchronized (group.handlers) { ++ for (TimingHandler id : group.handlers) { ++ ++ if (!id.isTimed() && !id.isSpecial()) { ++ continue; ++ } ++ ++ String name = id.identifier.name; ++ if (name.startsWith("##")) { ++ name = name.substring(3); ++ } ++ handlers.put(id.id, toArray( ++ group.id, ++ name ++ )); ++ } ++ } ++ } ++ ++ groupData = toObjectMapper( ++ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name)); ++ } ++ ++ parent.put("idmap", createObject( ++ pair("groups", groupData), ++ pair("handlers", handlers), ++ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))), ++ pair("tileentity", ++ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))), ++ pair("entity", ++ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name()))) ++ )); ++ ++ // Information about loaded plugins ++ ++ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), ++ plugin -> pair(plugin.getName(), createObject( ++ pair("version", plugin.getDescription().getVersion()), ++ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()), ++ pair("website", plugin.getDescription().getWebsite()), ++ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", ")) ++ )))); ++ ++ ++ ++ // Information on the users Config ++ ++ parent.put("config", createObject( ++ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), ++ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), ++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) ++ )); ++ ++ new TimingsExport(listeners, parent, history).start(); ++ } ++ ++ static long getCost() { ++ // Benchmark the users System.nanotime() for cost basis ++ int passes = 100; ++ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1"); ++ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2"); ++ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3"); ++ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4"); ++ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5"); ++ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6"); ++ ++ long start = System.nanoTime(); ++ for (int i = 0; i < passes; i++) { ++ SAMPLER1.startTiming(); ++ SAMPLER2.startTiming(); ++ SAMPLER3.startTiming(); ++ SAMPLER3.stopTiming(); ++ SAMPLER4.startTiming(); ++ SAMPLER5.startTiming(); ++ SAMPLER6.startTiming(); ++ SAMPLER6.stopTiming(); ++ SAMPLER5.stopTiming(); ++ SAMPLER4.stopTiming(); ++ SAMPLER2.stopTiming(); ++ SAMPLER1.stopTiming(); ++ } ++ long timingsCost = (System.nanoTime() - start) / passes / 6; ++ SAMPLER1.reset(true); ++ SAMPLER2.reset(true); ++ SAMPLER3.reset(true); ++ SAMPLER4.reset(true); ++ SAMPLER5.reset(true); ++ SAMPLER6.reset(true); ++ return timingsCost; ++ } ++ ++ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { ++ ++ JSONObject object = new JSONObject(); ++ for (String key : config.getKeys(false)) { ++ String fullKey = (parentKey != null ? parentKey + "." + key : key); ++ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) { ++ continue; ++ } ++ final Object val = config.get(key); ++ ++ object.put(key, valAsJSON(val, fullKey)); ++ } ++ return object; ++ } ++ ++ private static Object valAsJSON(Object val, final String parentKey) { ++ if (!(val instanceof MemorySection)) { ++ if (val instanceof List) { ++ Iterable v = (Iterable) val; ++ return toArrayMapper(v, input -> valAsJSON(input, parentKey)); ++ } else { ++ return String.valueOf(val); ++ } ++ } else { ++ return mapAsJSON((ConfigurationSection) val, parentKey); ++ } ++ } ++ ++ @Override ++ public void run() { ++ out.put("data", toArrayMapper(history, TimingHistory::export)); ++ ++ ++ String response = null; ++ String timingsURL = null; ++ try { ++ HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); ++ con.setDoOutput(true); ++ String hostName = "BrokenHost"; ++ try { ++ hostName = InetAddress.getLocalHost().getHostName(); ++ } catch (Exception ignored) {} ++ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName); ++ con.setRequestMethod("POST"); ++ con.setInstanceFollowRedirects(false); ++ ++ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{ ++ this.def.setLevel(7); ++ }}; ++ ++ request.write(JSONValue.toJSONString(out).getBytes("UTF-8")); ++ request.close(); ++ ++ response = getResponse(con); ++ ++ if (con.getResponseCode() != 302) { ++ listeners.sendMessage( ++ ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); ++ listeners.sendMessage(ChatColor.RED + "Check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ return; ++ } ++ ++ timingsURL = con.getHeaderField("Location"); ++ listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL); ++ ++ if (response != null && !response.isEmpty()) { ++ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response); ++ } ++ } catch (IOException ex) { ++ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex); ++ } finally { ++ this.listeners.done(timingsURL); ++ } ++ } ++ ++ private String getResponse(HttpURLConnection con) throws IOException { ++ InputStream is = null; ++ try { ++ is = con.getInputStream(); ++ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ++ ++ byte[] b = new byte[1024]; ++ int bytesRead; ++ while ((bytesRead = is.read(b)) != -1) { ++ bos.write(b, 0, bytesRead); ++ } ++ return bos.toString(); ++ ++ } catch (IOException ex) { ++ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); ++ return null; ++ } finally { ++ if (is != null) { ++ is.close(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..79ede25e4fe7a648b1d29c49d876482a2158f892 +--- /dev/null ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -0,0 +1,120 @@ ++package co.aikar.timings; ++ ++import Timing; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.storage.PrimaryLevelData; ++ ++/** ++ * Set of timers per world, to track world specific timings. ++ */ ++// TODO: Re-implement missing timers ++public class WorldTimingsHandler { ++ public final Timing mobSpawn; ++ public final Timing doChunkUnload; ++ public final Timing doPortalForcer; ++ public final Timing scheduledBlocks; ++ public final Timing scheduledBlocksCleanup; ++ public final Timing scheduledBlocksTicking; ++ public final Timing chunkTicks; ++ public final Timing lightChunk; ++ public final Timing chunkTicksBlocks; ++ public final Timing doVillages; ++ public final Timing doChunkMap; ++ public final Timing doChunkMapUpdate; ++ public final Timing doChunkMapToUpdate; ++ public final Timing doChunkMapSortMissing; ++ public final Timing doChunkMapSortSendToPlayers; ++ public final Timing doChunkMapPlayersNeedingChunks; ++ public final Timing doChunkMapPendingSendToPlayers; ++ public final Timing doChunkMapUnloadChunks; ++ public final Timing doChunkGC; ++ public final Timing doSounds; ++ public final Timing entityRemoval; ++ public final Timing entityTick; ++ public final Timing tileEntityTick; ++ public final Timing tileEntityPending; ++ public final Timing tracker1; ++ public final Timing tracker2; ++ public final Timing doTick; ++ public final Timing tickEntities; ++ public final Timing chunks; ++ public final Timing newEntities; ++ public final Timing raids; ++ public final Timing chunkProviderTick; ++ public final Timing broadcastChunkUpdates; ++ public final Timing countNaturalMobs; ++ ++ public final Timing chunkLoad; ++ public final Timing chunkLoadPopulate; ++ public final Timing syncChunkLoad; ++ public final Timing chunkLoadLevelTimer; ++ public final Timing chunkIO; ++ public final Timing chunkPostLoad; ++ public final Timing worldSave; ++ public final Timing worldSaveChunks; ++ public final Timing worldSaveLevel; ++ public final Timing chunkSaveData; ++ ++ ++ public final Timing miscMobSpawning; ++ ++ public WorldTimingsHandler(Level server) { ++ String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; ++ ++ mobSpawn = Timings.ofSafe(name + "mobSpawn"); ++ doChunkUnload = Timings.ofSafe(name + "doChunkUnload"); ++ scheduledBlocks = Timings.ofSafe(name + "Scheduled Blocks"); ++ scheduledBlocksCleanup = Timings.ofSafe(name + "Scheduled Blocks - Cleanup"); ++ scheduledBlocksTicking = Timings.ofSafe(name + "Scheduled Blocks - Ticking"); ++ chunkTicks = Timings.ofSafe(name + "Chunk Ticks"); ++ lightChunk = Timings.ofSafe(name + "Light Chunk"); ++ chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks"); ++ doVillages = Timings.ofSafe(name + "doVillages"); ++ doChunkMap = Timings.ofSafe(name + "doChunkMap"); ++ doChunkMapUpdate = Timings.ofSafe(name + "doChunkMap - Update"); ++ doChunkMapToUpdate = Timings.ofSafe(name + "doChunkMap - To Update"); ++ doChunkMapSortMissing = Timings.ofSafe(name + "doChunkMap - Sort Missing"); ++ doChunkMapSortSendToPlayers = Timings.ofSafe(name + "doChunkMap - Sort Send To Players"); ++ doChunkMapPlayersNeedingChunks = Timings.ofSafe(name + "doChunkMap - Players Needing Chunks"); ++ doChunkMapPendingSendToPlayers = Timings.ofSafe(name + "doChunkMap - Pending Send To Players"); ++ doChunkMapUnloadChunks = Timings.ofSafe(name + "doChunkMap - Unload Chunks"); ++ doSounds = Timings.ofSafe(name + "doSounds"); ++ doChunkGC = Timings.ofSafe(name + "doChunkGC"); ++ doPortalForcer = Timings.ofSafe(name + "doPortalForcer"); ++ entityTick = Timings.ofSafe(name + "entityTick"); ++ entityRemoval = Timings.ofSafe(name + "entityRemoval"); ++ tileEntityTick = Timings.ofSafe(name + "tileEntityTick"); ++ tileEntityPending = Timings.ofSafe(name + "tileEntityPending"); ++ ++ chunkLoad = Timings.ofSafe(name + "Chunk Load"); ++ chunkLoadPopulate = Timings.ofSafe(name + "Chunk Load - Populate"); ++ syncChunkLoad = Timings.ofSafe(name + "Sync Chunk Load"); ++ chunkLoadLevelTimer = Timings.ofSafe(name + "Chunk Load - Load Level"); ++ chunkIO = Timings.ofSafe(name + "Chunk Load - DiskIO"); ++ chunkPostLoad = Timings.ofSafe(name + "Chunk Load - Post Load"); ++ worldSave = Timings.ofSafe(name + "World Save"); ++ worldSaveLevel = Timings.ofSafe(name + "World Save - Level"); ++ worldSaveChunks = Timings.ofSafe(name + "World Save - Chunks"); ++ chunkSaveData = Timings.ofSafe(name + "Chunk Save - Data"); ++ ++ tracker1 = Timings.ofSafe(name + "tracker stage 1"); ++ tracker2 = Timings.ofSafe(name + "tracker stage 2"); ++ doTick = Timings.ofSafe(name + "doTick"); ++ tickEntities = Timings.ofSafe(name + "tickEntities"); ++ ++ chunks = Timings.ofSafe(name + "Chunks"); ++ newEntities = Timings.ofSafe(name + "New entity registration"); ++ raids = Timings.ofSafe(name + "Raids"); ++ chunkProviderTick = Timings.ofSafe(name + "Chunk provider tick"); ++ broadcastChunkUpdates = Timings.ofSafe(name + "Broadcast chunk updates"); ++ countNaturalMobs = Timings.ofSafe(name + "Count natural mobs"); ++ ++ ++ miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ } ++ ++ public static Timing getTickList(ServerLevel worldserver, String timingsType) { ++ return Timings.ofSafe(((PrimaryLevelData) worldserver.getLevelData()).getLevelName() + " - Scheduled " + timingsType); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index da922f395f0fff0881ead893c900c5b2623f48f0..1d03a79e9010bc514b72a81ba0ad4a62aeff1bb7 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -14,12 +14,15 @@ import java.util.concurrent.TimeUnit; + import java.util.logging.Level; + import java.util.regex.Pattern; + ++import com.google.common.collect.Lists; + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; + import org.bukkit.command.Command; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; ++import co.aikar.timings.Timings; ++import co.aikar.timings.TimingsManager; + + public class PaperConfig { + +@@ -188,4 +191,30 @@ public class PaperConfig { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } ++ ++ public static String timingsServerName; ++ private static void timings() { ++ boolean timings = getBoolean("timings.enabled", true); ++ boolean verboseTimings = getBoolean("timings.verbose", true); ++ TimingsManager.privacy = getBoolean("timings.server-name-privacy", false); ++ TimingsManager.hiddenConfigs = getList("timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses", "settings.velocity-support.secret")); ++ if (!TimingsManager.hiddenConfigs.contains("settings.velocity-support.secret")) { ++ TimingsManager.hiddenConfigs.add("settings.velocity-support.secret"); ++ } ++ int timingHistoryInterval = getInt("timings.history-interval", 300); ++ int timingHistoryLength = getInt("timings.history-length", 3600); ++ timingsServerName = getString("timings.server-name", "Unknown Server"); ++ ++ ++ Timings.setVerboseTimingsEnabled(verboseTimings); ++ Timings.setTimingsEnabled(timings); ++ Timings.setHistoryInterval(timingHistoryInterval * 20); ++ Timings.setHistoryLength(timingHistoryLength * 20); ++ ++ log("Timings: " + timings + ++ " - Verbose: " + verboseTimings + ++ " - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) + ++ " - Length: " + timeSummary(Timings.getHistoryLength() / 20) + ++ " - Server Name: " + timingsServerName); ++ } + } +diff --git a/src/main/java/net/minecraft/commands/CommandFunction.java b/src/main/java/net/minecraft/commands/CommandFunction.java +index cf57de0e791a9362fe95b65134d9fe4f4e95adf4..07b891075191161a8a903876b02caa75d4db1366 100644 +--- a/src/main/java/net/minecraft/commands/CommandFunction.java ++++ b/src/main/java/net/minecraft/commands/CommandFunction.java +@@ -15,12 +15,22 @@ public class CommandFunction { + + private final CommandFunction.Entry[] entries; + private final ResourceLocation id; ++ // Paper start ++ public co.aikar.timings.Timing timing; ++ public co.aikar.timings.Timing getTiming() { ++ if (timing == null) { ++ timing = co.aikar.timings.MinecraftTimings.getCommandFunctionTiming(this); ++ } ++ return timing; ++ } ++ // Paper end + + public CommandFunction(ResourceLocation id, CommandFunction.Entry[] elements) { + this.id = id; + this.entries = elements; + } + ++ public final ResourceLocation getMinecraftKey() { return this.getId(); } // Paper - OBFHELPER + public ResourceLocation getId() { + return this.id; + } +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index 23ccd095ad99fe90a6f2a16bcca368804b16101a..4ae8201d7dcffeb3298a4e593f978e15ffc5ac15 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -3,6 +3,8 @@ package net.minecraft.network.protocol; + import net.minecraft.network.PacketListener; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + // CraftBukkit start + import net.minecraft.server.MinecraftServer; +@@ -21,10 +23,13 @@ public class PacketUtils { + + public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { + if (!engine.isSameThread()) { ++ Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings + engine.execute(() -> { + if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 + if (listener.a().isConnected()) { ++ try (Timing ignored = timing.startTiming()) { // Paper - timings + packet.handle(listener); ++ } // Paper - timings + } else { + PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 2406879e76a110e96a4753e66366432a4bc52d9b..a456b9cbf0e5eea4e888e0e3d07ed17558650371 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -172,7 +172,7 @@ import org.bukkit.craftbukkit.Main; + import org.bukkit.event.server.ServerLoadEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements SnooperPopulator, CommandSource, AutoCloseable { +@@ -226,8 +226,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return !this.haveTime(); ++ return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick + }); + } + +@@ -1120,10 +1133,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ return !this.canOversleep(); ++ }); ++ isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); ++ // Paper end ++ + ++this.tickCount; + this.tickChildren(shouldKeepTicking); + if (i - this.lastServerStatus >= 5000000000L) { +@@ -1141,14 +1162,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.tickCount % autosavePeriod == 0) { // CraftBukkit +- SpigotTimings.worldSaveTimer.startTiming(); // Spigot + MinecraftServer.LOGGER.debug("Autosave started"); + this.profiler.push("save"); + this.playerList.saveAll(); + this.saveAllChunks(true, false, false); + this.profiler.pop(); + MinecraftServer.LOGGER.debug("Autosave finished"); +- SpigotTimings.worldSaveTimer.stopTiming(); // Spigot + } + + this.profiler.push("snooper"); +@@ -1161,6 +1180,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + // CraftBukkit start - fire RemoteServerCommandEvent +@@ -677,10 +679,39 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (event.isCancelled()) { + return; + } ++ // Paper start ++ if (command.toLowerCase().startsWith("timings") && command.toLowerCase().matches("timings (report|paste|get|merged|seperate)")) { ++ org.bukkit.command.BufferedCommandSender sender = new org.bukkit.command.BufferedCommandSender(); ++ Waitable waitable = new Waitable() { ++ @Override ++ protected String evaluate() { ++ return sender.getBuffer(); ++ } ++ }; ++ waitableArray[0] = waitable; ++ co.aikar.timings.Timings.generateReport(new co.aikar.timings.TimingsReportListener(sender, waitable)); ++ } else { ++ // Paper end + ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), rconConsoleSource.createCommandSourceStack()); + server.dispatchServerCommand(remoteConsole, serverCommand); ++ } // Paper + // CraftBukkit end + }); ++ // Paper start ++ if (waitableArray[0] != null) { ++ //noinspection unchecked ++ Waitable waitable = waitableArray[0]; ++ try { ++ return waitable.get(); ++ } catch (java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException("Exception processing rcon command " + command, e.getCause()); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // Maintain interrupted state ++ throw new RuntimeException("Interrupted processing rcon command " + command, e); ++ } ++ ++ } ++ // Paper end + return this.rconConsoleSource.getCommandResponse(); + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index d9d76d2ee43adc2b46d49f7fc3d9aef6af95e465..59a5f82c9f57d760ba4959a040ce8cbf0f49e4aa 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1,7 +1,9 @@ + package net.minecraft.server.level; + ++import co.aikar.timings.Timing; // Paper + import com.google.common.collect.ImmutableList; + import com.google.common.collect.Iterables; ++import com.google.common.collect.ComparisonChain; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Queues; + import com.google.common.collect.Sets; +@@ -552,11 +554,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + private CompletableFuture> scheduleChunkLoad(ChunkPos pos) { + return CompletableFuture.supplyAsync(() -> { +- try { ++ try (Timing ignored = this.level.timings.chunkLoad.startTimingIfSync()) { // Paper + this.level.getProfiler().incrementCounter("chunkLoad"); +- CompoundTag nbttagcompound = this.readChunk(pos); ++ CompoundTag nbttagcompound; // Paper ++ try (Timing ignored2 = this.level.timings.chunkIO.startTimingIfSync()) { // Paper start - timings ++ nbttagcompound = this.readChunk(pos); ++ } // Paper end + +- if (nbttagcompound != null) { ++ if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings + boolean flag = nbttagcompound.contains("Level", 10) && nbttagcompound.getCompound("Level").contains("Status", 8); + + if (flag) { +@@ -568,7 +573,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos); +- } ++ }} // Paper + } catch (ReportedException reportedexception) { + Throwable throwable = reportedexception.getCause(); + +@@ -605,7 +610,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return "chunkGenerate " + requiredStatus.getName(); + }); + return completablefuture.thenComposeAsync((either) -> { +- return (CompletableFuture) either.map((list) -> { ++ return either.map((list) -> { // Paper - Shut up. + try { + CompletableFuture> completablefuture1 = requiredStatus.generate(this.level, this.generator, this.structureManager, this.lightEngine, (ichunkaccess) -> { + return this.protoChunkToFullChunk(holder); +@@ -658,6 +663,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ChunkStatus chunkstatus = ChunkHolder.getStatus(playerchunk.getTicketLevel()); + + return !chunkstatus.isOrAfter(ChunkStatus.FULL) ? ChunkHolder.UNLOADED_CHUNK : either.mapLeft((ichunkaccess) -> { ++ try (Timing ignored = level.timings.chunkPostLoad.startTimingIfSync()) { // Paper + ChunkPos chunkcoordintpair = playerchunk.getPos(); + LevelChunk chunk; + +@@ -717,6 +723,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + return chunk; ++ } // Paper + }); + }, (runnable) -> { + ProcessorHandle mailbox = this.mainThreadMailbox; +@@ -1175,6 +1182,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + ChunkMap.TrackedEntity playerchunkmap_entitytracker; + ObjectIterator objectiterator; ++ level.timings.tracker1.startTiming(); // Paper + + for (objectiterator = this.entityMap.values().iterator(); objectiterator.hasNext(); playerchunkmap_entitytracker.serverEntity.sendChanges()) { + playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +@@ -1192,16 +1200,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + playerchunkmap_entitytracker.lastSectionPos = sectionposition1; + } + } ++ level.timings.tracker1.stopTiming(); // Paper + + if (!list.isEmpty()) { + objectiterator = this.entityMap.values().iterator(); + ++ level.timings.tracker2.startTiming(); // Paper + while (objectiterator.hasNext()) { + playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); + playerchunkmap_entitytracker.updatePlayers(list); + } ++ level.timings.tracker2.stopTiming(); // Paper + } + ++ + } + + protected void broadcast(Entity entity, Packet packet) { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index ef336645f3bd7a86129ad1dff7e1a15dc93d1e3e..d0b0fdaf5451bcc7f7ac7dab28aa59ef77e6dd97 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -344,11 +344,13 @@ public class ServerChunkCache extends ChunkSource { + } + + gameprofilerfiller.incrementCounter("getChunkCacheMiss"); +- level.timings.syncChunkLoadTimer.startTiming(); // Spigot + CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); + ++ if (!completablefuture.isDone()) { // Paper ++ this.level.timings.syncChunkLoad.startTiming(); // Paper + this.mainThreadProcessor.managedBlock(completablefuture::isDone); +- level.timings.syncChunkLoadTimer.stopTiming(); // Spigot ++ this.level.timings.syncChunkLoad.stopTiming(); // Paper ++ } // Paper + ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { + return ichunkaccess1; + }, (playerchunk_failure) -> { +@@ -536,7 +538,9 @@ public class ServerChunkCache extends ChunkSource { + + public void save(boolean flush) { + this.runDistanceManagerUpdates(); ++ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings + this.chunkMap.saveAllChunks(flush); ++ } // Paper - Timings + } + + @Override +@@ -573,7 +577,9 @@ public class ServerChunkCache extends ChunkSource { + this.runDistanceManagerUpdates(); + this.level.timings.doChunkMap.stopTiming(); // Spigot + this.level.getProfiler().popPush("chunks"); ++ this.level.timings.chunks.startTiming(); // Paper - timings + this.tickChunks(); ++ this.level.timings.chunks.stopTiming(); // Paper - timings + this.level.timings.doChunkUnload.startTiming(); // Spigot + this.level.getProfiler().popPush("unload"); + this.chunkMap.tick(shouldKeepTicking); +@@ -597,19 +603,24 @@ public class ServerChunkCache extends ChunkSource { + boolean flag2 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit + + this.level.getProfiler().push("naturalSpawnCount"); ++ this.level.timings.countNaturalMobs.startTiming(); // Paper - timings + int l = this.distanceManager.getNaturalSpawnChunkCount(); + NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk); ++ this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.lastSpawnState = spawnercreature_d; + this.level.getProfiler().pop(); + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper ++ this.level.timings.chunkTicks.startTiming(); // Paper + this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no... + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + + if (optional.isPresent()) { + this.level.getProfiler().push("broadcast"); ++ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timings + playerchunk.broadcastChanges((LevelChunk) optional.get()); ++ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings + this.level.getProfiler().pop(); + Optional optional1 = ((Either) playerchunk.getEntityTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + +@@ -623,25 +634,26 @@ public class ServerChunkCache extends ChunkSource { + NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2); + } + +- this.level.timings.doTickTiles.startTiming(); // Spigot ++ //this.world.timings.chunkTicks.startTiming(); // Spigot // Paper + this.level.tickChunk(chunk, k); +- this.level.timings.doTickTiles.stopTiming(); // Spigot ++ //this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper + } + } + } + }); ++ this.level.timings.chunkTicks.stopTiming(); // Paper + this.level.getProfiler().push("customSpawners"); + if (flag1) { ++ try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings + this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); ++ } // Paper - timings + } + + this.level.getProfiler().pop(); + this.level.getProfiler().pop(); + } + +- this.level.timings.tracker.startTiming(); // Spigot + this.chunkMap.tick(); +- this.level.timings.tracker.stopTiming(); // Spigot + } + + private void getFullChunk(long pos, Consumer chunkConsumer) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 34ed8f0d348e7bc2339660ebc6490057ba9ef214..0cc86ca4ea4a2e1b5acc3c0507397eef85dec0c1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2,6 +2,8 @@ package net.minecraft.server.level; + + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Iterables; ++import co.aikar.timings.TimingHistory; // Paper ++import co.aikar.timings.Timings; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Queues; +@@ -147,7 +149,6 @@ import org.apache.logging.log4j.Logger; + import java.util.logging.Level; + import org.bukkit.Bukkit; + import org.bukkit.WeatherType; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.event.CraftEventFactory; + import org.bukkit.craftbukkit.util.WorldUUID; + import org.bukkit.event.entity.CreatureSpawnEvent; +@@ -203,10 +204,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit end + this.blockTicks = new ServerTickList<>(this, (block) -> { + return block == null || block.defaultBlockState().isAir(); +- }, Registry.BLOCK::getKey, this::tickBlock); ++ }, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // Paper - Timings + this.liquidTicks = new ServerTickList<>(this, (fluidtype) -> { + return fluidtype == null || fluidtype == Fluids.EMPTY; +- }, Registry.FLUID::getKey, this::tickLiquid); ++ }, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // Paper - Timings + this.navigations = Sets.newHashSet(); + this.blockEvents = new ObjectLinkedOpenHashSet(); + this.tickTime = flag1; +@@ -436,17 +437,21 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.updateSkyBrightness(); + this.tickTime(); + gameprofilerfiller.popPush("chunkSource"); ++ this.timings.chunkProviderTick.startTiming(); // Paper - timings + this.getChunkSource().tick(shouldKeepTicking); ++ this.timings.chunkProviderTick.stopTiming(); // Paper - timings + gameprofilerfiller.popPush("tickPending"); +- timings.doTickPending.startTiming(); // Spigot ++ timings.scheduledBlocks.startTiming(); // Paper + if (!this.isDebug()) { + this.blockTicks.tick(); + this.liquidTicks.tick(); + } +- timings.doTickPending.stopTiming(); // Spigot ++ timings.scheduledBlocks.stopTiming(); // Paper + + gameprofilerfiller.popPush("raid"); ++ this.timings.raids.startTiming(); // Paper - timings + this.raids.tick(); ++ this.timings.raids.stopTiming(); // Paper - timings + gameprofilerfiller.popPush("blockEvents"); + timings.doSounds.startTiming(); // Spigot + this.runBlockEvents(); +@@ -618,6 +623,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + gameprofilerfiller.popPush("tickBlocks"); ++ timings.chunkTicksBlocks.startTiming(); // Paper + if (randomTickSpeed > 0) { + LevelChunkSection[] achunksection = chunk.getSections(); + int l = achunksection.length; +@@ -649,7 +655,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + } +- ++ timings.chunkTicksBlocks.stopTiming(); // Paper + gameprofilerfiller.pop(); + } + +@@ -747,14 +753,22 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (!(entity instanceof Player) && !this.getChunkSource().isEntityTickingChunk(entity)) { + this.updateChunkPos(entity); + } else { ++ ++TimingHistory.entityTicks; // Paper - timings + // Spigot start ++ co.aikar.timings.Timing timer; // Paper + if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { + entity.tickCount++; ++ timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings + entity.inactiveTick(); ++ } finally { timer.stopTiming(); } // Paper + return; + } + // Spigot end +- entity.tickTimer.startTiming(); // Spigot ++ // Paper start- timings ++ TimingHistory.activatedEntityTicks++; ++ timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); ++ try { ++ // Paper end - timings + entity.setPosAndOldPos(entity.getX(), entity.getY(), entity.getZ()); + entity.yRotO = entity.yRot; + entity.xRotO = entity.xRot; +@@ -781,7 +795,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.tickPassenger(entity, entity1); + } + } +- entity.tickTimer.stopTiming(); // Spigot ++ } finally { timer.stopTiming(); } // Paper - timings + + } + } +@@ -859,6 +873,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + if (!flag1) { + org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit ++ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper + if (progressListener != null) { + progressListener.progressStartNoAbort(new TranslatableComponent("menu.savingLevel")); + } +@@ -868,7 +883,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + progressListener.progressStage(new TranslatableComponent("menu.savingChunks")); + } + ++ timings.worldSaveChunks.startTiming(); // Paper + chunkproviderserver.save(flush); ++ timings.worldSaveChunks.stopTiming(); // Paper ++ } // Paper + } + + // CraftBukkit start - moved from MinecraftServer.saveChunks +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 35f3940cebb00ee29da54b1ee148ee931fa11636..466c4322803bedf1fa61be281b954bf94fb8ff02 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -209,6 +209,7 @@ import org.bukkit.inventory.EquipmentSlot; + import org.bukkit.inventory.InventoryView; + import org.bukkit.inventory.SmithingInventory; + import org.bukkit.util.NumberConversions; ++import co.aikar.timings.MinecraftTimings; // Paper + // CraftBukkit end + + public class ServerGamePacketListenerImpl implements ServerGamePacketListener { +@@ -291,7 +292,6 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit end + + public void tick() { +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.startTiming(); // Spigot + this.resetPosition(); + this.player.xo = this.player.getX(); + this.player.yo = this.player.getY(); +@@ -367,7 +367,6 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect(new TranslatableComponent("multiplayer.disconnect.idling")); + } +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.stopTiming(); // Spigot + + } + +@@ -1915,7 +1914,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit end + + private void handleCommand(String input) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.startTiming(); // Paper + // CraftBukkit start - whole method + if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot + this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + input); +@@ -1926,7 +1925,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.craftServer.getPluginManager().callEvent(event); + + if (event.isCancelled()) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + return; + } + +@@ -1939,7 +1938,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + return; + } finally { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + } + // this.minecraftServer.getCommandDispatcher().a(this.player.getCommandListener(), s); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 28121e2fee9d862057042261d25360f0d4ee4530..88af57699d7f9e45ad1366243049e4f3565703ff 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.players; + ++import co.aikar.timings.MinecraftTimings; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -1018,10 +1019,11 @@ public abstract class PlayerList { + } + + public void saveAll() { ++ MinecraftTimings.savePlayers.startTiming(); // Paper + for (int i = 0; i < this.players.size(); ++i) { + this.save((ServerPlayer) this.players.get(i)); + } +- ++ MinecraftTimings.savePlayers.stopTiming(); // Paper + } + + public UserWhiteList getWhiteList() { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index b633f6b3a36b793e6dbc1b8b554bfba74c719570..2a7f587e19fcdd6d01b360d6b47d9eadd9df92cc 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -115,7 +115,6 @@ import org.bukkit.craftbukkit.event.CraftPortalEvent; + import org.bukkit.entity.Hanging; + import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Vehicle; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.bukkit.event.entity.EntityCombustByEntityEvent; + import org.bukkit.event.hanging.HangingBreakByEntityEvent; + import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; +@@ -247,7 +246,6 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean forceExplosionKnockback; // SPIGOT-949 + public boolean persistentInvisibility = false; +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; +@@ -616,7 +614,6 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public void move(MoverType type, Vec3 movement) { +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot + if (this.noPhysics) { + this.setBoundingBox(this.getBoundingBox().move(movement)); + this.setLocationFromBoundingbox(); +@@ -752,7 +749,6 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + this.level.getProfiler().pop(); + } +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot + } + + protected BlockPos getOnPos() { +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 2cb86de4bfc87a709f0cfa2c4e550d8e7928a3f0..e3d92d1d35911b2960a7ca82bd4f324d285d0533 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -281,17 +281,27 @@ public class EntityType { + return Registry.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(id)); + } + +- public EntityType(EntityType.EntityFactory factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet immutableset, EntityDimensions entitysize, int i, int j) { +- this.factory = factory; +- this.category = spawnGroup; +- this.canSpawnFarFromPlayer = spawnableFarFromPlayer; +- this.serialize = saveable; +- this.summon = summonable; +- this.fireImmune = fireImmune; ++ public final String id; ++ public EntityType(EntityType.EntityFactory factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet immutableset, EntityDimensions entitysize, int i, int j) { this(factory, spawnGroup, saveable, summonable, fireImmune, spawnableFarFromPlayer, immutableset, entitysize, i, j, "custom"); } // Paper - old signature ++ public EntityType(EntityType.EntityFactory entitytypes_b, MobCategory enumcreaturetype, boolean flag, boolean flag1, boolean flag2, boolean flag3, ImmutableSet immutableset, EntityDimensions entitysize, int i, int j, String id) { // Paper - add id ++ this.factory = entitytypes_b; ++ this.category = enumcreaturetype; ++ this.canSpawnFarFromPlayer = flag3; ++ this.serialize = flag; ++ this.summon = flag1; ++ this.fireImmune = flag2; + this.immuneTo = immutableset; + this.dimensions = entitysize; + this.clientTrackingRange = i; + this.updateInterval = j; ++ ++ // Paper start - timings ++ this.id = id; ++ this.tickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "tick"); ++ this.inactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "inactiveTick"); ++ this.passengerTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerTick"); ++ this.passengerInactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerInactiveTick"); ++ // Paper end + } + + @Nullable +@@ -512,6 +522,12 @@ public class EntityType { + return this.updateInterval; + } + ++ // Paper start - timings ++ public final co.aikar.timings.Timing tickTimer; ++ public final co.aikar.timings.Timing inactiveTickTimer; ++ public final co.aikar.timings.Timing passengerTickTimer; ++ public final co.aikar.timings.Timing passengerInactiveTickTimer; ++ // Paper end + public boolean trackDeltas() { + return this != EntityType.PLAYER && this != EntityType.LLAMA_SPIT && this != EntityType.WITHER && this != EntityType.BAT && this != EntityType.ITEM_FRAME && this != EntityType.LEASH_KNOT && this != EntityType.PAINTING && this != EntityType.END_CRYSTAL && this != EntityType.EVOKER_FANGS; + } +@@ -599,7 +615,7 @@ public class EntityType { + Util.fetchChoiceType(References.ENTITY_TREE, id); + } + +- return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions, this.clientTrackingRange, this.updateInterval); ++ return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions, this.clientTrackingRange, this.updateInterval, id); // Paper - add id + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9e5dde73bd65bf4e51352c628fba024c36e29ef1..e5f8cee6726ea9a90c540bb10fd8594a35bb5e40 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -132,7 +132,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper + + public abstract class LivingEntity extends Entity { + +@@ -2455,7 +2455,6 @@ public abstract class LivingEntity extends Entity { + + @Override + public void tick() { +- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot + super.tick(); + this.updatingUsingItem(); + this.updateSwimAmount(); +@@ -2504,9 +2503,7 @@ public abstract class LivingEntity extends Entity { + } + } + +- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot + this.aiStep(); +- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot + double d0 = this.getX() - this.xo; + double d1 = this.getZ() - this.zo; + float f = (float) (d0 * d0 + d1 * d1); +@@ -2586,8 +2583,6 @@ public abstract class LivingEntity extends Entity { + if (this.isSleeping()) { + this.xRot = 0.0F; + } +- +- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot + } + + public void detectEquipmentUpdates() { +@@ -2769,7 +2764,6 @@ public abstract class LivingEntity extends Entity { + + this.setDeltaMovement(d4, d5, d6); + this.level.getProfiler().push("ai"); +- SpigotTimings.timerEntityAI.startTiming(); // Spigot + if (this.isImmobile()) { + this.jumping = false; + this.xxa = 0.0F; +@@ -2779,7 +2773,6 @@ public abstract class LivingEntity extends Entity { + this.serverAiStep(); + this.level.getProfiler().pop(); + } +- SpigotTimings.timerEntityAI.stopTiming(); // Spigot + + this.level.getProfiler().pop(); + this.level.getProfiler().push("jump"); +@@ -2814,9 +2807,9 @@ public abstract class LivingEntity extends Entity { + this.updateFallFlying(); + AABB axisalignedbb = this.getBoundingBox(); + +- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot ++ // SpigotTimings.timerEntityAIMove.startTiming(); // Spigot // Paper + this.travel(new Vec3((double) this.xxa, (double) this.yya, (double) this.zza)); +- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot ++ // SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot // Paper + this.level.getProfiler().pop(); + this.level.getProfiler().push("push"); + if (this.autoSpinAttackTicks > 0) { +@@ -2824,9 +2817,7 @@ public abstract class LivingEntity extends Entity { + this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); + } + +- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot + this.pushEntities(); +- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot + this.level.getProfiler().pop(); + if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurt(DamageSource.DROWN, 1.0F); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5cc4c2668df72f83fb1526f4586b71d2ae0103dc..c153df1f4dea3dc0ae744bde01e334b3bd3b50af 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -70,7 +70,6 @@ import org.apache.logging.log4j.Logger; + import java.util.HashMap; + import java.util.Map; + import org.bukkit.Bukkit; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.block.CapturedBlockState; +@@ -132,7 +131,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper + +- public final SpigotTimings.WorldTimingsHandler timings; // Spigot ++ public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -217,7 +216,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {} + }); + // CraftBukkit end +- timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings ++ timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } +@@ -797,15 +796,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + timings.tileEntityPending.stopTiming(); // Spigot ++ co.aikar.timings.TimingHistory.tileEntityTicks += this.tickableBlockEntities.size(); // Paper + gameprofilerfiller.pop(); + spigotConfig.currentPrimedTnt = 0; // Spigot + } + + public void guardEntityTick(Consumer tickConsumer, Entity entity) { + try { +- SpigotTimings.tickEntityTimer.startTiming(); // Spigot + tickConsumer.accept(entity); +- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked"); +diff --git a/src/main/java/net/minecraft/world/level/ServerTickList.java b/src/main/java/net/minecraft/world/level/ServerTickList.java +index 0519d5adf392dd46b4a93c5c1b977c8355163c2e..10ac1ba0a3d192486f22c2127d5bc30353f0edb6 100644 +--- a/src/main/java/net/minecraft/world/level/ServerTickList.java ++++ b/src/main/java/net/minecraft/world/level/ServerTickList.java +@@ -38,12 +38,17 @@ public class ServerTickList implements TickList { + private final List> alreadyTicked = Lists.newArrayList(); + private final Consumer> ticker; + +- public ServerTickList(ServerLevel world, Predicate invalidObjPredicate, Function idToName, Consumer> consumer) { +- this.ignore = invalidObjPredicate; +- this.toId = idToName; +- this.level = world; ++ public ServerTickList(ServerLevel worldserver, Predicate predicate, Function function, Consumer> consumer, String timingsType) { // Paper ++ this.ignore = predicate; ++ this.toId = function; ++ this.level = worldserver; + this.ticker = consumer; ++ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(worldserver, timingsType + " - Cleanup"); ++ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(worldserver, timingsType + " - Ticking"); + } ++ private final co.aikar.timings.Timing timingCleanup; // Paper ++ private final co.aikar.timings.Timing timingTicking; // Paper ++ // Paper end + + public void tick() { + int i = this.tickNextTickList.size(); +@@ -66,6 +71,7 @@ public class ServerTickList implements TickList { + + this.level.getProfiler().push("cleaning"); + ++ this.timingCleanup.startTiming(); // Paper + TickNextTickData nextticklistentry; + + while (i > 0 && iterator.hasNext()) { +@@ -81,7 +87,9 @@ public class ServerTickList implements TickList { + --i; + } + } ++ this.timingCleanup.stopTiming(); // Paper + ++ this.timingTicking.startTiming(); // Paper + this.level.getProfiler().popPush("ticking"); + + while ((nextticklistentry = (TickNextTickData) this.currentlyTicking.poll()) != null) { +@@ -101,6 +109,7 @@ public class ServerTickList implements TickList { + } + } + ++ this.timingTicking.stopTiming(); // Paper + this.level.getProfiler().pop(); + this.alreadyTicked.clear(); + this.currentlyTicking.clear(); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 73a271a1fccd6f82dac8d33c0d378f0d84ceb5e5..2ae786b8fc6da19ca2a40252b0606f9e06d31ded 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -61,6 +61,15 @@ public class Block extends BlockBehaviour implements ItemLike { + }); + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; ++ // Paper start ++ public co.aikar.timings.Timing timing; ++ public co.aikar.timings.Timing getTiming() { ++ if (timing == null) { ++ timing = co.aikar.timings.MinecraftTimings.getBlockTiming(this); ++ } ++ return timing; ++ } ++ // Paper end + @Nullable + private String descriptionId; + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index bb60c9da9f3ba0d5c5bad22512675ccb841a60e5..d445a1b7b7605eed66923789c5d8e2199c31c5ac 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -23,10 +23,12 @@ import org.bukkit.inventory.InventoryHolder; + // CraftBukkit end + + import org.spigotmc.CustomTimingsHandler; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + public abstract class BlockEntity implements net.minecraft.server.KeyedObject { // Paper + +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot ++ public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + public CraftPersistentDataContainer persistentDataContainer; +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 00ce55c17980da87a3834f952475a766543506b0..f30793b81dfd9018b4879d655c7c18a9f9c25267 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -732,6 +732,7 @@ public class LevelChunk implements ChunkAccess { + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(this.bukkitChunk, this.needsDecoration)); + + if (this.needsDecoration) { ++ try (co.aikar.timings.Timing ignored = this.world.timings.chunkLoadPopulate.startTiming()) { // Paper + this.needsDecoration = false; + java.util.Random random = new java.util.Random(); + random.setSeed(world.getSeed()); +@@ -751,6 +752,7 @@ public class LevelChunk implements ChunkAccess { + } + } + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); ++ } // Paper + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 7e13a438bd80ab5452eacf107d418c42c2e5c727..0efaf4d0f58bcf38b427e76bf09b96e354294159 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.chunk.storage; + ++import co.aikar.timings.Timings; + import com.google.common.collect.Maps; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; +@@ -446,7 +447,6 @@ public class ChunkSerializer { + private static void postLoadChunk(CompoundTag tag, LevelChunk chunk) { + ListTag nbttaglist = tag.getList("Entities", 10); + Level world = chunk.getLevel(); +- world.timings.syncChunkLoadEntitiesTimer.startTiming(); // Spigot + + for (int i = 0; i < nbttaglist.size(); ++i) { + CompoundTag nbttagcompound1 = nbttaglist.getCompound(i); +@@ -458,8 +458,6 @@ public class ChunkSerializer { + chunk.setLastSaveHadEntities(true); + } + +- world.timings.syncChunkLoadEntitiesTimer.stopTiming(); // Spigot +- world.timings.syncChunkLoadTileEntitiesTimer.startTiming(); // Spigot + ListTag nbttaglist1 = tag.getList("TileEntities", 10); + + for (int j = 0; j < nbttaglist1.size(); ++j) { +@@ -477,8 +475,6 @@ public class ChunkSerializer { + } + } + } +- world.timings.syncChunkLoadTileEntitiesTimer.stopTiming(); // Spigot +- + } + + private static CompoundTag packStructureData(ChunkPos pos, Map, StructureStart> structureStarts, Map, LongSet> structureReferences) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 328d1e2b128b62f24917719c79823c9fb64a0dcf..c4cf1394fe4c2782b1fea8b3653a817157d857eb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2073,12 +2073,31 @@ public final class CraftServer implements Server { + private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() + { + ++ @Deprecated + @Override + public YamlConfiguration getConfig() + { + return org.spigotmc.SpigotConfig.config; + } + ++ @Override ++ public YamlConfiguration getBukkitConfig() ++ { ++ return configuration; ++ } ++ ++ @Override ++ public YamlConfiguration getSpigotConfig() ++ { ++ return org.spigotmc.SpigotConfig.config; ++ } ++ ++ @Override ++ public YamlConfiguration getPaperConfig() ++ { ++ return com.destroystokyo.paper.PaperConfig.config; ++ } ++ + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); +diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +deleted file mode 100644 +index 56b644e272ddad0e5410061e0a202daaebb734b8..0000000000000000000000000000000000000000 +--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java ++++ /dev/null +@@ -1,163 +0,0 @@ +-package org.bukkit.craftbukkit; +- +-import java.util.HashMap; +-import net.minecraft.world.entity.Entity; +-import net.minecraft.world.level.Level; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.storage.PrimaryLevelData; +-import org.bukkit.craftbukkit.scheduler.CraftTask; +-import org.bukkit.plugin.java.JavaPluginLoader; +-import org.bukkit.scheduler.BukkitTask; +-import org.spigotmc.CustomTimingsHandler; +- +-public class SpigotTimings { +- +- public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick"); +- public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List"); +- public static final CustomTimingsHandler commandFunctionsTimer = new CustomTimingsHandler("Command Functions"); +- public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Connection Handler"); +- public static final CustomTimingsHandler playerConnectionTimer = new CustomTimingsHandler("** PlayerConnection"); +- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); +- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); +- public static final CustomTimingsHandler timeUpdateTimer = new CustomTimingsHandler("Time Update"); +- public static final CustomTimingsHandler serverCommandTimer = new CustomTimingsHandler("Server Command"); +- public static final CustomTimingsHandler worldSaveTimer = new CustomTimingsHandler("World Save"); +- +- public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove"); +- public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity"); +- public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity"); +- public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity"); +- +- public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick"); +- public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI"); +- public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision"); +- public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove"); +- public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest"); +- +- public static final CustomTimingsHandler processQueueTimer = new CustomTimingsHandler("processQueue"); +- public static final CustomTimingsHandler schedulerSyncTimer = new CustomTimingsHandler("** Scheduler - Sync Tasks", JavaPluginLoader.pluginParentTimer); +- +- public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand"); +- +- public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck"); +- public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive"); +- +- public static final HashMap entityTypeTimingMap = new HashMap(); +- public static final HashMap tileEntityTypeTimingMap = new HashMap(); +- public static final HashMap pluginTaskTimingMap = new HashMap(); +- +- /** +- * Gets a timer associated with a plugins tasks. +- * @param task +- * @param period +- * @return +- */ +- public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) { +- if (!task.isSync()) { +- return null; +- } +- String plugin; +- final CraftTask ctask = (CraftTask) task; +- +- if (task.getOwner() != null) { +- plugin = task.getOwner().getDescription().getFullName(); +- } else { +- plugin = "Unknown"; +- } +- String taskname = ctask.getTaskName(); +- +- String name = "Task: " + plugin + " Runnable: " + taskname; +- if (period > 0) { +- name += "(interval:" + period + ")"; +- } else { +- name += "(Single)"; +- } +- CustomTimingsHandler result = pluginTaskTimingMap.get(name); +- if (result == null) { +- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer); +- pluginTaskTimingMap.put(name, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getEntityTimings(Entity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = entityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickEntity - " + entity.getClass().getSimpleName(), activatedEntityTimer); +- entityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified tile entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getTileEntityTimings(BlockEntity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = tileEntityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickTileEntity - " + entity.getClass().getSimpleName(), tickTileEntityTimer); +- tileEntityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Set of timers per world, to track world specific timings. +- */ +- public static class WorldTimingsHandler { +- public final CustomTimingsHandler mobSpawn; +- public final CustomTimingsHandler doChunkUnload; +- public final CustomTimingsHandler doTickPending; +- public final CustomTimingsHandler doTickTiles; +- public final CustomTimingsHandler doChunkMap; +- public final CustomTimingsHandler doSounds; +- public final CustomTimingsHandler entityTick; +- public final CustomTimingsHandler tileEntityTick; +- public final CustomTimingsHandler tileEntityPending; +- public final CustomTimingsHandler tracker; +- public final CustomTimingsHandler doTick; +- public final CustomTimingsHandler tickEntities; +- +- public final CustomTimingsHandler syncChunkLoadTimer; +- public final CustomTimingsHandler syncChunkLoadStructuresTimer; +- public final CustomTimingsHandler syncChunkLoadEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileTicksTimer; +- public final CustomTimingsHandler syncChunkLoadPostTimer; +- +- public WorldTimingsHandler(Level server) { +- String name = ((PrimaryLevelData) server.levelData).getLevelName() + " - "; +- +- mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn"); +- doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload"); +- doTickPending = new CustomTimingsHandler("** " + name + "doTickPending"); +- doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles"); +- doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap"); +- doSounds = new CustomTimingsHandler("** " + name + "doSounds"); +- entityTick = new CustomTimingsHandler("** " + name + "entityTick"); +- tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick"); +- tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending"); +- +- syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad"); +- syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures"); +- syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities"); +- syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities"); +- syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks"); +- syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post"); +- +- +- tracker = new CustomTimingsHandler(name + "tracker"); +- doTick = new CustomTimingsHandler(name + "doTick"); +- tickEntities = new CustomTimingsHandler(name + "tickEntities"); +- } +- } +-} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8fbf6c56d8820f3fa86e70a2636c0b58043232c3..61e2d92471d1498eb97d42dc642605a2e00e6089 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1806,6 +1806,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + packet.components = components; + getHandle().connection.send(packet); + } ++ ++ // Paper start ++ @Override ++ public int getPing() ++ { ++ return getHandle().latency; ++ } ++ // Paper end + }; + + public Player.Spigot spigot() +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 4bf48f77f3f7cd62a91590543f5af441c8268029..ffe9cc1011226d604dc5499e7692e9a9a5132b72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.scheduler; + ++import co.aikar.timings.MinecraftTimings; // Paper + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import java.util.ArrayList; + import java.util.Comparator; +@@ -179,7 +180,8 @@ public class CraftScheduler implements BukkitScheduler { + } + + public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) { +- final CraftTask task = new CraftTask(run, nextId(), taskName); ++ final CraftTask task = new CraftTask(run, nextId(), "Internal - " + (taskName != null ? taskName : "Unknown")); ++ task.internal = true; + return handle(task, delay); + } + +@@ -260,7 +262,7 @@ public class CraftScheduler implements BukkitScheduler { + } + return false; + } +- }); ++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -295,7 +297,7 @@ public class CraftScheduler implements BukkitScheduler { + } + } + } +- }); ++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -402,9 +404,7 @@ public class CraftScheduler implements BukkitScheduler { + if (task.isSync()) { + currentTask = task; + try { +- task.timings.startTiming(); // Spigot + task.run(); +- task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { + // Paper start + String msg = String.format( +@@ -438,8 +438,10 @@ public class CraftScheduler implements BukkitScheduler { + runners.remove(task.getTaskId()); + } + } ++ MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); + pending.addAll(temp); + temp.clear(); ++ MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); + debugHead = debugHead.getNextHead(currentTick); + } + +@@ -472,6 +474,7 @@ public class CraftScheduler implements BukkitScheduler { + } + + private void parsePending() { ++ MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; +@@ -490,6 +493,7 @@ public class CraftScheduler implements BukkitScheduler { + task.setNext(null); + } + this.head = lastTask; ++ MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); + } + + private boolean isReady(final int currentTick) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 09aa6809c5400ce8548ac902908b750ce7c964ec..3c96807e97657502849093e4371e9fef3584a346 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -1,12 +1,15 @@ + package org.bukkit.craftbukkit.scheduler; + + import java.util.function.Consumer; ++ ++import co.aikar.timings.NullTimingHandler; + import org.bukkit.Bukkit; + import org.bukkit.plugin.Plugin; + import org.bukkit.scheduler.BukkitTask; + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.spigotmc.CustomTimingsHandler; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + public class CraftTask implements BukkitTask, Runnable { // Spigot + +@@ -26,12 +29,12 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + */ + private volatile long period; + private long nextRun; +- private final Runnable rTask; +- private final Consumer cTask; ++ public final Runnable rTask; // Paper ++ public final Consumer cTask; // Paper ++ public Timing timings; // Paper + private final Plugin plugin; + private final int id; + +- final CustomTimingsHandler timings; // Spigot + CraftTask() { + this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } +@@ -51,7 +54,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + this.id = id; + this.period = CraftTask.NO_REPEATING; + this.taskName = taskName; +- this.timings = null; // Will be changed in later patch ++ this.timings = MinecraftTimings.getInternalTaskName(taskName); + } + // Paper end + +@@ -72,7 +75,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + } + this.id = id; + this.period = period; +- this.timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot ++ timings = task != null ? MinecraftTimings.getPluginTaskTimings(this, period) : NullTimingHandler.NULL; // Paper + } + + @Override +@@ -92,11 +95,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + + @Override + public void run() { ++ try (Timing ignored = timings.startTiming()) { // Paper + if (rTask != null) { + rTask.run(); + } else { + cTask.accept(this); + } ++ } // Paper + } + + long getPeriod() { +@@ -123,7 +128,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + this.next = next; + } + +- Class getTaskClass() { ++ public Class getTaskClass() { + return (rTask != null) ? rTask.getClass() : ((cTask != null) ? cTask.getClass() : null); + } + +@@ -147,9 +152,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + return true; + } + +- // Spigot start +- public String getTaskName() { +- return (getTaskClass() == null) ? "Unknown" : getTaskClass().getName(); +- } +- // Spigot end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +index e52ef47b783785dc214746b678e7b549aea9a274..3d90b3426873a3528af14f7f1ab0adae0027da2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +@@ -5,6 +5,7 @@ import org.bukkit.util.CachedServerIcon; + public class CraftIconCache implements CachedServerIcon { + public final String value; + ++ public String getData() { return value; } // Paper + public CraftIconCache(final String value) { + this.value = value; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ec3d663886eb134eb9ecc87e7517a73bcfb9ec02..ef7715774fbdc4c42b217d8192784e09a43fe66f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -156,6 +156,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftNamespacedKey.toMinecraft(mat.getKey()); + } + // ======================================================================== ++ // Paper start ++ @Override ++ public void reportTimings() { ++ co.aikar.timings.TimingsExport.reportTimings(); ++ } ++ // Paper end + + public static byte toLegacyData(BlockState data) { + return CraftLegacy.toLegacyData(data); +@@ -330,6 +336,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + return clazz; + } + ++ // Paper start ++ @Override ++ public String getTimingsServerName() { ++ return com.destroystokyo.paper.PaperConfig.timingsServerName; ++ } ++ // Paper end ++ + /** + * This helper class represents the different NBT Tags. + *

+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index ac6fc546931f7884952058b42e7e3fab3ce42998..9bb35ec64e1538aabec9ff7831706c4717239449 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -29,7 +29,7 @@ import net.minecraft.world.entity.raid.Raider; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.phys.AABB; +-import org.bukkit.craftbukkit.SpigotTimings; ++import co.aikar.timings.MinecraftTimings; + + public class ActivationRange + { +@@ -73,8 +73,8 @@ public class ActivationRange + /** + * These entities are excluded from Activation range checks. + * +- * @param entity +- * @param config ++ * @param entity Entity to initialize ++ * @param config Spigot config to determine ranges + * @return boolean If it should always tick. + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) +@@ -109,7 +109,7 @@ public class ActivationRange + */ + public static void activateEntities(Level world) + { +- SpigotTimings.entityActivationCheckTimer.startTiming(); ++ MinecraftTimings.entityActivationCheckTimer.startTiming(); + final int miscActivationRange = world.spigotConfig.miscActivationRange; + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; +@@ -146,7 +146,7 @@ public class ActivationRange + } + } + } +- SpigotTimings.entityActivationCheckTimer.stopTiming(); ++ MinecraftTimings.entityActivationCheckTimer.stopTiming(); + } + + /** +@@ -243,10 +243,8 @@ public class ActivationRange + */ + public static boolean checkIfActive(Entity entity) + { +- SpigotTimings.checkIfActiveTimer.startTiming(); + // Never safe to skip fireworks or entities not yet added to chunk + if ( !entity.inChunk || entity instanceof FireworkRocketEntity ) { +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return true; + } + +@@ -270,7 +268,6 @@ public class ActivationRange + { + isActive = false; + } +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return isActive; + } + } diff --git a/Remapped-Spigot-Server-Patches/0010-Adventure.patch b/Remapped-Spigot-Server-Patches/0010-Adventure.patch new file mode 100644 index 000000000..4ad53cc5f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0010-Adventure.patch @@ -0,0 +1,3238 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Fri, 29 Jan 2021 17:54:03 +0100 +Subject: [PATCH] Adventure + +Co-authored-by: zml +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 1d03a79e9010bc514b72a81ba0ad4a62aeff1bb7..429b74474ced04d8dd8f038b8590b8dfe178bf4d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -217,4 +217,9 @@ public class PaperConfig { + " - Length: " + timeSummary(Timings.getHistoryLength() / 20) + + " - Server Name: " + timingsServerName); + } ++ ++ public static boolean useDisplayNameInQuit = false; ++ private static void useDisplayNameInQuit() { ++ useDisplayNameInQuit = getBoolean("use-display-name-in-quit-message", useDisplayNameInQuit); ++ } + } +diff --git a/src/main/java/io/papermc/paper/adventure/AdventureComponent.java b/src/main/java/io/papermc/paper/adventure/AdventureComponent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4b4cec054ad0146773b722c7e3708f988aeeb76d +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/AdventureComponent.java +@@ -0,0 +1,76 @@ ++package io.papermc.paper.adventure; ++ ++import com.google.gson.JsonElement; ++import com.google.gson.JsonSerializationContext; ++import com.google.gson.JsonSerializer; ++import java.lang.reflect.Type; ++import java.util.List; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextComponent; ++import net.minecraft.network.chat.MutableComponent; ++import net.minecraft.network.chat.Style; ++import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public final class AdventureComponent implements net.minecraft.network.chat.Component { ++ final Component wrapped; ++ private @MonotonicNonNull net.minecraft.network.chat.Component converted; ++ ++ public AdventureComponent(final Component wrapped) { ++ this.wrapped = wrapped; ++ } ++ ++ public net.minecraft.network.chat.Component deepConverted() { ++ net.minecraft.network.chat.Component converted = this.converted; ++ if (converted == null) { ++ converted = PaperAdventure.WRAPPER_AWARE_SERIALIZER.serialize(this.wrapped); ++ this.converted = converted; ++ } ++ return converted; ++ } ++ ++ public @Nullable net.minecraft.network.chat.Component deepConvertedIfPresent() { ++ return this.converted; ++ } ++ ++ @Override ++ public Style getStyle() { ++ return this.deepConverted().getStyle(); ++ } ++ ++ @Override ++ public String getContents() { ++ if (this.wrapped instanceof TextComponent) { ++ return ((TextComponent) this.wrapped).content(); ++ } else { ++ return this.deepConverted().getContents(); ++ } ++ } ++ ++ @Override ++ public String getString() { ++ return PaperAdventure.PLAIN.serialize(this.wrapped); ++ } ++ ++ @Override ++ public List getSiblings() { ++ return this.deepConverted().getSiblings(); ++ } ++ ++ @Override ++ public MutableComponent plainCopy() { ++ return this.deepConverted().plainCopy(); ++ } ++ ++ @Override ++ public MutableComponent copy() { ++ return this.deepConverted().copy(); ++ } ++ ++ public static class Serializer implements JsonSerializer { ++ @Override ++ public JsonElement serialize(final AdventureComponent src, final Type type, final JsonSerializationContext context) { ++ return PaperAdventure.GSON.serializer().toJsonTree(src.wrapped, Component.class); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a29b6aaafd529e56a83dd96c32211f21e4aad348 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -0,0 +1,214 @@ ++package io.papermc.paper.adventure; ++ ++import io.papermc.paper.chat.ChatRenderer; ++import io.papermc.paper.event.player.AbstractChatEvent; ++import io.papermc.paper.event.player.AsyncChatEvent; ++import io.papermc.paper.event.player.ChatEvent; ++import java.util.Set; ++import java.util.concurrent.ExecutionException; ++import java.util.function.Consumer; ++import java.util.regex.Pattern; ++ ++import net.kyori.adventure.audience.Audience; ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextReplacementConfig; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerChatEvent; ++ ++public final class ChatProcessor { ++ // <-- copied from adventure-text-serializer-legacy ++ private static final Pattern DEFAULT_URL_PATTERN = Pattern.compile("(?:(https?)://)?([-\\w_.]+\\.\\w{2,})(/\\S*)?"); ++ private static final Pattern URL_SCHEME_PATTERN = Pattern.compile("^[a-z][a-z0-9+\\-.]*:"); ++ private static final TextReplacementConfig URL_REPLACEMENT_CONFIG = TextReplacementConfig.builder() ++ .match(DEFAULT_URL_PATTERN) ++ .replacement(url -> { ++ String clickUrl = url.content(); ++ if (!URL_SCHEME_PATTERN.matcher(clickUrl).find()) { ++ clickUrl = "http://" + clickUrl; ++ } ++ return url.clickEvent(ClickEvent.openUrl(clickUrl)); ++ }) ++ .build(); ++ // copied from adventure-text-serializer-legacy --> ++ final MinecraftServer server; ++ final ServerPlayer player; ++ final String message; ++ final boolean async; ++ final Component originalMessage; ++ ++ public ChatProcessor(final MinecraftServer server, final ServerPlayer player, final String message, final boolean async) { ++ this.server = server; ++ this.player = player; ++ this.message = message; ++ this.async = async; ++ this.originalMessage = Component.text(message); ++ } ++ ++ @SuppressWarnings({"CodeBlock2Expr", "deprecated"}) ++ public void process() { ++ this.processingLegacyFirst( ++ // continuing from AsyncPlayerChatEvent (without PlayerChatEvent) ++ event -> { ++ this.processModern( ++ legacyRenderer(event.getFormat()), ++ event.getRecipients(), ++ PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), ++ event.isCancelled() ++ ); ++ }, ++ // continuing from AsyncPlayerChatEvent and PlayerChatEvent ++ event -> { ++ this.processModern( ++ legacyRenderer(event.getFormat()), ++ event.getRecipients(), ++ PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), ++ event.isCancelled() ++ ); ++ }, ++ // no legacy events called, all nice and fresh! ++ () -> { ++ this.processModern( ++ ChatRenderer.defaultRenderer(), ++ new LazyPlayerSet(this.server), ++ Component.text(this.message).replaceText(URL_REPLACEMENT_CONFIG), ++ false ++ ); ++ } ++ ); ++ } ++ ++ @SuppressWarnings("deprecation") ++ private void processingLegacyFirst( ++ final Consumer continueAfterAsync, ++ final Consumer continueAfterAsyncAndSync, ++ final Runnable modernOnly ++ ) { ++ final boolean listenersOnAsyncEvent = anyListeners(AsyncPlayerChatEvent.getHandlerList()); ++ final boolean listenersOnSyncEvent = anyListeners(PlayerChatEvent.getHandlerList()); ++ if (listenersOnAsyncEvent || listenersOnSyncEvent) { ++ final CraftPlayer player = this.player.getBukkitEntity(); ++ final AsyncPlayerChatEvent ae = new AsyncPlayerChatEvent(this.async, player, this.message, new LazyPlayerSet(this.server)); ++ post(ae); ++ if (listenersOnSyncEvent) { ++ final PlayerChatEvent se = new PlayerChatEvent(player, ae.getMessage(), ae.getFormat(), ae.getRecipients()); ++ se.setCancelled(ae.isCancelled()); // propagate cancelled state ++ this.queueIfAsyncOrRunImmediately(new Waitable() { ++ @Override ++ protected Void evaluate() { ++ post(se); ++ return null; ++ } ++ }); ++ continueAfterAsyncAndSync.accept(se); ++ } else { ++ continueAfterAsync.accept(ae); ++ } ++ } else { ++ modernOnly.run(); ++ } ++ } ++ ++ private void processModern(final ChatRenderer renderer, final Set recipients, final Component message, final boolean cancelled) { ++ final AsyncChatEvent ae = this.createAsync(renderer, recipients, new LazyChatAudienceSet(), message); ++ ae.setCancelled(cancelled); // propagate cancelled state ++ post(ae); ++ final boolean listenersOnSyncEvent = anyListeners(ChatEvent.getHandlerList()); ++ if (listenersOnSyncEvent) { ++ this.continueWithSyncFromWhereAsyncLeftOff(ae); ++ } else { ++ this.complete(ae); ++ } ++ } ++ ++ private void continueWithSyncFromWhereAsyncLeftOff(final AsyncChatEvent ae) { ++ this.queueIfAsyncOrRunImmediately(new Waitable() { ++ @Override ++ protected Void evaluate() { ++ final ChatEvent se = ChatProcessor.this.createSync(ae.renderer(), ae.recipients(), ae.viewers(), ae.message()); ++ se.setCancelled(ae.isCancelled()); // propagate cancelled state ++ post(se); ++ ChatProcessor.this.complete(se); ++ return null; ++ } ++ }); ++ } ++ ++ private void complete(final AbstractChatEvent event) { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ final CraftPlayer player = this.player.getBukkitEntity(); ++ final Component displayName = displayName(player); ++ final Component message = event.message(); ++ final ChatRenderer renderer = event.renderer(); ++ ++ final Set viewers = event.viewers(); ++ final Set recipients = event.recipients(); ++ if (viewers instanceof LazyChatAudienceSet && recipients instanceof LazyPlayerSet && ++ (!((LazyChatAudienceSet) viewers).isLazy() || ((LazyPlayerSet) recipients).isLazy())) { ++ for (final Audience viewer : viewers) { ++ viewer.sendMessage(player, renderer.render(player, displayName, message, viewer), MessageType.CHAT); ++ } ++ } else { ++ this.server.console.sendMessage(player, renderer.render(player, displayName, message, this.server.console), MessageType.CHAT); ++ for (final Player recipient : recipients) { ++ recipient.sendMessage(player, renderer.render(player, displayName, message, recipient), MessageType.CHAT); ++ } ++ } ++ } ++ ++ private AsyncChatEvent createAsync(final ChatRenderer renderer, final Set recipients, final Set viewers, final Component message) { ++ return new AsyncChatEvent(this.async, this.player.getBukkitEntity(), recipients, viewers, renderer, message, this.originalMessage); ++ } ++ ++ private ChatEvent createSync(final ChatRenderer renderer, final Set recipients, final Set viewers, final Component message) { ++ return new ChatEvent(this.player.getBukkitEntity(), recipients, viewers, renderer, message, this.originalMessage); ++ } ++ ++ private static String legacyDisplayName(final CraftPlayer player) { ++ return player.getDisplayName(); ++ } ++ ++ private static Component displayName(final CraftPlayer player) { ++ return player.displayName(); ++ } ++ ++ private static ChatRenderer legacyRenderer(final String format) { ++ return (player, displayName, message, recipient) -> PaperAdventure.LEGACY_SECTION_UXRC.deserialize(String.format(format, legacyDisplayName((CraftPlayer) player), PaperAdventure.LEGACY_SECTION_UXRC.serialize(message))).replaceText(URL_REPLACEMENT_CONFIG); ++ } ++ ++ private void queueIfAsyncOrRunImmediately(final Waitable waitable) { ++ if (this.async) { ++ this.server.processQueue.add(waitable); ++ } else { ++ waitable.run(); ++ } ++ try { ++ waitable.get(); ++ } catch (final InterruptedException e) { ++ Thread.currentThread().interrupt(); // tag, you're it ++ } catch (final ExecutionException e) { ++ throw new RuntimeException("Exception processing chat", e.getCause()); ++ } ++ } ++ ++ private static void post(final Event event) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ ++ private static boolean anyListeners(final HandlerList handlers) { ++ return handlers.getRegisteredListeners().length > 0; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/DisplayNames.java b/src/main/java/io/papermc/paper/adventure/DisplayNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bfaf5d3c5aae8a587c2b11d90089c588b2a2aba0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/DisplayNames.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.adventure; ++ ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.ChatColor; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++ ++public final class DisplayNames { ++ private DisplayNames() { ++ } ++ ++ public static String getLegacy(final CraftPlayer player) { ++ return getLegacy(player.getHandle()); ++ } ++ ++ public static String getLegacy(final ServerPlayer player) { ++ final String legacy = player.displayName; ++ if (legacy != null) { ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(player.adventure$displayName) + ChatColor.getLastColors(player.displayName); ++ } ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(player.adventure$displayName); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java b/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..10f08e2b73610ab06928d1f63348920fef8e91fa +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.adventure; ++ ++import net.kyori.adventure.audience.Audience; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.LazyHashSet; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.entity.Player; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++final class LazyChatAudienceSet extends LazyHashSet { ++ @Override ++ protected Set makeReference() { ++ final Set playerSet = LazyPlayerSet.makePlayerSet(MinecraftServer.getServer()); ++ final HashSet audiences = new HashSet<>(playerSet); ++ audiences.add(Bukkit.getConsoleSender()); ++ return audiences; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java b/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eeedc30a45d9637d68f04f185b3dd90dd711b9e0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java +@@ -0,0 +1,88 @@ ++package io.papermc.paper.adventure; ++ ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.io.IOException; ++import java.util.UUID; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.nbt.api.BinaryTagHolder; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.HoverEvent; ++import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; ++import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; ++import net.kyori.adventure.util.Codec; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.Tag; ++import net.minecraft.nbt.TagParser; ++ ++final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer { ++ public static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer(); ++ private static final Codec SNBT_CODEC = Codec.of(TagParser::parseTag, Tag::toString); ++ ++ static final String ITEM_TYPE = "id"; ++ static final String ITEM_COUNT = "Count"; ++ static final String ITEM_TAG = "tag"; ++ ++ static final String ENTITY_NAME = "name"; ++ static final String ENTITY_TYPE = "type"; ++ static final String ENTITY_ID = "id"; ++ ++ NBTLegacyHoverEventSerializer() { ++ } ++ ++ @Override ++ public HoverEvent.ShowItem deserializeShowItem(final Component input) throws IOException { ++ final String raw = PlainComponentSerializer.plain().serialize(input); ++ try { ++ final CompoundTag contents = SNBT_CODEC.decode(raw); ++ final CompoundTag tag = contents.getCompound(ITEM_TAG); ++ return HoverEvent.ShowItem.of( ++ Key.key(contents.getString(ITEM_TYPE)), ++ contents.contains(ITEM_COUNT) ? contents.getByte(ITEM_COUNT) : 1, ++ tag.isEmpty() ? null : BinaryTagHolder.encode(tag, SNBT_CODEC) ++ ); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ ++ @Override ++ public HoverEvent.ShowEntity deserializeShowEntity(final Component input, final Codec.Decoder componentCodec) throws IOException { ++ final String raw = PlainComponentSerializer.plain().serialize(input); ++ try { ++ final CompoundTag contents = SNBT_CODEC.decode(raw); ++ return HoverEvent.ShowEntity.of( ++ Key.key(contents.getString(ENTITY_TYPE)), ++ UUID.fromString(contents.getString(ENTITY_ID)), ++ componentCodec.decode(contents.getString(ENTITY_NAME)) ++ ); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ ++ @Override ++ public Component serializeShowItem(final HoverEvent.ShowItem input) throws IOException { ++ final CompoundTag tag = new CompoundTag(); ++ tag.putString(ITEM_TYPE, input.item().asString()); ++ tag.putByte(ITEM_COUNT, (byte) input.count()); ++ if (input.nbt() != null) { ++ try { ++ tag.put(ITEM_TAG, input.nbt().get(SNBT_CODEC)); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ return Component.text(SNBT_CODEC.encode(tag)); ++ } ++ ++ @Override ++ public Component serializeShowEntity(final HoverEvent.ShowEntity input, final Codec.Encoder componentCodec) throws IOException { ++ final CompoundTag tag = new CompoundTag(); ++ tag.putString(ENTITY_ID, input.id().toString()); ++ tag.putString(ENTITY_TYPE, input.type().asString()); ++ if (input.name() != null) { ++ tag.putString(ENTITY_NAME, componentCodec.encode(input.name())); ++ } ++ return Component.text(SNBT_CODEC.encode(tag)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +new file mode 100644 +index 0000000000000000000000000000000000000000..696b7ad89af4e379d7b8f1961d1ab0cba1c3313f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +@@ -0,0 +1,342 @@ ++package io.papermc.paper.adventure; ++ ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import io.netty.util.AttributeKey; ++import java.io.IOException; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Locale; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import net.kyori.adventure.bossbar.BossBar; ++import net.kyori.adventure.inventory.Book; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.nbt.api.BinaryTagHolder; ++import net.kyori.adventure.sound.Sound; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TranslatableComponent; ++import net.kyori.adventure.text.flattener.ComponentFlattener; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; ++import net.kyori.adventure.translation.GlobalTranslator; ++import net.kyori.adventure.util.Codec; ++import net.minecraft.ChatFormatting; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.TagParser; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.sounds.SoundSource; ++import net.minecraft.world.BossEvent; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.ChatColor; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public final class PaperAdventure { ++ public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); ++ private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s"); ++ public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder() ++ .complexMapper(TranslatableComponent.class, (translatable, consumer) -> { ++ final @NonNull String translated = LocaleLanguage.a().a(translatable.key()); ++ ++ final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated); ++ final List args = translatable.args(); ++ int argPosition = 0; ++ int lastIdx = 0; ++ while (matcher.find()) { ++ // append prior ++ if (lastIdx < matcher.start()) { ++ consumer.accept(Component.text(translated.substring(lastIdx, matcher.start()))); ++ } ++ lastIdx = matcher.end(); ++ ++ final @Nullable String argIdx = matcher.group(1); ++ // calculate argument position ++ if (argIdx != null) { ++ try { ++ final int idx = Integer.parseInt(argIdx) - 1; ++ if (idx < args.size()) { ++ consumer.accept(args.get(idx)); ++ } ++ } catch (final NumberFormatException ex) { ++ // ignore, drop the format placeholder ++ } ++ } else { ++ final int idx = argPosition++; ++ if (idx < args.size()) { ++ consumer.accept(args.get(idx)); ++ } ++ } ++ } ++ ++ // append tail ++ if (lastIdx < translated.length()) { ++ consumer.accept(Component.text(translated.substring(lastIdx))); ++ } ++ }) ++ .build(); ++ public static final LegacyComponentSerializer LEGACY_SECTION_UXRC = LegacyComponentSerializer.builder().flattener(FLATTENER).hexColors().useUnusualXRepeatedCharacterHexFormat().build(); ++ public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); ++ public static final GsonComponentSerializer GSON = GsonComponentSerializer.builder() ++ .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) ++ .build(); ++ public static final GsonComponentSerializer COLOR_DOWNSAMPLING_GSON = GsonComponentSerializer.builder() ++ .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) ++ .downsampleColors() ++ .build(); ++ private static final Codec NBT_CODEC = new Codec() { ++ @Override ++ public @NonNull CompoundTag decode(final @NonNull String encoded) throws IOException { ++ try { ++ return TagParser.parseTag(encoded); ++ } catch (final CommandSyntaxException e) { ++ throw new IOException(e); ++ } ++ } ++ ++ @Override ++ public @NonNull String encode(final @NonNull CompoundTag decoded) { ++ return decoded.toString(); ++ } ++ }; ++ static final WrapperAwareSerializer WRAPPER_AWARE_SERIALIZER = new WrapperAwareSerializer(); ++ ++ private PaperAdventure() { ++ } ++ ++ // Key ++ ++ public static ResourceLocation asVanilla(final Key key) { ++ return new ResourceLocation(key.namespace(), key.value()); ++ } ++ ++ public static ResourceLocation asVanillaNullable(final Key key) { ++ if (key == null) { ++ return null; ++ } ++ return new ResourceLocation(key.namespace(), key.value()); ++ } ++ ++ // Component ++ ++ public static Component asAdventure(final net.minecraft.network.chat.Component component) { ++ return component == null ? Component.empty() : GSON.serializer().fromJson(net.minecraft.network.chat.Component.Serializer.toJsonTree(component), Component.class); ++ } ++ ++ public static ArrayList asAdventure(final List vanillas) { ++ final ArrayList adventures = new ArrayList<>(vanillas.size()); ++ for (final net.minecraft.network.chat.Component vanilla : vanillas) { ++ adventures.add(asAdventure(vanilla)); ++ } ++ return adventures; ++ } ++ ++ public static ArrayList asAdventureFromJson(final List jsonStrings) { ++ final ArrayList adventures = new ArrayList<>(jsonStrings.size()); ++ for (final String json : jsonStrings) { ++ adventures.add(GsonComponentSerializer.gson().deserialize(json)); ++ } ++ return adventures; ++ } ++ ++ public static List asJson(final List adventures) { ++ final List jsons = new ArrayList<>(adventures.size()); ++ for (final Component component : adventures) { ++ jsons.add(GsonComponentSerializer.gson().serialize(component)); ++ } ++ return jsons; ++ } ++ ++ public static net.minecraft.network.chat.Component asVanilla(final Component component) { ++ if (true) return new AdventureComponent(component); ++ return net.minecraft.network.chat.Component.Serializer.fromJsonTree(GSON.serializer().toJsonTree(component)); ++ } ++ ++ public static List asVanilla(final List adventures) { ++ final List vanillas = new ArrayList<>(adventures.size()); ++ for (final Component adventure : adventures) { ++ vanillas.add(asVanilla(adventure)); ++ } ++ return vanillas; ++ } ++ ++ public static String asJsonString(final Component component, final Locale locale) { ++ return GSON.serialize( ++ GlobalTranslator.render( ++ component, ++ // play it safe ++ locale != null ++ ? locale ++ : Locale.US ++ ) ++ ); ++ } ++ ++ public static String asJsonString(final net.minecraft.network.chat.Component component, final Locale locale) { ++ if (component instanceof AdventureComponent) { ++ return asJsonString(((AdventureComponent) component).wrapped, locale); ++ } ++ return net.minecraft.network.chat.Component.Serializer.componentToJson(component); ++ } ++ ++ // thank you for being worse than wet socks, Bukkit ++ public static String superHackyLegacyRepresentationOfComponent(final Component component, final String string) { ++ return LEGACY_SECTION_UXRC.serialize(component) + ChatColor.getLastColors(string); ++ } ++ ++ // BossBar ++ ++ public static BossEvent.BossBarColor asVanilla(final BossBar.Color color) { ++ if (color == BossBar.Color.PINK) { ++ return BossEvent.BossBarColor.PINK; ++ } else if (color == BossBar.Color.BLUE) { ++ return BossEvent.BossBarColor.BLUE; ++ } else if (color == BossBar.Color.RED) { ++ return BossEvent.BossBarColor.RED; ++ } else if (color == BossBar.Color.GREEN) { ++ return BossEvent.BossBarColor.GREEN; ++ } else if (color == BossBar.Color.YELLOW) { ++ return BossEvent.BossBarColor.YELLOW; ++ } else if (color == BossBar.Color.PURPLE) { ++ return BossEvent.BossBarColor.PURPLE; ++ } else if (color == BossBar.Color.WHITE) { ++ return BossEvent.BossBarColor.WHITE; ++ } ++ throw new IllegalArgumentException(color.name()); ++ } ++ ++ public static BossBar.Color asAdventure(final BossEvent.BossBarColor color) { ++ if(color == BossEvent.BossBarColor.PINK) { ++ return BossBar.Color.PINK; ++ } else if(color == BossEvent.BossBarColor.BLUE) { ++ return BossBar.Color.BLUE; ++ } else if(color == BossEvent.BossBarColor.RED) { ++ return BossBar.Color.RED; ++ } else if(color == BossEvent.BossBarColor.GREEN) { ++ return BossBar.Color.GREEN; ++ } else if(color == BossEvent.BossBarColor.YELLOW) { ++ return BossBar.Color.YELLOW; ++ } else if(color == BossEvent.BossBarColor.PURPLE) { ++ return BossBar.Color.PURPLE; ++ } else if(color == BossEvent.BossBarColor.WHITE) { ++ return BossBar.Color.WHITE; ++ } ++ throw new IllegalArgumentException(color.name()); ++ } ++ ++ public static BossEvent.BossBarOverlay asVanilla(final BossBar.Overlay overlay) { ++ if (overlay == BossBar.Overlay.PROGRESS) { ++ return BossEvent.BossBarOverlay.PROGRESS; ++ } else if (overlay == BossBar.Overlay.NOTCHED_6) { ++ return BossEvent.BossBarOverlay.NOTCHED_6; ++ } else if (overlay == BossBar.Overlay.NOTCHED_10) { ++ return BossEvent.BossBarOverlay.NOTCHED_10; ++ } else if (overlay == BossBar.Overlay.NOTCHED_12) { ++ return BossEvent.BossBarOverlay.NOTCHED_12; ++ } else if (overlay == BossBar.Overlay.NOTCHED_20) { ++ return BossEvent.BossBarOverlay.NOTCHED_20; ++ } ++ throw new IllegalArgumentException(overlay.name()); ++ } ++ ++ public static BossBar.Overlay asAdventure(final BossEvent.BossBarOverlay overlay) { ++ if (overlay == BossEvent.BossBarOverlay.PROGRESS) { ++ return BossBar.Overlay.PROGRESS; ++ } else if (overlay == BossEvent.BossBarOverlay.NOTCHED_6) { ++ return BossBar.Overlay.NOTCHED_6; ++ } else if (overlay == BossEvent.BossBarOverlay.NOTCHED_10) { ++ return BossBar.Overlay.NOTCHED_10; ++ } else if (overlay == BossEvent.BossBarOverlay.NOTCHED_12) { ++ return BossBar.Overlay.NOTCHED_12; ++ } else if (overlay == BossEvent.BossBarOverlay.NOTCHED_20) { ++ return BossBar.Overlay.NOTCHED_20; ++ } ++ throw new IllegalArgumentException(overlay.name()); ++ } ++ ++ public static void setFlag(final BossBar bar, final BossBar.Flag flag, final boolean value) { ++ if (value) { ++ bar.addFlag(flag); ++ } else { ++ bar.removeFlag(flag); ++ } ++ } ++ ++ // Book ++ ++ public static ItemStack asItemStack(final Book book, final Locale locale) { ++ final ItemStack item = new ItemStack(net.minecraft.world.item.Items.WRITTEN_BOOK, 1); ++ final CompoundTag tag = item.getOrCreateTag(); ++ tag.putString("title", asJsonString(book.title(), locale)); ++ tag.putString("author", asJsonString(book.author(), locale)); ++ final ListTag pages = new ListTag(); ++ for (final Component page : book.pages()) { ++ pages.add(StringTag.create(asJsonString(page, locale))); ++ } ++ tag.put("pages", pages); ++ return item; ++ } ++ ++ // Sounds ++ ++ public static SoundSource asVanilla(final Sound.Source source) { ++ if (source == Sound.Source.MASTER) { ++ return SoundSource.MASTER; ++ } else if (source == Sound.Source.MUSIC) { ++ return SoundSource.MUSIC; ++ } else if (source == Sound.Source.RECORD) { ++ return SoundSource.RECORDS; ++ } else if (source == Sound.Source.WEATHER) { ++ return SoundSource.WEATHER; ++ } else if (source == Sound.Source.BLOCK) { ++ return SoundSource.BLOCKS; ++ } else if (source == Sound.Source.HOSTILE) { ++ return SoundSource.HOSTILE; ++ } else if (source == Sound.Source.NEUTRAL) { ++ return SoundSource.NEUTRAL; ++ } else if (source == Sound.Source.PLAYER) { ++ return SoundSource.PLAYERS; ++ } else if (source == Sound.Source.AMBIENT) { ++ return SoundSource.AMBIENT; ++ } else if (source == Sound.Source.VOICE) { ++ return SoundSource.VOICE; ++ } ++ throw new IllegalArgumentException(source.name()); ++ } ++ ++ public static @Nullable SoundSource asVanillaNullable(final Sound.@Nullable Source source) { ++ if (source == null) { ++ return null; ++ } ++ return asVanilla(source); ++ } ++ ++ // NBT ++ ++ public static @Nullable BinaryTagHolder asBinaryTagHolder(final @Nullable CompoundTag tag) { ++ if (tag == null) { ++ return null; ++ } ++ try { ++ return BinaryTagHolder.encode(tag, NBT_CODEC); ++ } catch (final IOException e) { ++ return null; ++ } ++ } ++ ++ // Colors ++ ++ public static @NonNull TextColor asAdventure(ChatFormatting minecraftColor) { ++ if (minecraftColor.getColor() == null) { ++ throw new IllegalArgumentException("Not a valid color"); ++ } ++ return TextColor.color(minecraftColor.getColor()); ++ } ++ ++ public static @Nullable ChatFormatting asVanilla(TextColor color) { ++ return ChatFormatting.getByHexValue(color.value()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee6d9d5c072d68cace63068a8e2ed603ad475378 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java +@@ -0,0 +1,41 @@ ++package io.papermc.paper.adventure; ++ ++import java.util.Set; ++import java.util.function.Consumer; ++import net.kyori.adventure.bossbar.BossBar; ++import net.kyori.adventure.text.Component; ++import net.minecraft.network.protocol.game.ClientboundBossEventPacket; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++public final class VanillaBossBarListener implements BossBar.Listener { ++ private final Consumer action; ++ ++ public VanillaBossBarListener(final Consumer action) { ++ this.action = action; ++ } ++ ++ @Override ++ public void bossBarNameChanged(final @NonNull BossBar bar, final @NonNull Component oldName, final @NonNull Component newName) { ++ this.action.accept(ClientboundBossEventPacket.Operation.UPDATE_NAME); ++ } ++ ++ @Override ++ public void bossBarProgressChanged(final @NonNull BossBar bar, final float oldProgress, final float newProgress) { ++ this.action.accept(ClientboundBossEventPacket.Operation.UPDATE_PCT); ++ } ++ ++ @Override ++ public void bossBarColorChanged(final @NonNull BossBar bar, final BossBar.@NonNull Color oldColor, final BossBar.@NonNull Color newColor) { ++ this.action.accept(ClientboundBossEventPacket.Operation.UPDATE_STYLE); ++ } ++ ++ @Override ++ public void bossBarOverlayChanged(final @NonNull BossBar bar, final BossBar.@NonNull Overlay oldOverlay, final BossBar.@NonNull Overlay newOverlay) { ++ this.action.accept(ClientboundBossEventPacket.Operation.UPDATE_STYLE); ++ } ++ ++ @Override ++ public void bossBarFlagsChanged(final @NonNull BossBar bar, final @NonNull Set flagsAdded, final @NonNull Set flagsRemoved) { ++ this.action.accept(ClientboundBossEventPacket.Operation.UPDATE_PROPERTIES); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2e93ac0eb74a89c020f3356f77320cf6459727fd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java +@@ -0,0 +1,19 @@ ++package io.papermc.paper.adventure; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.ComponentSerializer; ++ ++final class WrapperAwareSerializer implements ComponentSerializer { ++ @Override ++ public Component deserialize(final net.minecraft.network.chat.Component input) { ++ if (input instanceof AdventureComponent) { ++ return ((AdventureComponent) input).wrapped; ++ } ++ return PaperAdventure.GSON.serializer().fromJson(net.minecraft.network.chat.Component.Serializer.toJsonTree(input), Component.class); ++ } ++ ++ @Override ++ public net.minecraft.network.chat.Component serialize(final Component component) { ++ return net.minecraft.network.chat.Component.Serializer.fromJsonTree(PaperAdventure.GSON.serializer().toJsonTree(component)); ++ } ++} +diff --git a/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2dc92d8d2764d3e9b621d5c7d5e30c30367b3117 +--- /dev/null ++++ b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java +@@ -0,0 +1,36 @@ ++package net.kyori.adventure.bossbar; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.adventure.VanillaBossBarListener; ++import net.minecraft.server.level.ServerBossEvent; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++ ++public abstract class HackyBossBarPlatformBridge { ++ public ServerBossEvent vanilla$bar; ++ private VanillaBossBarListener vanilla$listener; ++ ++ public final void paper$playerShow(final CraftPlayer player) { ++ if (this.vanilla$bar == null) { ++ final BossBar $this = (BossBar) this; ++ this.vanilla$bar = new ServerBossEvent( ++ PaperAdventure.asVanilla($this.name()), ++ PaperAdventure.asVanilla($this.color()), ++ PaperAdventure.asVanilla($this.overlay()) ++ ); ++ this.vanilla$bar.adventure = $this; ++ this.vanilla$listener = new VanillaBossBarListener(this.vanilla$bar::broadcast); ++ $this.addListener(this.vanilla$listener); ++ } ++ this.vanilla$bar.addPlayer(player.getHandle()); ++ } ++ ++ public final void paper$playerHide(final CraftPlayer player) { ++ if (this.vanilla$bar != null) { ++ this.vanilla$bar.removePlayer(player.getHandle()); ++ if (this.vanilla$bar.getPlayers().isEmpty()) { ++ ((BossBar) this).removeListener(this.vanilla$listener); ++ this.vanilla$bar = null; ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/ChatFormatting.java b/src/main/java/net/minecraft/ChatFormatting.java +index e888a90226c30757b190a3b89a56c1a312f32850..1720eba650c959810c6e4a3522979e75e10a1bb8 100644 +--- a/src/main/java/net/minecraft/ChatFormatting.java ++++ b/src/main/java/net/minecraft/ChatFormatting.java +@@ -61,6 +61,7 @@ public enum ChatFormatting { + return !this.isFormat && this != ChatFormatting.RESET; + } + ++ @Nullable public Integer getHexValue() { return this.getColor(); } // Paper - OBFHELPER + @Nullable + public Integer getColor() { + return this.color; +@@ -84,6 +85,18 @@ public enum ChatFormatting { + return name == null ? null : (ChatFormatting) ChatFormatting.FORMATTING_BY_NAME.get(cleanName(name)); + } + ++ // Paper start ++ @Nullable public static ChatFormatting getByHexValue(int i) { ++ for (ChatFormatting value : values()) { ++ if (value.getHexValue() != null && value.getHexValue() == i) { ++ return value; ++ } ++ } ++ ++ return null; ++ } ++ // Paper end ++ + @Nullable + public static ChatFormatting getById(int colorIndex) { + if (colorIndex < 0) { +diff --git a/src/main/java/net/minecraft/nbt/StringTag.java b/src/main/java/net/minecraft/nbt/StringTag.java +index 620e10b5ba9e57ff4d0d7967bf4240fafadb2be7..dab8983fa65b97ecf0fe20fdc06cc8ec60d8fe95 100644 +--- a/src/main/java/net/minecraft/nbt/StringTag.java ++++ b/src/main/java/net/minecraft/nbt/StringTag.java +@@ -43,6 +43,7 @@ public class StringTag implements Tag { + this.data = value; + } + ++ public static StringTag create(final String value) { return valueOf(value); } // Paper - OBFHELPER + public static StringTag valueOf(String value) { + return value.isEmpty() ? StringTag.EMPTY : new StringTag(value); + } +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index 50f14acb062c2f90266279dbd1945a3297396f0b..59788eaef0dae5ee01ceba1bf45e85cb07f88e53 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -10,6 +10,7 @@ import io.netty.buffer.ByteBufOutputStream; + import io.netty.handler.codec.DecoderException; + import io.netty.handler.codec.EncoderException; + import io.netty.util.ByteProcessor; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.io.DataInput; + import java.io.DataOutput; + import java.io.IOException; +@@ -43,6 +44,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit + public class FriendlyByteBuf extends ByteBuf { + + private final ByteBuf source; ++ public java.util.Locale adventure$locale; // Paper + + public FriendlyByteBuf(ByteBuf bytebuf) { + this.source = bytebuf; +@@ -164,8 +166,15 @@ public class FriendlyByteBuf extends ByteBuf { + return Component.Serializer.fromJson(this.readUtf(262144)); + } + ++ // Paper start ++ public FriendlyByteBuf writeComponent(final net.kyori.adventure.text.Component component) { ++ return this.writeUtf(PaperAdventure.asJsonString(component, this.adventure$locale), 262144); ++ } ++ // Paper end ++ + public FriendlyByteBuf writeComponent(Component text) { +- return this.writeUtf(Component.Serializer.toJson(text), 262144); ++ //return this.a(IChatBaseComponent.ChatSerializer.a(ichatbasecomponent), 262144); // Paper - comment ++ return this.writeUtf(PaperAdventure.asJsonString(text, this.adventure$locale), 262144); // Paper + } + + public > T readEnum(Class oclass) { +@@ -348,6 +357,7 @@ public class FriendlyByteBuf extends ByteBuf { + return this.writeUtf(s, 32767); + } + ++ public FriendlyByteBuf writeUtf(final String string, final int maxLength) { return this.writeUtf(string, maxLength); } // Paper - OBFHELPER + public FriendlyByteBuf writeUtf(String s, int i) { + byte[] abyte = s.getBytes(StandardCharsets.UTF_8); + +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 14fa1371e52b9af5a7550a9aa144fa406b754046..d36d0424bcd4811af892f5f76fdcefda2af1ad33 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -3,6 +3,7 @@ package net.minecraft.network; + import io.netty.buffer.ByteBuf; + import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.io.IOException; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.PacketFlow; +@@ -37,6 +38,7 @@ public class PacketEncoder extends MessageToByteEncoder> { + throw new IOException("Can't serialize unregistered packet"); + } else { + FriendlyByteBuf packetdataserializer = new FriendlyByteBuf(bytebuf); ++ packetdataserializer.adventure$locale = channelhandlercontext.channel().attr(PaperAdventure.LOCALE_ATTRIBUTE).get(); // Paper + + packetdataserializer.writeVarInt(integer); + +diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java +index 819ea90ea116dea396c225539fc0fbebd6176ba5..54d186a195aca6d0a4c412ed609d8c86dcc76072 100644 +--- a/src/main/java/net/minecraft/network/chat/Component.java ++++ b/src/main/java/net/minecraft/network/chat/Component.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.chat; + ++import io.papermc.paper.adventure.AdventureComponent; // Paper + import com.google.gson.Gson; + import com.google.gson.GsonBuilder; + import com.google.gson.JsonArray; +@@ -110,6 +111,7 @@ public interface Component extends Message, FormattedText, Iterable { + GsonBuilder gsonbuilder = new GsonBuilder(); + + gsonbuilder.disableHtmlEscaping(); ++ gsonbuilder.registerTypeAdapter(AdventureComponent.class, new AdventureComponent.Serializer()); // Paper + gsonbuilder.registerTypeHierarchyAdapter(Component.class, new Component.Serializer()); + gsonbuilder.registerTypeHierarchyAdapter(Style.class, new Style.Serializer()); + gsonbuilder.registerTypeAdapterFactory(new LowerCaseEnumTypeAdapterFactory()); +@@ -262,6 +264,7 @@ public interface Component extends Message, FormattedText, Iterable { + } + + public JsonElement serialize(Component ichatbasecomponent, Type type, JsonSerializationContext jsonserializationcontext) { ++ if (ichatbasecomponent instanceof AdventureComponent) return jsonserializationcontext.serialize(ichatbasecomponent); // Paper + JsonObject jsonobject = new JsonObject(); + + if (!ichatbasecomponent.getStyle().isEmpty()) { +@@ -350,10 +353,12 @@ public interface Component extends Message, FormattedText, Iterable { + return jsonobject; + } + ++ public static String componentToJson(final Component component) { return toJson(component); } // Paper - OBFHELPER + public static String toJson(Component text) { + return Component.Serializer.GSON.toJson(text); + } + ++ public static JsonElement toJsonTree(final Component component) { return toJsonTree(component); } // Paper - OBFHELPER + public static JsonElement toJsonTree(Component text) { + return Component.Serializer.GSON.toJsonTree(text); + } +@@ -363,6 +368,7 @@ public interface Component extends Message, FormattedText, Iterable { + return (MutableComponent) GsonHelper.fromJson(Component.Serializer.GSON, json, MutableComponent.class, false); + } + ++ public static @Nullable Component fromJsonTree(final JsonElement json) { return fromJson(json); } // Paper - OBFHELPER + @Nullable + public static MutableComponent fromJson(JsonElement json) { + return (MutableComponent) Component.Serializer.GSON.fromJson(json, MutableComponent.class); +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +index ce64931b5c363352f03baddbc747246469d56a84..e47102cadb40ed8a9c011386445f15fd30de7596 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +@@ -11,6 +11,7 @@ import net.minecraft.network.protocol.Packet; + public class ClientboundChatPacket implements Packet { + + private Component message; ++ public net.kyori.adventure.text.Component adventure$message; // Paper + public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot + private ChatType type; + private UUID sender; +@@ -32,6 +33,11 @@ public class ClientboundChatPacket implements Packet { + + @Override + public void write(FriendlyByteBuf buf) throws IOException { ++ // Paper start ++ if (this.adventure$message != null) { ++ buf.writeComponent(this.adventure$message); ++ } else ++ // Paper end + // Spigot start + if (components != null) { + buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(components)); +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java +index 915120cc505c70153f7b70f07d8d42c13eb77ea7..69ff8df7340e60c476803256750a48f0b43414d3 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java +@@ -10,6 +10,7 @@ public class ClientboundSetTitlesPacket implements Packet 0 && flag) { // TODO: allow plugins to override? +- Component ichatbasecomponent; +- if (deathMessage.equals(deathmessage)) { +- ichatbasecomponent = this.getCombatTracker().getDeathMessage(); +- } else { +- ichatbasecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(deathMessage); +- } ++ if (deathMessage != null && deathMessage != net.kyori.adventure.text.Component.empty() && flag) { // Paper - Adventure // TODO: allow plugins to override? ++ Component ichatbasecomponent = PaperAdventure.asVanilla(deathMessage); // Paper - Adventure + + this.connection.send((Packet) (new ClientboundPlayerCombatPacket(this.getCombatTracker(), ClientboundPlayerCombatPacket.Event.ENTITY_DIED, ichatbasecomponent)), (future) -> { + if (!future.isSuccess()) { +@@ -1666,6 +1663,7 @@ public class ServerPlayer extends Player implements ContainerListener { + this.sendMessage(message, ChatType.SYSTEM, senderUuid); + } + ++ public void sendMessage(final Component message, final ChatType type, final UUID sender) { this.sendMessage(message, type, sender); } // Paper - OBFHELPER + public void sendMessage(Component message, ChatType type, UUID senderUuid) { + this.connection.send((Packet) (new ClientboundChatPacket(message, type, senderUuid)), (future) -> { + if (!future.isSuccess() && (type == ChatType.GAME_INFO || type == ChatType.SYSTEM)) { +@@ -1688,6 +1686,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + + public String locale = "en_us"; // CraftBukkit - add, lowercase ++ public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void updateOptions(ServerboundClientInformationPacket packet) { + // CraftBukkit start + if (getMainArm() != packet.getMainHand()) { +@@ -1699,6 +1698,10 @@ public class ServerPlayer extends Player implements ContainerListener { + this.server.server.getPluginManager().callEvent(event); + } + this.locale = packet.language; ++ // Paper start ++ this.adventure$locale = net.kyori.adventure.translation.Translator.parseLocale(this.locale); ++ this.connection.connection.channel.attr(PaperAdventure.LOCALE_ATTRIBUTE).set(this.adventure$locale); ++ // Paper end + this.clientViewDistance = packet.viewDistance; + // CraftBukkit end + this.chatVisibility = packet.getChatVisibility(); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 466c4322803bedf1fa61be281b954bf94fb8ff02..016e91a6ca1c8457e3e367ac0597b73e81919b68 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -168,6 +168,8 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.ChatProcessor; // Paper ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.util.concurrent.ExecutionException; + import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + import org.bukkit.Location; +@@ -388,21 +390,24 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + return this.server.isSingleplayerOwner(this.player.getGameProfile()); + } + +- // CraftBukkit start +- @Deprecated +- public void disconnect(Component reason) { +- disconnect(CraftChatMessage.fromComponent(reason)); ++ public void disconnect(String s) { ++ // Paper start ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s)); + } +- // CraftBukkit end + +- public void disconnect(String s) { ++ public void disconnect(final Component reason) { ++ this.disconnect(PaperAdventure.asAdventure(reason)); ++ } ++ ++ public void disconnect(net.kyori.adventure.text.Component reason) { ++ // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { + return; + } +- String leaveMessage = ChatFormatting.YELLOW + this.player.getScoreboardName() + " left the game."; ++ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, this.player.getBukkitEntity().displayName()); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.craftServer.getPlayer(this.player), s, leaveMessage); ++ PlayerKickEvent event = new PlayerKickEvent(this.craftServer.getPlayer(this.player), reason, leaveMessage); // Paper - Adventure + + if (this.craftServer.getServer().isRunning()) { + this.craftServer.getPluginManager().callEvent(event); +@@ -413,8 +418,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + return; + } + // Send the possibly modified leave message +- s = event.getReason(); +- final Component ichatbasecomponent = CraftChatMessage.fromString(s, true)[0]; ++ final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), (future) -> { +@@ -1631,9 +1635,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + */ + + this.player.disconnect(); +- String quitMessage = this.server.getPlayerList().disconnect(this.player); +- if ((quitMessage != null) && (quitMessage.length() > 0)) { +- this.server.getPlayerList().sendMessage(CraftChatMessage.fromString(quitMessage)); ++ // Paper start - Adventure ++ net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().disconnect(this.player); ++ if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.server.getPlayerList().sendMessage(PaperAdventure.asVanilla(quitMessage)); ++ // Paper end + } + // CraftBukkit end + TextFilter itextfilter = this.player.getTextFilter(); +@@ -1849,8 +1855,13 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.handleCommand(s); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { + // Do nothing, this is coming from a plugin +- } else { +- Player player = this.getPlayer(); ++ // Paper start ++ } else if (true) { ++ final ChatProcessor cp = new ChatProcessor(this.server, this.player, s, async); ++ cp.process(); ++ // Paper end ++ } else if (false) { // Paper ++ Player player = this.getPlayer(); // Paper + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(server)); + this.craftServer.getPluginManager().callEvent(event); + +@@ -2668,21 +2679,20 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + return; + } + +- // CraftBukkit start +- Player player = this.craftServer.getPlayer(this.player); +- int x = packetplayinupdatesign.getPos().getX(); +- int y = packetplayinupdatesign.getPos().getY(); +- int z = packetplayinupdatesign.getPos().getZ(); +- String[] lines = new String[4]; ++ // CraftBukkit start // Paper start - Adventure ++ List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines[i] = ChatFormatting.stripFormatting(new TextComponent(ChatFormatting.stripFormatting((String) list.get(i))).getString()); ++ lines.add(net.kyori.adventure.text.Component.text(list.get(i))); + } +- SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.craftServer.getPlayer(this.player), lines); ++ SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.craftServer.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- System.arraycopy(org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.getLines()), 0, tileentitysign.messages, 0, 4); ++ for (int i = 0; i < 4; i++) { ++ tileentitysign.setMessage(i, PaperAdventure.asVanilla(event.line(i))); ++ } ++ // Paper end + tileentitysign.isEditable = false; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index a1dd76f49c59ed0a0b7f2b68fbd1a9a70a89128b..9dbb3bb79ae2de6635f71e5ee5bebeb5e57b624e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -35,6 +35,7 @@ import net.minecraft.world.entity.player.Player; + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; +@@ -299,7 +300,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { + final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); + if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { +- event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); ++ event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure + } + Waitable waitable = new Waitable() { + @Override +@@ -310,12 +311,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + + ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable); + if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { +- disconnect(event.getKickMessage()); ++ disconnect(PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure + return; + } + } else { + if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { +- disconnect(asyncEvent.getKickMessage()); ++ disconnect(PaperAdventure.asVanilla(asyncEvent.kickMessage())); // Paper - Adventure + return; + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +index de19aa66e7b6ddde17e9acf65643b4a71573d759..223df8d27c2ff1cbff634bca3dbde5cc24de7f98 100644 +--- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -55,7 +55,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + CraftIconCache icon = server.server.getServerIcon(); + + ServerListPingEvent() { +- super(((InetSocketAddress) connection.getRemoteAddress()).getAddress(), server.getMotd(), server.getPlayerList().getMaxPlayers()); ++ super(((InetSocketAddress) connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure + } + + @Override +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 88af57699d7f9e45ad1366243049e4f3565703ff..8cdecaf2f63c78196e0c5046fe2431b40e072c8a 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -8,6 +8,7 @@ import com.mojang.authlib.GameProfile; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import io.netty.buffer.Unpooled; ++import io.papermc.paper.adventure.PaperAdventure; + import java.io.File; + import java.net.SocketAddress; + import java.text.SimpleDateFormat; +@@ -83,6 +84,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; + import net.minecraft.server.dedicated.DedicatedServer; +@@ -251,7 +253,7 @@ public abstract class PlayerList { + } + // CraftBukkit start + chatmessage.withStyle(ChatFormatting.YELLOW); +- String joinMessage = CraftChatMessage.fromComponent(chatmessage); ++ Component joinMessage = chatmessage; // Paper - Adventure + + playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.yRot, player.xRot); + this.players.add(player); +@@ -260,19 +262,18 @@ public abstract class PlayerList { + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below + + // CraftBukkit start +- PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(cserver.getPlayer(player), joinMessage); ++ PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(player), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + cserver.getPluginManager().callEvent(playerJoinEvent); + + if (!player.connection.connection.isConnected()) { + return; + } + +- joinMessage = playerJoinEvent.getJoinMessage(); ++ final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); + +- if (joinMessage != null && joinMessage.length() > 0) { +- for (Component line : org.bukkit.craftbukkit.util.CraftChatMessage.fromString(joinMessage)) { +- server.getPlayerList().broadcastAll(new ClientboundChatPacket(line, ChatType.SYSTEM, Util.NIL_UUID)); +- } ++ if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure ++ joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure ++ server.getPlayerList().broadcastAll(new ClientboundChatPacket(joinMessage, ChatType.SYSTEM, Util.NIL_UUID)); // Paper - Adventure + } + // CraftBukkit end + +@@ -469,7 +470,7 @@ public abstract class PlayerList { + + } + +- public String disconnect(ServerPlayer entityplayer) { // CraftBukkit - return string ++ public net.kyori.adventure.text.Component disconnect(ServerPlayer entityplayer) { // Paper - return Component + ServerLevel worldserver = entityplayer.getLevel(); + + entityplayer.awardStat(Stats.LEAVE_GAME); +@@ -480,7 +481,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(); + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getScoreboardName() + " left the game"); ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); + cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +@@ -541,7 +542,7 @@ public abstract class PlayerList { + cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); + // CraftBukkit end + +- return playerQuitEvent.getQuitMessage(); // CraftBukkit ++ return playerQuitEvent.quitMessage(); // Paper - Adventure + } + + // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer +@@ -587,10 +588,10 @@ public abstract class PlayerList { + } + + // return chatmessage; +- if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CraftChatMessage.fromComponent(chatmessage)); // Spigot ++ if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Spigot // Paper - Adventure + } else if (!this.isWhiteListed(gameprofile)) { + chatmessage = new TranslatableComponent("multiplayer.disconnect.not_whitelisted"); +- event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot ++ event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure + } else if (getIpBans().isBanned(socketaddress) && !getIpBans().get(socketaddress).hasExpired()) { + IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); + +@@ -600,17 +601,17 @@ public abstract class PlayerList { + } + + // return chatmessage; +- event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CraftChatMessage.fromComponent(chatmessage)); ++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + } else { + // return this.players.size() >= this.maxPlayers && !this.f(gameprofile) ? new ChatMessage("multiplayer.disconnect.server_full") : null; + if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { +- event.disallow(PlayerLoginEvent.Result.KICK_FULL, org.spigotmc.SpigotConfig.serverFullMessage); // Spigot ++ event.disallow(PlayerLoginEvent.Result.KICK_FULL, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure + } + } + + cserver.getPluginManager().callEvent(event); + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { +- loginlistener.disconnect(event.getKickMessage()); ++ loginlistener.disconnect(PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure + return null; + } + return entity; +@@ -1131,7 +1132,7 @@ public abstract class PlayerList { + public void removeAll() { + // CraftBukkit start - disconnect safely + for (ServerPlayer player : this.players) { +- player.connection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message ++ player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/BossEvent.java b/src/main/java/net/minecraft/world/BossEvent.java +index 8718449abc08ed7795ac70c2bef12d15b94d4127..adb7c8db8e173801a83e5ff1f4cad0dda2abeb82 100644 +--- a/src/main/java/net/minecraft/world/BossEvent.java ++++ b/src/main/java/net/minecraft/world/BossEvent.java +@@ -1,5 +1,6 @@ + package net.minecraft.world; + ++import io.papermc.paper.adventure.PaperAdventure; + import java.util.UUID; + import net.minecraft.ChatFormatting; + import net.minecraft.network.chat.Component; +@@ -14,6 +15,7 @@ public abstract class BossEvent { + protected boolean darkenScreen; + protected boolean playBossMusic; + protected boolean createWorldFog; ++ public net.kyori.adventure.bossbar.BossBar adventure; // Paper + + public BossEvent(UUID uuid, Component name, BossEvent.BossBarColor color, BossEvent.BossBarOverlay style) { + this.id = uuid; +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 2a6a6e291efbd7cc8fed6532f18321bd141e1306..3faf52e7ac5e7e22d09cfb73cfda6b9f622137d4 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -865,6 +865,7 @@ public final class ItemStack { + } + // CraftBukkit end + ++ public Component displayName() { return this.getDisplayName(); } // Paper - OBFHELPER + public Component getDisplayName() { + MutableComponent ichatmutablecomponent = (new TextComponent("")).append(this.getHoverName()); + +diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +index 72367311f79e3ef2868c05b38bae98c5a9fb129c..c23ec1b31950471905c65e46273ae105de853d9b 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +@@ -94,6 +94,7 @@ public abstract class Enchantment { + return this.getOrCreateDescriptionId(); + } + ++ public final Component getTranslationComponentForLevel(int level) { return this.getFullname(level); } // Paper - OBFHELPER + public Component getFullname(int level) { + TranslatableComponent chatmessage = new TranslatableComponent(this.getDescriptionId()); + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 2cccec66fbc7114c65336769e354fe6f756c9fca..d44505b3ee2a35422568e9bce0d868191e348fc0 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.util.UUID; + + import org.bukkit.craftbukkit.CraftServer; +@@ -473,7 +474,7 @@ public class MapItemSavedData extends SavedData { + for ( org.bukkit.map.MapCursor cursor : render.cursors) { + + if (cursor.isVisible()) { +- icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), CraftChatMessage.fromStringOrNull(cursor.getCaption()))); ++ icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), PaperAdventure.asVanilla(cursor.caption()))); // Paper - Adventure + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c4cf1394fe4c2782b1fea8b3653a817157d857eb..f7f5457d20586e0ba72368e64ff6025f6755e61e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -561,8 +561,10 @@ public final class CraftServer implements Server { + } + + @Override ++ @Deprecated // Paper start + public int broadcastMessage(String message) { +- return broadcast(message, BROADCAST_CHANNEL_USERS); ++ return this.broadcast(message, BROADCAST_CHANNEL_USERS); ++ // Paper end + } + + public Player getPlayer(final ServerPlayer entity) { +@@ -1306,7 +1308,15 @@ public final class CraftServer implements Server { + return configuration.getInt("settings.spawn-radius", -1); + } + ++ // Paper start + @Override ++ public net.kyori.adventure.text.Component shutdownMessage() { ++ String msg = getShutdownMessage(); ++ return msg != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(msg) : null; ++ } ++ // Paper end ++ @Override ++ @Deprecated // Paper + public String getShutdownMessage() { + return configuration.getString("settings.shutdown-message"); + } +@@ -1422,7 +1432,20 @@ public final class CraftServer implements Server { + } + + @Override ++ @Deprecated // Paper + public int broadcast(String message, String permission) { ++ // Paper start - Adventure ++ return this.broadcast(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(message), permission); ++ } ++ ++ @Override ++ public int broadcast(net.kyori.adventure.text.Component message) { ++ return this.broadcast(message, BROADCAST_CHANNEL_USERS); ++ } ++ ++ @Override ++ public int broadcast(net.kyori.adventure.text.Component message, String permission) { ++ // Paper end + Set recipients = new HashSet<>(); + for (Permissible permissible : getPluginManager().getPermissionSubscriptions(permission)) { + if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { +@@ -1430,14 +1453,14 @@ public final class CraftServer implements Server { + } + } + +- BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); ++ BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); // Paper - Adventure + getPluginManager().callEvent(broadcastMessageEvent); + + if (broadcastMessageEvent.isCancelled()) { + return 0; + } + +- message = broadcastMessageEvent.getMessage(); ++ message = broadcastMessageEvent.message(); // Paper - Adventure + + for (CommandSender recipient : recipients) { + recipient.sendMessage(message); +@@ -1663,6 +1686,14 @@ public final class CraftServer implements Server { + return CraftInventoryCreator.INSTANCE.createInventory(owner, type); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); ++ return CraftInventoryCreator.INSTANCE.createInventory(owner, type, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); +@@ -1675,13 +1706,28 @@ public final class CraftServer implements Server { + return CraftInventoryCreator.INSTANCE.createInventory(owner, size); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) throws IllegalArgumentException { ++ Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); ++ return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException { + Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); + return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title); + } + ++ // Paper start ++ @Override ++ public Merchant createMerchant(net.kyori.adventure.text.Component title) { ++ return new org.bukkit.craftbukkit.inventory.CraftMerchantCustom(title == null ? InventoryType.MERCHANT.defaultTitle() : title); ++ } ++ // Paper end + @Override ++ @Deprecated // Paper + public Merchant createMerchant(String title) { + return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); + } +@@ -1725,6 +1771,12 @@ public final class CraftServer implements Server { + return Thread.currentThread().equals(console.serverThread) || console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component motd() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(new net.minecraft.network.chat.TextComponent(console.getMotd())); ++ } ++ // Paper end + @Override + public String getMotd() { + return console.getMotd(); +@@ -2153,5 +2205,15 @@ public final class CraftServer implements Server { + return null; + } + } ++ ++ // Paper start ++ private Iterable adventure$audiences; ++ @Override ++ public Iterable audiences() { ++ if (this.adventure$audiences == null) { ++ this.adventure$audiences = com.google.common.collect.Iterables.concat(java.util.Collections.singleton(this.getConsoleSender()), this.getOnlinePlayers()); ++ } ++ return this.adventure$audiences; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 05aedca561919a12ced1925c5cc9af585bb04523..ce9f10f890a5866ab6208c7253b15b09fe323a81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -19,6 +19,12 @@ public class Main { + public static boolean useConsole = true; + + public static void main(String[] args) { ++ // Paper start ++ final String warnWhenLegacyFormattingDetected = String.join(".", "net", "kyori", "adventure", "text", "warnWhenLegacyFormattingDetected"); ++ if (false && System.getProperty(warnWhenLegacyFormattingDetected) == null) { ++ System.setProperty(warnWhenLegacyFormattingDetected, String.valueOf(true)); ++ } ++ // Paper end + // Todo: Installation script + OptionParser parser = new OptionParser() { + { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index 639577ab8b6467302a243c99ba5a4eede3aed655..940fef58f14e06213c7f305f67dcb8918976c03d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -70,6 +70,19 @@ public class CraftBeacon extends CraftBlockEntityState implem + this.getSnapshot().secondaryPower = (effect != null) ? MobEffect.byId(effect.getId()) : null; + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final BeaconBlockEntity be = this.getSnapshot(); ++ return be.name != null ? io.papermc.paper.adventure.PaperAdventure.asAdventure(be.name) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public String getCustomName() { + BeaconBlockEntity beacon = this.getSnapshot(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java +index 16a0f6e390a7415635e3573c1f79f7d78e5ef859..b1edc96d7e0444e72b79f190982de1d1bb5987f3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java +@@ -32,6 +32,19 @@ public abstract class CraftContainer extends + this.getSnapshot().lockKey = (key == null) ? LockCode.NO_LOCK : new LockCode(key); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final T be = this.getSnapshot(); ++ return be.hasCustomName() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(be.getCustomName()) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public String getCustomName() { + T container = this.getSnapshot(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java +index add5b68d5fbd887e3fc2d226eff9ab00ed01ce73..2c3d6ba06d876df168aae4cc09b7b4400e2fa33d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java +@@ -16,6 +16,19 @@ public class CraftEnchantingTable extends CraftBlockEntityState implements Sign { + + // Lazily initialized only if requested: +- private String[] originalLines = null; +- private String[] lines = null; ++ // Paper start ++ private java.util.ArrayList originalLines = null; // ArrayList for RandomAccess ++ private java.util.ArrayList lines = null; // ArrayList for RandomAccess ++ // Paper end + + public CraftSign(final Block block) { + super(block, SignBlockEntity.class); +@@ -23,27 +25,52 @@ public class CraftSign extends CraftBlockEntityState implements + super(material, te); + } + ++ // Paper start + @Override +- public String[] getLines() { +- if (lines == null) { +- // Lazy initialization: +- SignBlockEntity sign = this.getSnapshot(); +- lines = new String[sign.messages.length]; +- System.arraycopy(revertComponents(sign.messages), 0, lines, 0, lines.length); +- originalLines = new String[lines.length]; +- System.arraycopy(lines, 0, originalLines, 0, originalLines.length); ++ public java.util.List lines() { ++ this.loadLines(); ++ return this.lines; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component line(int index) { ++ this.loadLines(); ++ return this.lines.get(index); ++ } ++ ++ @Override ++ public void line(int index, net.kyori.adventure.text.Component line) { ++ this.loadLines(); ++ this.lines.set(index, line); ++ } ++ ++ private void loadLines() { ++ if (lines != null) { ++ return; + } +- return lines; ++ ++ // Lazy initialization: ++ SignBlockEntity sign = this.getSnapshot(); ++ lines = io.papermc.paper.adventure.PaperAdventure.asAdventure(com.google.common.collect.Lists.newArrayList(sign.messages)); ++ originalLines = new java.util.ArrayList<>(lines); ++ } ++ // Paper end ++ @Override ++ public String[] getLines() { ++ this.loadLines(); ++ return this.lines.stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::serialize).toArray(String[]::new); // Paper + } + + @Override + public String getLine(int index) throws IndexOutOfBoundsException { +- return getLines()[index]; ++ this.loadLines(); ++ return io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(this.lines.get(index)); // Paper + } + + @Override + public void setLine(int index, String line) throws IndexOutOfBoundsException { +- getLines()[index] = line; ++ this.loadLines(); ++ this.lines.set(index, line != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(line) : net.kyori.adventure.text.Component.empty()); // Paper + } + + @Override +@@ -71,16 +98,32 @@ public class CraftSign extends CraftBlockEntityState implements + super.applyTo(sign); + + if (lines != null) { +- for (int i = 0; i < lines.length; i++) { +- String line = (lines[i] == null) ? "" : lines[i]; +- if (line.equals(originalLines[i])) { ++ // Paper start ++ for (int i = 0; i < this.lines.size(); ++i) { ++ net.kyori.adventure.text.Component component = this.lines.get(i); ++ net.kyori.adventure.text.Component origComp = this.originalLines.get(i); ++ if (component.equals(origComp)) { + continue; // The line contents are still the same, skip. + } +- sign.messages[i] = CraftChatMessage.fromString(line)[0]; ++ sign.messages[i] = io.papermc.paper.adventure.PaperAdventure.asVanilla(component); + } ++ // Paper end + } + } + ++ // Paper start ++ public static Component[] sanitizeLines(java.util.List lines) { ++ Component[] components = new Component[4]; ++ for (int i = 0; i < 4; i++) { ++ if (i < lines.size() && lines.get(i) != null) { ++ components[i] = io.papermc.paper.adventure.PaperAdventure.asVanilla(lines.get(i)); ++ } else { ++ components[i] = new TextComponent(""); ++ } ++ } ++ return components; ++ } ++ // Paper end + public static Component[] sanitizeLines(String[] lines) { + Component[] components = new Component[4]; + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index 089fe4a3458ed3106fa214f89a7004a5d3c6bb95..af986adfdb547cb61fbd52f0f89858f1a9e52cc3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -80,4 +80,11 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + public boolean isConversing() { + return conversationTracker.isConversing(); + } ++ ++ // Paper start ++ @Override ++ public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { ++ this.sendRawMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index fd473d213f4050f420bd7d729fe0df757d5398bc..b1ffe6c7a5915f00a476e88f3a38349b740b4910 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -187,6 +187,12 @@ public class CraftEnchantment extends Enchantment { + CraftEnchantment ench = (CraftEnchantment) other; + return !target.isCompatibleWith(ench.target); + } ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName(int level) { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getTranslationComponentForLevel(level)); ++ } ++ // Paper end + + public net.minecraft.world.item.enchantment.Enchantment getHandle() { + return target; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index eb61c803cf74c5ca2c51d5027a02ed3db6b53096..53c231925ef1b17e48c5863570e3c54124874621 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -768,6 +768,19 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().getVehicle().getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final Component name = this.getHandle().getCustomName(); ++ return name != null ? io.papermc.paper.adventure.PaperAdventure.asAdventure(name) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getHandle().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public void setCustomName(String name) { + // sane limit for name length +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 7c200e43bdc170ecf8b8fbfadd7bb38c66133443..b5c0f3d91cf451a972f0cf293db03a306073c493 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -317,9 +317,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + container = CraftEventFactory.callInventoryOpenEvent(player, container); + if (container == null) return; + +- String title = container.getBukkitView().getTitle(); ++ //String title = container.getBukkitView().getTitle(); // Paper - comment ++ net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper ++ if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(container.getBukkitView().getTitle()); // Paper + +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); ++ //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper // Paper - comment ++ player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + getHandle().containerMenu = container; + getHandle().containerMenu.addSlotListener(player); + } +@@ -388,8 +391,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + // Now open the window + MenuType windowType = CraftContainer.getNotchInventoryType(inventory.getTopInventory()); +- String title = inventory.getTitle(); +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); ++ ++ //String title = inventory.getTitle(); // Paper - comment ++ net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper ++ if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(inventory.getTitle()); // Paper ++ //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment ++ player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.containerMenu = container; + player.containerMenu.addSlotListener(player); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 61e2d92471d1498eb97d42dc642605a2e00e6089..50d11611702e3d1f0e980fb8f2280b05b891167b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -238,14 +238,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public String getDisplayName() { ++ if(true) return io.papermc.paper.adventure.DisplayNames.getLegacy(this); // Paper + return getHandle().displayName; + } + + @Override + public void setDisplayName(final String name) { ++ this.getHandle().adventure$displayName = name != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(name) : net.kyori.adventure.text.Component.text(this.getName()); // Paper + getHandle().displayName = name == null ? getName() : name; + } + ++ // Paper start ++ @Override ++ public void playerListName(net.kyori.adventure.text.Component name) { ++ getHandle().listName = name == null ? null : io.papermc.paper.adventure.PaperAdventure.asVanilla(name); ++ for (ServerPlayer player : server.getHandle().players) { ++ if (player.getBukkitEntity().canSee(this)) { ++ player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, getHandle())); ++ } ++ } ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListName() { ++ return getHandle().listName == null ? net.kyori.adventure.text.Component.text(getName()) : io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().listName); ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListHeader() { ++ return playerListHeader; ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListFooter() { ++ return playerListFooter; ++ } ++ // Paper end + @Override + public String getPlayerListName() { + return getHandle().listName == null ? getName() : CraftChatMessage.fromComponent(getHandle().listName); +@@ -264,35 +289,35 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + +- private Component playerListHeader; +- private Component playerListFooter; ++ private net.kyori.adventure.text.Component playerListHeader; // Paper - Adventure ++ private net.kyori.adventure.text.Component playerListFooter; // Paper - Adventure + + @Override + public String getPlayerListHeader() { +- return (playerListHeader == null) ? null : CraftChatMessage.fromComponent(playerListHeader); ++ return (playerListHeader == null) ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(playerListHeader); // Paper - Adventure + } + + @Override + public String getPlayerListFooter() { +- return (playerListFooter == null) ? null : CraftChatMessage.fromComponent(playerListFooter); ++ return (playerListFooter == null) ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(playerListFooter); // Paper - Adventure + } + + @Override + public void setPlayerListHeader(String header) { +- this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true); ++ this.playerListHeader = header == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(header); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + + @Override + public void setPlayerListFooter(String footer) { +- this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true); ++ this.playerListFooter = footer == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(footer); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + + @Override + public void setPlayerListHeaderFooter(String header, String footer) { +- this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true); +- this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true); ++ this.playerListHeader = header == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(header); // Paper - Adventure ++ this.playerListFooter = footer == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(footer); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + +@@ -300,8 +325,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (getHandle().connection == null) return; + + ClientboundTabListPacket packet = new ClientboundTabListPacket(); +- packet.header = (this.playerListHeader == null) ? new TextComponent("") : this.playerListHeader; +- packet.footer = (this.playerListFooter == null) ? new TextComponent("") : this.playerListFooter; ++ packet.header = (this.playerListHeader == null) ? new TextComponent("") : io.papermc.paper.adventure.PaperAdventure.asVanilla(this.playerListHeader); // Paper - Adventure ++ packet.footer = (this.playerListFooter == null) ? new TextComponent("") : io.papermc.paper.adventure.PaperAdventure.asVanilla(this.playerListFooter); // Paper - Adventure + getHandle().connection.send(packet); + } + +@@ -333,6 +358,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getHandle().connection.disconnect(message == null ? "" : message); + } + ++ // Paper start ++ @Override ++ public void kick(final net.kyori.adventure.text.Component message) { ++ org.spigotmc.AsyncCatcher.catchOp("player kick"); ++ final ServerGamePacketListenerImpl connection = this.getHandle().connection; ++ if (connection != null) { ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ } ++ } ++ // Paper end ++ + @Override + public void setCompassTarget(Location loc) { + if (getHandle().connection == null) return; +@@ -559,6 +595,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void sendSignChange(Location loc, List lines) { ++ this.sendSignChange(loc, lines, org.bukkit.DyeColor.BLACK); ++ } ++ @Override ++ public void sendSignChange(Location loc, List lines, DyeColor dyeColor) { ++ if (getHandle().connection == null) { ++ return; ++ } ++ if (lines == null) { ++ lines = new java.util.ArrayList<>(4); ++ } ++ Validate.notNull(loc, "Location cannot be null"); ++ Validate.notNull(dyeColor, "DyeColor cannot be null"); ++ if (lines.size() < 4) { ++ throw new IllegalArgumentException("Must have at least 4 lines"); ++ } ++ Component[] components = CraftSign.sanitizeLines(lines); ++ this.sendSignChange0(components, loc, dyeColor); ++ } ++ ++ private void sendSignChange0(Component[] components, Location loc, DyeColor dyeColor) { ++ SignBlockEntity sign = new SignBlockEntity(); ++ sign.setPosition(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); ++ sign.setColor(net.minecraft.world.item.DyeColor.byId(dyeColor.getWoolData())); ++ System.arraycopy(components, 0, sign.messages, 0, sign.messages.length); ++ ++ getHandle().connection.send(sign.getUpdatePacket()); ++ } ++ // Paper end + @Override + public void sendSignChange(Location loc, String[] lines) { + sendSignChange(loc, lines, DyeColor.BLACK); +@@ -581,12 +648,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + Component[] components = CraftSign.sanitizeLines(lines); +- SignBlockEntity sign = new SignBlockEntity(); +- sign.setPosition(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); +- sign.setColor(net.minecraft.world.item.DyeColor.byId(dyeColor.getWoolData())); +- System.arraycopy(components, 0, sign.messages, 0, sign.messages.length); ++ /*TileEntitySign sign = new TileEntitySign(); // Paper ++ sign.setPosition(new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); ++ sign.setColor(EnumColor.fromColorIndex(dyeColor.getWoolData())); ++ System.arraycopy(components, 0, sign.lines, 0, sign.lines.length); + +- getHandle().connection.send(sign.getUpdatePacket()); ++ getHandle().playerConnection.sendPacket(sign.getUpdatePacket());*/ // Paper ++ this.sendSignChange0(components, loc, dyeColor); // Paper + } + + @Override +@@ -1686,6 +1754,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return (getHandle().clientViewDistance == null) ? Bukkit.getViewDistance() : getHandle().clientViewDistance; + } + ++ // Paper start ++ @Override ++ public java.util.Locale locale() { ++ return getHandle().adventure$locale; ++ } ++ // Paper end + @Override + public int getPing() { + return getHandle().latency; +@@ -1714,6 +1788,138 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getInventory().setItemInMainHand(hand); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() { ++ return this.getHandle().adventure$displayName; ++ } ++ ++ @Override ++ public void displayName(final net.kyori.adventure.text.Component displayName) { ++ this.getHandle().adventure$displayName = displayName != null ? displayName : net.kyori.adventure.text.Component.text(this.getName()); ++ this.getHandle().displayName = null; ++ } ++ ++ @Override ++ public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { ++ final ClientboundChatPacket packet = new ClientboundChatPacket(null, type == net.kyori.adventure.audience.MessageType.CHAT ? net.minecraft.network.chat.ChatType.CHAT : net.minecraft.network.chat.ChatType.SYSTEM, identity.uuid()); ++ packet.adventure$message = message; ++ this.getHandle().connection.send(packet); ++ } ++ ++ @Override ++ public void sendActionBar(final net.kyori.adventure.text.Component message) { ++ final ClientboundSetTitlesPacket packet = new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.ACTIONBAR, null); ++ packet.adventure$text = message; ++ this.getHandle().connection.send(packet); ++ } ++ ++ @Override ++ public void sendPlayerListHeader(final net.kyori.adventure.text.Component header) { ++ this.playerListHeader = header; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ @Override ++ public void sendPlayerListFooter(final net.kyori.adventure.text.Component footer) { ++ this.playerListFooter = footer; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ @Override ++ public void sendPlayerListHeaderAndFooter(final net.kyori.adventure.text.Component header, final net.kyori.adventure.text.Component footer) { ++ this.playerListHeader = header; ++ this.playerListFooter = footer; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ private void adventure$sendPlayerListHeaderAndFooter() { ++ final ServerGamePacketListenerImpl connection = this.getHandle().connection; ++ if (connection == null) return; ++ final ClientboundTabListPacket packet = new ClientboundTabListPacket(); ++ packet.adventure$header = (this.playerListHeader == null) ? net.kyori.adventure.text.Component.empty() : this.playerListHeader; ++ packet.adventure$footer = (this.playerListFooter == null) ? net.kyori.adventure.text.Component.empty() : this.playerListFooter; ++ connection.send(packet); ++ } ++ ++ @Override ++ public void showTitle(final net.kyori.adventure.title.Title title) { ++ final ServerGamePacketListenerImpl connection = this.getHandle().connection; ++ final net.kyori.adventure.title.Title.Times times = title.times(); ++ if (times != null) { ++ connection.send(new ClientboundSetTitlesPacket(ticks(times.fadeIn()), ticks(times.stay()), ticks(times.fadeOut()))); ++ } ++ final ClientboundSetTitlesPacket sp = new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.SUBTITLE, null); ++ sp.adventure$text = title.subtitle(); ++ connection.send(sp); ++ final ClientboundSetTitlesPacket tp = new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.TITLE, null); ++ tp.adventure$text = title.title(); ++ connection.send(tp); ++ } ++ ++ private static int ticks(final java.time.Duration duration) { ++ if (duration == null) { ++ return -1; ++ } ++ return (int) (duration.toMillis() / 50L); ++ } ++ ++ @Override ++ public void clearTitle() { ++ this.getHandle().connection.send(new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.CLEAR, null)); ++ } ++ ++ // resetTitle implemented above ++ ++ @Override ++ public void showBossBar(final net.kyori.adventure.bossbar.BossBar bar) { ++ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerShow(this); ++ } ++ ++ @Override ++ public void hideBossBar(final net.kyori.adventure.bossbar.BossBar bar) { ++ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerHide(this); ++ } ++ ++ @Override ++ public void playSound(final net.kyori.adventure.sound.Sound sound) { ++ final Vec3 pos = this.getHandle().position(); ++ this.playSound(sound, pos.x, pos.y, pos.z); ++ } ++ ++ @Override ++ public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { ++ final ResourceLocation name = io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.name()); ++ final java.util.Optional event = net.minecraft.core.Registry.SOUND_EVENT.getOptional(name); ++ if (event.isPresent()) { ++ this.getHandle().connection.send(new ClientboundSoundPacket(event.get(), io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.source()), x, y, z, sound.volume(), sound.pitch())); ++ } else { ++ this.getHandle().connection.send(new ClientboundCustomSoundPacket(name, io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.source()), new Vec3(x, y, z), sound.volume(), sound.pitch())); ++ } ++ } ++ ++ @Override ++ public void stopSound(final net.kyori.adventure.sound.SoundStop stop) { ++ this.getHandle().connection.send(new ClientboundStopSoundPacket( ++ io.papermc.paper.adventure.PaperAdventure.asVanillaNullable(stop.sound()), ++ io.papermc.paper.adventure.PaperAdventure.asVanillaNullable(stop.source()) ++ )); ++ } ++ ++ @Override ++ public void openBook(final net.kyori.adventure.inventory.Book book) { ++ final java.util.Locale locale = this.getHandle().adventure$locale; ++ final net.minecraft.world.item.ItemStack item = io.papermc.paper.adventure.PaperAdventure.asItemStack(book, locale); ++ final ServerPlayer player = this.getHandle(); ++ final ServerGamePacketListenerImpl connection = player.connection; ++ final net.minecraft.world.entity.player.Inventory inventory = player.inventory; ++ final int slot = inventory.items.size() + inventory.selected; ++ connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(0, slot, item)); ++ connection.send(new net.minecraft.network.protocol.game.ClientboundOpenBookPacket(net.minecraft.world.InteractionHand.MAIN_HAND)); ++ connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(0, slot, inventory.getSelected())); ++ } ++ // Paper end ++ + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index abd65693aca1964b65d091e633a36c97513c1d69..7fde1bb7587e567270e3f936381c6d361870211f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -776,9 +776,9 @@ public class CraftEventFactory { + return event; + } + +- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, String deathMessage, boolean keepInventory) { ++ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, String stringDeathMessage, boolean keepInventory) { // Paper - Adventure + CraftPlayer entity = victim.getBukkitEntity(); +- PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); ++ PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure + event.setKeepInventory(keepInventory); + org.bukkit.World world = entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); +@@ -802,7 +802,7 @@ public class CraftEventFactory { + * Server methods + */ + public static ServerListPingEvent callServerListPingEvent(Server craftServer, InetAddress address, String motd, int numPlayers, int maxPlayers) { +- ServerListPingEvent event = new ServerListPingEvent(address, motd, numPlayers, maxPlayers); ++ ServerListPingEvent event = new ServerListPingEvent(address, craftServer.motd(), numPlayers, maxPlayers); // Paper - Adventure + craftServer.getPluginManager().callEvent(event); + return event; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +index a393bdae6ef588289c8814a1896146a993c92e71..680c7818a7097355158eb76662ecebbae6dd6637 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +@@ -1,6 +1,5 @@ + package org.bukkit.craftbukkit.inventory; + +-import net.minecraft.network.chat.TextComponent; + import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; + import net.minecraft.world.Container; + import net.minecraft.world.entity.player.Player; +@@ -38,6 +37,7 @@ public class CraftContainer extends AbstractContainerMenu { + + private final InventoryView view; + private InventoryType cachedType; ++ private net.kyori.adventure.text.Component adventure$title; // Paper + private String cachedTitle; + private AbstractContainerMenu delegate; + private final int cachedSize; +@@ -49,7 +49,9 @@ public class CraftContainer extends AbstractContainerMenu { + Container top = ((CraftInventory) view.getTopInventory()).getInventory(); + net.minecraft.world.entity.player.Inventory bottom = (net.minecraft.world.entity.player.Inventory) ((CraftInventory) view.getBottomInventory()).getInventory(); + cachedType = view.getType(); +- cachedTitle = view.getTitle(); ++ this.adventure$title = view.title(); // Paper ++ if (this.adventure$title == null) this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(view.getTitle()); // Paper ++ //cachedTitle = view.getTitle(); // Paper - comment + cachedSize = getSize(); + setupSlots(top, bottom, player); + } +@@ -76,6 +78,13 @@ public class CraftContainer extends AbstractContainerMenu { + return inventory.getType(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).title() : net.kyori.adventure.text.Component.text(inventory.getType().getDefaultTitle()); ++ } ++ // Paper end ++ + @Override + public String getTitle() { + return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).getTitle() : inventory.getType().getDefaultTitle(); +@@ -94,7 +103,8 @@ public class CraftContainer extends AbstractContainerMenu { + + @Override + public boolean isSynched(Player player) { +- if (cachedType == view.getType() && cachedSize == getSize() && cachedTitle.equals(view.getTitle())) { ++ if (cachedType == view.getType() && cachedSize == getSize() && this.adventure$title.equals(view.title())) { // Paper ++ //if (cachedType == view.getType() && cachedSize == getSize() && cachedTitle.equals(view.getTitle())) { // Paper - comment + return true; + } + // If the window type has changed for some reason, update the player +@@ -102,7 +112,9 @@ public class CraftContainer extends AbstractContainerMenu { + // as good a place as any to put something like this. + boolean typeChanged = (cachedType != view.getType()); + cachedType = view.getType(); +- cachedTitle = view.getTitle(); ++ this.adventure$title = view.title(); // Paper ++ if (this.adventure$title == null) this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(view.getTitle()); // Paper ++ //cachedTitle = view.getTitle(); // Paper - comment + if (view.getPlayer() instanceof CraftPlayer) { + CraftPlayer player1 = (CraftPlayer) view.getPlayer(); + MenuType type = getNotchInventoryType(view.getTopInventory()); +@@ -114,7 +126,8 @@ public class CraftContainer extends AbstractContainerMenu { + setupSlots(top, bottom, player1.getHandle()); + } + int size = getSize(); +- player1.getHandle().connection.send(new ClientboundOpenScreenPacket(this.containerId, type, new TextComponent(cachedTitle))); ++ player1.getHandle().connection.send(new ClientboundOpenScreenPacket(this.containerId, type, io.papermc.paper.adventure.PaperAdventure.asVanilla(this.adventure$title))); // Paper ++ //player.getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.windowId, type, new ChatComponentText(cachedTitle))); // Paper - comment + player1.updateInventory(); + } + return true; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java +index 397cd9651381a8a0ddb4ca173690e9b80428b182..46805a25c0273b76c19a3bbd40078d78c5379f43 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java +@@ -19,6 +19,12 @@ public class CraftInventoryCustom extends CraftInventory { + super(new MinecraftInventory(owner, type)); + } + ++ // Paper start ++ public CraftInventoryCustom(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ super(new MinecraftInventory(owner, type, title)); ++ } ++ // Paper end ++ + public CraftInventoryCustom(InventoryHolder owner, InventoryType type, String title) { + super(new MinecraftInventory(owner, type, title)); + } +@@ -27,6 +33,12 @@ public class CraftInventoryCustom extends CraftInventory { + super(new MinecraftInventory(owner, size)); + } + ++ // Paper start ++ public CraftInventoryCustom(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) { ++ super(new MinecraftInventory(owner, size, title)); ++ } ++ // Paper end ++ + public CraftInventoryCustom(InventoryHolder owner, int size, String title) { + super(new MinecraftInventory(owner, size, title)); + } +@@ -36,9 +48,17 @@ public class CraftInventoryCustom extends CraftInventory { + private int maxStack = MAX_STACK; + private final List viewers; + private final String title; ++ private final net.kyori.adventure.text.Component adventure$title; // Paper + private InventoryType type; + private final InventoryHolder owner; + ++ // Paper start ++ public MinecraftInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ this(owner, type.getDefaultSize(), title); ++ this.type = type; ++ } ++ // Paper end ++ + public MinecraftInventory(InventoryHolder owner, InventoryType type) { + this(owner, type.getDefaultSize(), type.getDefaultTitle()); + this.type = type; +@@ -57,11 +77,24 @@ public class CraftInventoryCustom extends CraftInventory { + Validate.notNull(title, "Title cannot be null"); + this.items = NonNullList.a(size, ItemStack.EMPTY); + this.title = title; ++ this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(title); + this.viewers = new ArrayList(); + this.owner = owner; + this.type = InventoryType.CHEST; + } + ++ // Paper start ++ public MinecraftInventory(final InventoryHolder owner, final int size, final net.kyori.adventure.text.Component title) { ++ Validate.notNull(title, "Title cannot be null"); ++ this.items = NonNullList.a(size, ItemStack.EMPTY); ++ this.title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title); ++ this.adventure$title = title; ++ this.viewers = new ArrayList(); ++ this.owner = owner; ++ this.type = InventoryType.CHEST; ++ } ++ // Paper end ++ + @Override + public int getContainerSize() { + return items.size(); +@@ -183,6 +216,12 @@ public class CraftInventoryCustom extends CraftInventory { + return null; + } + ++ // Paper start ++ public net.kyori.adventure.text.Component title() { ++ return this.adventure$title; ++ } ++ // Paper end ++ + public String getTitle() { + return title; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java +index 326e8eb49e67ccfbf8f9b23bd6f03ea3576b9d35..945a80ca026c12e16a7ac5bebe56f846a437f847 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java +@@ -64,6 +64,13 @@ public class CraftInventoryView extends InventoryView { + return CraftItemStack.asCraftMirror(container.getSlot(slot).getItem()); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.container.getTitle()); ++ } ++ // Paper end ++ + @Override + public String getTitle() { + return CraftChatMessage.fromComponent(container.getTitle()); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 89a3617068421bb86baf4e8bfd9df2d0626adff7..32fa5ca0df07466e40817341d85d359b282f3078 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -334,4 +334,17 @@ public final class CraftItemFactory implements ItemFactory { + public Material updateMaterial(ItemMeta meta, Material material) throws IllegalArgumentException { + return ((CraftMetaItem) meta).updateMaterial(material); + } ++ ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.event.HoverEvent asHoverEvent(final ItemStack item, final java.util.function.UnaryOperator op) { ++ final net.minecraft.nbt.CompoundTag tag = CraftItemStack.asNMSCopy(item).getTag(); ++ return net.kyori.adventure.text.event.HoverEvent.showItem(op.apply(net.kyori.adventure.text.event.HoverEvent.ShowItem.of(item.getType().getKey(), item.getAmount(), io.papermc.paper.adventure.PaperAdventure.asBinaryTagHolder(tag)))); ++ } ++ ++ @Override ++ public net.kyori.adventure.text.@org.jetbrains.annotations.NotNull Component displayName(@org.jetbrains.annotations.NotNull ItemStack itemStack) { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(CraftItemStack.asNMSCopy(itemStack).displayName()); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index 71136bb009d6c8c92595f957ee7680a771ea9a63..306c6483708ae1b41bd16f122d36beec1916a776 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -14,10 +14,17 @@ import org.apache.commons.lang.Validate; + + public class CraftMerchantCustom extends CraftMerchant { + ++ @Deprecated // Paper - Adventure + public CraftMerchantCustom(String title) { + super(new MinecraftMerchant(title)); + getMerchant().craftMerchant = this; + } ++ // Paper start ++ public CraftMerchantCustom(net.kyori.adventure.text.Component title) { ++ super(new MinecraftMerchant(title)); ++ getMerchant().craftMerchant = this; ++ } ++ // Paper end + + @Override + public String toString() { +@@ -37,10 +44,17 @@ public class CraftMerchantCustom extends CraftMerchant { + private Level tradingWorld; + protected CraftMerchant craftMerchant; + ++ @Deprecated // Paper - Adventure + public MinecraftMerchant(String title) { + Validate.notNull(title, "Title cannot be null"); + this.title = new TextComponent(title); + } ++ // Paper start ++ public MinecraftMerchant(net.kyori.adventure.text.Component title) { ++ Validate.notNull(title, "Title cannot be null"); ++ this.title = io.papermc.paper.adventure.PaperAdventure.asVanilla(title); ++ } ++ // Paper end + + @Override + public CraftMerchant getCraftMerchant() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index 6dba12c4f58a462c8d466461eb8b804dd045766d..a592d4a286a775a61192dde2a4d21a0681090415 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -1,8 +1,9 @@ + package org.bukkit.craftbukkit.inventory; + + import com.google.common.collect.ImmutableList; +-import com.google.common.collect.ImmutableMap.Builder; + import com.google.common.collect.Lists; ++ ++import com.google.common.collect.ImmutableMap; // Paper + import java.util.ArrayList; + import java.util.Arrays; + import java.util.List; +@@ -17,9 +18,12 @@ import org.bukkit.craftbukkit.util.CraftChatMessage; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; + import org.bukkit.inventory.meta.BookMeta; + import org.bukkit.inventory.meta.BookMeta.Generation; ++import org.checkerframework.checker.nullness.qual.NonNull; + + // Spigot start + import static org.spigotmc.ValidateUtils.*; ++ ++import BookMetaBuilder; + import java.util.AbstractList; + import net.md_5.bungee.api.chat.BaseComponent; + import net.md_5.bungee.chat.ComponentSerializer; +@@ -269,6 +273,141 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + this.generation = (generation == null) ? null : generation.ordinal(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return this.title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(this.title); ++ } ++ ++ @Override ++ public org.bukkit.inventory.meta.BookMeta title(net.kyori.adventure.text.Component title) { ++ this.setTitle(title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title)); ++ return this; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component author() { ++ return this.author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(this.author); ++ } ++ ++ @Override ++ public org.bukkit.inventory.meta.BookMeta author(net.kyori.adventure.text.Component author) { ++ this.setAuthor(author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(author)); ++ return this; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component page(final int page) { ++ Validate.isTrue(isValidPage(page), "Invalid page number"); ++ return this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(pages.get(page - 1)) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(pages.get(page - 1)); ++ } ++ ++ @Override ++ public void page(final int page, net.kyori.adventure.text.Component data) { ++ if (!isValidPage(page)) { ++ throw new IllegalArgumentException("Invalid page number " + page + "/" + pages.size()); ++ } ++ if (data == null) { ++ data = net.kyori.adventure.text.Component.empty(); ++ } ++ pages.set(page - 1, this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(data) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(data)); ++ } ++ ++ @Override ++ public List pages() { ++ if (this.pages == null) return ImmutableList.of(); ++ if (this instanceof CraftMetaBookSigned) ++ return pages.stream().map(net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson()::deserialize).collect(ImmutableList.toImmutableList()); ++ else ++ return pages.stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::deserialize).collect(ImmutableList.toImmutableList()); ++ } ++ ++ @Override ++ public BookMeta pages(List pages) { ++ if (this.pages != null) this.pages.clear(); ++ for (net.kyori.adventure.text.Component page : pages) { ++ addPages(page); ++ } ++ return this; ++ } ++ ++ @Override ++ public BookMeta pages(net.kyori.adventure.text.Component... pages) { ++ if (this.pages != null) this.pages.clear(); ++ addPages(pages); ++ return this; ++ } ++ ++ @Override ++ public void addPages(net.kyori.adventure.text.Component... pages) { ++ if (this.pages == null) this.pages = new ArrayList<>(); ++ for (net.kyori.adventure.text.Component page : pages) { ++ if (this.pages.size() >= MAX_PAGES) { ++ return; ++ } ++ ++ if (page == null) { ++ page = net.kyori.adventure.text.Component.empty(); ++ } ++ ++ this.pages.add(this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(page) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(page)); ++ } ++ } ++ ++ private CraftMetaBook(net.kyori.adventure.text.Component title, net.kyori.adventure.text.Component author, List pages) { ++ super((org.bukkit.craftbukkit.inventory.CraftMetaItem) org.bukkit.Bukkit.getItemFactory().getItemMeta(org.bukkit.Material.WRITABLE_BOOK)); ++ this.title = title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title); ++ this.author = author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(author); ++ this.pages = pages.subList(0, Math.min(MAX_PAGES, pages.size())).stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::serialize).collect(java.util.stream.Collectors.toList()); ++ } ++ ++ static final class CraftMetaBookBuilder implements BookMetaBuilder { ++ private net.kyori.adventure.text.Component title = null; ++ private net.kyori.adventure.text.Component author = null; ++ private final List pages = new java.util.ArrayList<>(); ++ ++ @Override ++ public BookMetaBuilder title(net.kyori.adventure.text.Component title) { ++ this.title = title; ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder author(net.kyori.adventure.text.Component author) { ++ this.author = author; ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder addPage(net.kyori.adventure.text.Component page) { ++ this.pages.add(page); ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder pages(net.kyori.adventure.text.Component... pages) { ++ java.util.Collections.addAll(this.pages, pages); ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder pages(java.util.Collection pages) { ++ this.pages.addAll(pages); ++ return this; ++ } ++ ++ @Override ++ public BookMeta build() { ++ return new CraftMetaBook(title, author, pages); ++ } ++ } ++ ++ @Override ++ public BookMetaBuilder toBuilder() { ++ return new CraftMetaBookBuilder(); ++ } ++ ++ // Paper end + @Override + public String getPage(final int page) { + Validate.isTrue(isValidPage(page), "Invalid page number"); +@@ -413,7 +552,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + } + + @Override +- Builder serialize(Builder builder) { ++ ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + + if (hasTitle()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +index 00445fc7373c70f4cecc4114f9bcfb4b6f27c0e8..0cf60eb9b6ba1a79c9b603c4349debd478101f9a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +@@ -1,6 +1,6 @@ + package org.bukkit.craftbukkit.inventory; + +-import com.google.common.collect.ImmutableMap.Builder; ++import com.google.common.collect.ImmutableMap; // Paper + import java.util.Map; + import net.minecraft.nbt.CompoundTag; + import org.bukkit.Material; +@@ -84,7 +84,7 @@ class CraftMetaBookSigned extends CraftMetaBook implements BookMeta { + } + + @Override +- Builder serialize(Builder builder) { ++ ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + return builder; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 2e819849e83967d751f7b0612a7cf4edb5a7264b..cca04daf84e506382365c0ba945cb024bd4d4475 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -744,6 +744,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() { ++ return displayName == null ? null : net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(displayName); ++ } ++ ++ @Override ++ public void displayName(final net.kyori.adventure.text.Component displayName) { ++ this.displayName = displayName == null ? null : net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(displayName); ++ } ++ // Paper end ++ + @Override + public String getDisplayName() { + return CraftChatMessage.fromJSONComponent(displayName); +@@ -779,6 +791,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return this.lore != null && !this.lore.isEmpty(); + } + ++ // Paper start ++ @Override ++ public List lore() { ++ return this.lore != null ? io.papermc.paper.adventure.PaperAdventure.asAdventureFromJson(this.lore) : null; ++ } ++ ++ @Override ++ public void lore(final List lore) { ++ this.lore = lore != null ? io.papermc.paper.adventure.PaperAdventure.asJson(lore) : null; ++ } ++ // Paper end ++ + @Override + public boolean hasRepairCost() { + return repairCost > 0; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java +index ed4415f6dd588c08c922efd5beebb3b124beb9d6..78a7ac47f20e84ccd67ff44d0bc7a2f2faa0d476 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java +@@ -12,6 +12,13 @@ public class CraftCustomInventoryConverter implements CraftInventoryCreator.Inve + return new CraftInventoryCustom(holder, type); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ return new CraftInventoryCustom(owner, type, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + return new CraftInventoryCustom(owner, type, title); +@@ -21,6 +28,12 @@ public class CraftCustomInventoryConverter implements CraftInventoryCreator.Inve + return new CraftInventoryCustom(owner, size); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) { ++ return new CraftInventoryCustom(owner, size, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder owner, int size, String title) { + return new CraftInventoryCustom(owner, size, title); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +index b51321c8dd70a90ab149f456c7ffb4587c4fbd34..94d807c5d09f165c6eedd0a1c4026c2b833806a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +@@ -43,6 +43,17 @@ public final class CraftInventoryCreator { + return converterMap.get(type).createInventory(holder, type); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder holder, InventoryType type, net.kyori.adventure.text.Component title) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type, title); ++ } ++ //noinspection ConstantConditions // Paper end ++ return converterMap.get(type).createInventory(holder, type, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + return converterMap.get(type).createInventory(holder, type, title); + } +@@ -51,6 +62,12 @@ public final class CraftInventoryCreator { + return DEFAULT_CONVERTER.createInventory(holder, size); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder holder, int size, net.kyori.adventure.text.Component title) { ++ return DEFAULT_CONVERTER.createInventory(holder, size, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder holder, int size, String title) { + return DEFAULT_CONVERTER.createInventory(holder, size, title); + } +@@ -59,6 +76,10 @@ public final class CraftInventoryCreator { + + Inventory createInventory(InventoryHolder holder, InventoryType type); + ++ // Paper start ++ Inventory createInventory(InventoryHolder holder, InventoryType type, net.kyori.adventure.text.Component title); ++ // Paper end ++ + Inventory createInventory(InventoryHolder holder, InventoryType type, String title); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +index 0395a6235d62126540e370d6ef77e6966c50ca1d..3ef9742a68084d159ea8ed5709298f9863e04a67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +@@ -31,6 +31,18 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return getInventory(getTileEntity()); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ Container te = getTileEntity(); ++ if (te instanceof RandomizableContainerBlockEntity) { ++ ((RandomizableContainerBlockEntity) te).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ } ++ ++ return getInventory(te); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + Container te = getTileEntity(); +@@ -54,6 +66,15 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return furnace; + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ Container tileEntity = getTileEntity(); ++ ((AbstractFurnaceBlockEntity) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ return getInventory(tileEntity); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + Container tileEntity = getTileEntity(); +@@ -74,6 +95,18 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return new BrewingStandBlockEntity(); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ // BrewingStand does not extend TileEntityLootable ++ Container tileEntity = getTileEntity(); ++ if (tileEntity instanceof BrewingStandBlockEntity) { ++ ((BrewingStandBlockEntity) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ } ++ return getInventory(tileEntity); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + // BrewingStand does not extend TileEntityLootable +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +index d5ebb11447a8effa41138a13874fd3e5246dfe71..96ba508c87336d012c1ed5aa982588f55782a2fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +@@ -30,6 +30,21 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective + return objective.getName(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(objective.getDisplayName()); ++ } ++ @Override ++ public void displayName(net.kyori.adventure.text.Component displayName) throws IllegalStateException, IllegalArgumentException { ++ if (displayName == null) { ++ displayName = net.kyori.adventure.text.Component.empty(); ++ } ++ CraftScoreboard scoreboard = checkState(); ++ objective.setDisplayName(io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); ++ } ++ // Paper end + @Override + public String getDisplayName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index af160d3bdb82f07c6c836a438b8ab37b803c29e2..4c93be31fd95d731327479519ecb34a08785c1ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -27,6 +27,27 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + public CraftObjective registerNewObjective(String name, String criteria) throws IllegalArgumentException { + return registerNewObjective(name, criteria, name); + } ++ // Paper start ++ @Override ++ public CraftObjective registerNewObjective(String name, String criteria, net.kyori.adventure.text.Component displayName) { ++ return registerNewObjective(name, criteria, displayName, org.bukkit.scoreboard.RenderType.INTEGER); ++ } ++ @Override ++ public CraftObjective registerNewObjective(String name, String criteria, net.kyori.adventure.text.Component displayName, RenderType renderType) { ++ if (displayName == null) { ++ displayName = net.kyori.adventure.text.Component.empty(); ++ } ++ Validate.notNull(name, "Objective name cannot be null"); ++ Validate.notNull(criteria, "Criteria cannot be null"); ++ Validate.notNull(displayName, "Display name cannot be null"); ++ Validate.notNull(renderType, "RenderType cannot be null"); ++ Validate.isTrue(name.length() <= 16, "The name '" + name + "' is longer than the limit of 16 characters"); ++ Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); ++ CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ net.minecraft.world.scores.Objective objective = board.registerObjective(name, craftCriteria.criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); ++ return new CraftObjective(this, objective); ++ } ++ // Paper end + + @Override + public CraftObjective registerNewObjective(String name, String criteria, String displayName) throws IllegalArgumentException { +@@ -35,7 +56,7 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + + @Override + public CraftObjective registerNewObjective(String name, String criteria, String displayName, RenderType renderType) throws IllegalArgumentException { +- Validate.notNull(name, "Objective name cannot be null"); ++ /*Validate.notNull(name, "Objective name cannot be null"); // Paper + Validate.notNull(criteria, "Criteria cannot be null"); + Validate.notNull(displayName, "Display name cannot be null"); + Validate.notNull(renderType, "RenderType cannot be null"); +@@ -44,8 +65,9 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); +- net.minecraft.world.scores.Objective objective = board.registerObjective(name, craftCriteria.criteria, CraftChatMessage.fromStringOrNull(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); +- return new CraftObjective(this, objective); ++ ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, CraftChatMessage.fromStringOrNull(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); ++ return new CraftObjective(this, objective);*/ // Paper ++ return registerNewObjective(name, criteria, io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(displayName), renderType); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index f5237ff19d8b26be0b762659ad0ea6887205b3f9..7ebcba4ada42f5599d56cfdeb75dbf62f2a09b78 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -28,6 +28,55 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + + return team.getName(); + } ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getDisplayName()); ++ } ++ @Override ++ public void displayName(net.kyori.adventure.text.Component displayName) throws IllegalStateException, IllegalArgumentException { ++ if (displayName == null) displayName = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setDisplayName(io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); ++ } ++ @Override ++ public net.kyori.adventure.text.Component prefix() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getPlayerPrefix()); ++ } ++ @Override ++ public void prefix(net.kyori.adventure.text.Component prefix) throws IllegalStateException, IllegalArgumentException { ++ if (prefix == null) prefix = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setPlayerPrefix(io.papermc.paper.adventure.PaperAdventure.asVanilla(prefix)); ++ } ++ @Override ++ public net.kyori.adventure.text.Component suffix() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getPlayerSuffix()); ++ } ++ @Override ++ public void suffix(net.kyori.adventure.text.Component suffix) throws IllegalStateException, IllegalArgumentException { ++ if (suffix == null) suffix = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setPlayerSuffix(io.papermc.paper.adventure.PaperAdventure.asVanilla(suffix)); ++ } ++ @Override ++ public net.kyori.adventure.text.format.TextColor color() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ if (team.getColor().getHexValue() == null) throw new IllegalStateException("Team colors must have hex values"); ++ net.kyori.adventure.text.format.TextColor color = net.kyori.adventure.text.format.TextColor.color(team.getColor().getHexValue()); ++ if (!(color instanceof net.kyori.adventure.text.format.NamedTextColor)) throw new IllegalStateException("Team doesn't have a NamedTextColor"); ++ return (net.kyori.adventure.text.format.NamedTextColor) color; ++ } ++ @Override ++ public void color(net.kyori.adventure.text.format.NamedTextColor color) { ++ if (color == null) color = net.kyori.adventure.text.format.NamedTextColor.WHITE; ++ CraftScoreboard scoreboard = checkState(); ++ team.setColor(io.papermc.paper.adventure.PaperAdventure.asVanilla(color)); ++ } ++ // Paper end + + @Override + public String getDisplayName() throws IllegalStateException { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index 35e6ce5f33b3d6b86c3654e6a207e26aa44badb5..b27af66795d902a2e95d692fa0ff18eccbef8a75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -290,6 +290,7 @@ public final class CraftChatMessage { + + public static String fromComponent(Component component) { + if (component == null) return ""; ++ if (component instanceof io.papermc.paper.adventure.AdventureComponent) component = ((io.papermc.paper.adventure.AdventureComponent) component).deepConverted(); + StringBuilder out = new StringBuilder(); + + boolean hadFormat = false; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ef7715774fbdc4c42b217d8192784e09a43fe66f..2d4faef5a2b9c4fe8b65ff4f1346b8375e0e02c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -57,6 +57,33 @@ public final class CraftMagicNumbers implements UnsafeValues { + + private CraftMagicNumbers() {} + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.flattener.ComponentFlattener componentFlattener() { ++ return io.papermc.paper.adventure.PaperAdventure.FLATTENER; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.gson.GsonComponentSerializer colorDownsamplingGsonComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.COLOR_DOWNSAMPLING_GSON; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.gson.GsonComponentSerializer gsonComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.GSON; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.plain.PlainComponentSerializer plainComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.PLAIN; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer legacyComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC; ++ } ++ // Paper end ++ + public static BlockState getBlock(MaterialData material) { + return getBlock(material.getItemType(), material.getData()); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java +index f194cf2663919ea18309a0501ddfab5e2ed639dd..4b110d6c6f22ff7c2fa0fd4b459820797066199d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java +@@ -80,7 +80,7 @@ public abstract class LazyHashSet implements Set { + return this.reference = makeReference(); + } + +- abstract Set makeReference(); ++ protected abstract Set makeReference(); // Paper - protected + + public boolean isLazy() { + return reference == null; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java +index 103c1407b2946e9cae2271646178e5f243c8abb1..b22b7603f3f66e1b64f413106e7989b30475110b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java +@@ -15,10 +15,15 @@ public class LazyPlayerSet extends LazyHashSet { + } + + @Override +- HashSet makeReference() { ++ protected HashSet makeReference() { // Paper - protected + if (reference != null) { + throw new IllegalStateException("Reference already created!"); + } ++ // Paper start ++ return makePlayerSet(this.server); ++ } ++ public static HashSet makePlayerSet(final MinecraftServer server) { ++ // Paper end + List players = server.getPlayerList().players; + HashSet reference = new HashSet(players.size()); + for (ServerPlayer player : players) { diff --git a/Remapped-Spigot-Server-Patches/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch b/Remapped-Spigot-Server-Patches/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch new file mode 100644 index 000000000..2bef4fada --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:02:51 -0600 +Subject: [PATCH] Configurable cactus bamboo and reed growth heights + +Bamboo - Both the minimum fully-grown heights and the maximum are configurable +- Machine_Maker + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b31109d2dadd29e8852468c19265066b773d2be0..3618cc017feb60e257a28f67cbddca3f792a9833 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -65,4 +65,17 @@ public class PaperWorldConfig { + config.addDefault("world-settings.default." + path, def); + return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); + } ++ ++ public int cactusMaxHeight; ++ public int reedMaxHeight; ++ public int bambooMaxHeight; ++ public int bambooMinHeight; ++ private void blockGrowthHeight() { ++ cactusMaxHeight = getInt("max-growth-height.cactus", 3); ++ reedMaxHeight = getInt("max-growth-height.reeds", 3); ++ bambooMaxHeight = getInt("max-growth-height.bamboo.max", 16); ++ bambooMinHeight = getInt("max-growth-height.bamboo.min", 11); ++ log("Max height for cactus growth " + cactusMaxHeight + ". Max height for reed growth " + reedMaxHeight + ". Max height for bamboo growth " + bambooMaxHeight + ". Min height for fully-grown bamboo " + bambooMinHeight + "."); ++ ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BambooBlock.java b/src/main/java/net/minecraft/world/level/block/BambooBlock.java +index 8f423ae6261434a670bb94aa70b6bc1694f1fc45..36583c189aa5e55de7f5eba362285e57c8279176 100644 +--- a/src/main/java/net/minecraft/world/level/block/BambooBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BambooBlock.java +@@ -124,7 +124,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + if (random.nextInt(Math.max(1, (int) (100.0F / world.spigotConfig.bambooModifier) * 3)) == 0 && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot + int i = this.getHeightBelowUpToMax(world, pos) + 1; + +- if (i < 16) { ++ if (i < world.paperConfig.bambooMaxHeight) { // Paper + this.growBamboo(state, (Level) world, pos, random, i); + } + } +@@ -155,7 +155,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + int i = this.getHeightAboveUpToMax(world, pos); + int j = this.getHeightBelowUpToMax(world, pos); + +- return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooBlock.STAGE) != 1; ++ return i + j + 1 < ((Level) world).paperConfig.bambooMaxHeight && (Integer) world.getBlockState(pos.above(i)).getValue(BambooBlock.STAGE) != 1; // Paper + } + + @Override +@@ -174,7 +174,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + BlockPos blockposition1 = pos.above(i); + BlockState iblockdata1 = world.getBlockState(blockposition1); + +- if (k >= 16 || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here ++ if (k >= world.paperConfig.bambooMaxHeight || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus bamboo and reed growth heights + return; + } + +@@ -215,7 +215,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + } + + int j = (Integer) state.getValue(BambooBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1; +- int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1; ++ int k = (height < world.paperConfig.bambooMinHeight || random.nextFloat() >= 0.25F) && height != (world.paperConfig.bambooMaxHeight - 1) ? 0 : 1; // Paper + + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooBlock.AGE, j)).setValue(BambooBlock.LEAVES, blockpropertybamboosize)).setValue(BambooBlock.STAGE, k), 3)) { +@@ -230,7 +230,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig.bambooMaxHeight && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper + ; + } + +@@ -240,7 +240,7 @@ public class BambooBlock extends Block implements BonemealableBlock { + protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig.bambooMaxHeight && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper + ; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index d07fd9c1f726b1d45992352408499034c12683e6..de61393e3f702554817d81ff10693ec3fb63d492 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -54,7 +54,7 @@ public class CactusBlock extends Block { + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig.cactusMaxHeight) { // Paper - Configurable growth height + int j = (Integer) state.getValue(CactusBlock.AGE); + + if (j >= (byte) range(3, ((100.0F / world.spigotConfig.cactusModifier) * 15) + 0.5F, 15)) { // Spigot +diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +index 25f634ee93fa4678eaf09694d98783f2aef9d0f0..a795732af122204b88a01311e73892658132da25 100644 +--- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +@@ -51,7 +51,7 @@ public class SugarCaneBlock extends Block { + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig.reedMaxHeight) { // Paper - Configurable growth height + int j = (Integer) state.getValue(SugarCaneBlock.AGE); + + if (j >= (byte) range(3, ((100.0F / world.spigotConfig.caneModifier) * 15) + 0.5F, 15)) { // Spigot diff --git a/Remapped-Spigot-Server-Patches/0012-Configurable-baby-zombie-movement-speed.patch b/Remapped-Spigot-Server-Patches/0012-Configurable-baby-zombie-movement-speed.patch new file mode 100644 index 000000000..1b143bb4a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0012-Configurable-baby-zombie-movement-speed.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:09:16 -0600 +Subject: [PATCH] Configurable baby zombie movement speed + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3618cc017feb60e257a28f67cbddca3f792a9833..796c17e0941922a9716212c6eae91643d8360418 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -78,4 +78,15 @@ public class PaperWorldConfig { + log("Max height for cactus growth " + cactusMaxHeight + ". Max height for reed growth " + reedMaxHeight + ". Max height for bamboo growth " + bambooMaxHeight + ". Min height for fully-grown bamboo " + bambooMinHeight + "."); + + } ++ ++ public double babyZombieMovementModifier; ++ private void babyZombieMovementModifier() { ++ babyZombieMovementModifier = getDouble("baby-zombie-movement-modifier", 0.5D); ++ if (PaperConfig.version < 20) { ++ babyZombieMovementModifier = getDouble("baby-zombie-movement-speed", 0.5D); ++ set("baby-zombie-movement-modifier", babyZombieMovementModifier); ++ } ++ ++ log("Baby zombies will move at the speed of " + babyZombieMovementModifier); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 992c9646c274350b30c1abb75e0469adc471397f..94e2a8f74e74d68d4a9b82b667fbff24b7e9e629 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -79,7 +79,7 @@ import org.bukkit.event.entity.EntityTransformEvent; + public class Zombie extends Monster { + + private static final UUID SPEED_MODIFIER_BABY_UUID = UUID.fromString("B9766B59-9566-4402-BC1F-2EE2A276D836"); +- private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_UUID, "Baby speed boost", 0.5D, AttributeModifier.Operation.MULTIPLY_BASE); ++ private final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_UUID, "Baby speed boost", 0.5D, AttributeModifier.Operation.MULTIPLY_BASE); private final AttributeModifier babyModifier = this.SPEED_MODIFIER_BABY; // Paper - remove static - Make baby speed configurable + private static final EntityDataAccessor DATA_BABY_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor DATA_SPECIAL_TYPE_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.INT); + public static final EntityDataAccessor DATA_DROWNED_CONVERSION_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); +@@ -182,9 +182,9 @@ public class Zombie extends Monster { + if (this.level != null && !this.level.isClientSide) { + AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); + +- attributemodifiable.removeModifier(Zombie.SPEED_MODIFIER_BABY); ++ attributemodifiable.removeModifier(this.babyModifier); // Paper + if (baby) { +- attributemodifiable.addTransientModifier(Zombie.SPEED_MODIFIER_BABY); ++ attributemodifiable.addTransientModifier(this.babyModifier); // Paper + } + } + diff --git a/Remapped-Spigot-Server-Patches/0013-Configurable-fishing-time-ranges.patch b/Remapped-Spigot-Server-Patches/0013-Configurable-fishing-time-ranges.patch new file mode 100644 index 000000000..33edff1ef --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0013-Configurable-fishing-time-ranges.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:14:11 -0600 +Subject: [PATCH] Configurable fishing time ranges + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 796c17e0941922a9716212c6eae91643d8360418..78948c42b13194005bdbbbc69c2b7ae0732a78c5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -89,4 +89,12 @@ public class PaperWorldConfig { + + log("Baby zombies will move at the speed of " + babyZombieMovementModifier); + } ++ ++ public int fishingMinTicks; ++ public int fishingMaxTicks; ++ private void fishingTickRange() { ++ fishingMinTicks = getInt("fishing-time-range.MinimumTicks", 100); ++ fishingMaxTicks = getInt("fishing-time-range.MaximumTicks", 600); ++ log("Fishing time ranges are between " + fishingMinTicks +" and " + fishingMaxTicks + " ticks"); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index d74dae6d7bd78c082b39a4e38da640a57c40b341..2f67c2065ef29f17f12190b25bd1ea53e1fb55b4 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -82,6 +82,10 @@ public class FishingHook extends Projectile { + owner.fishing = this; + this.luck = Math.max(0, lureLevel); + this.lureSpeed = Math.max(0, luckOfTheSeaLevel); ++ // Paper start ++ minWaitTime = world.paperConfig.fishingMinTicks; ++ maxWaitTime = world.paperConfig.fishingMaxTicks; ++ // paper end + } + + public FishingHook(net.minecraft.world.entity.player.Player thrower, Level world, int lureLevel, int luckOfTheSeaLevel) { diff --git a/Remapped-Spigot-Server-Patches/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch b/Remapped-Spigot-Server-Patches/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch new file mode 100644 index 000000000..6f5fd676d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:24:16 -0600 +Subject: [PATCH] Allow nerfed mobs to jump and take water damage + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 78948c42b13194005bdbbbc69c2b7ae0732a78c5..b41e7922dd96c3358eb849ab39982a75736e3476 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -97,4 +97,9 @@ public class PaperWorldConfig { + fishingMaxTicks = getInt("fishing-time-range.MaximumTicks", 600); + log("Fishing time ranges are between " + fishingMinTicks +" and " + fishingMaxTicks + " ticks"); + } ++ ++ public boolean nerfedMobsShouldJump; ++ private void nerfedMobsShouldJump() { ++ nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2a7f587e19fcdd6d01b360d6b47d9eadd9df92cc..584e83441a9fef88eb1b0a29bec8bda29d6a0c9c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1104,6 +1104,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.isInWater() || this.isInRain(); + } + ++ public final boolean isInWaterOrRainOrBubble() { return isInWaterRainOrBubble(); } // Paper - OBFHELPER + public boolean isInWaterRainOrBubble() { + return this.isInWater() || this.isInRain() || this.isInBubbleColumn(); + } +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 99cb4dc1a1009d4a29e651c94d21babcc61388ed..151ebcffc1f2ae02fa55ab83d2ae7d8a0057f29d 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity; + ++import PathfinderGoalFloat; + import com.google.common.collect.Maps; + import java.util.Arrays; + import java.util.Iterator; +@@ -96,6 +97,7 @@ public abstract class Mob extends LivingEntity { + private final BodyRotationControl bodyRotationControl; + protected PathNavigation navigation; + public GoalSelector goalSelector; ++ @Nullable public PathfinderGoalFloat goalFloat; // Paper + public GoalSelector targetSelector; + private LivingEntity target; + private final Sensing sensing; +@@ -782,7 +784,17 @@ public abstract class Mob extends LivingEntity { + @Override + protected final void serverAiStep() { + ++this.noActionTime; +- if (!this.aware) return; // CraftBukkit ++ if (!this.aware) { // Paper start - Allow nerfed mobs to jump, float and take water damage ++ if (goalFloat != null) { ++ if (goalFloat.validConditions()) goalFloat.update(); ++ this.getJumpControl().jumpIfSet(); ++ } ++ if ((this instanceof EntityBlaze || this instanceof EntityEnderman) && isInWaterOrRainOrBubble()) { ++ hurt(DamageSource.DROWN, 1.0F); ++ } ++ return; ++ } ++ // Paper end + this.level.getProfiler().push("sensing"); + this.sensing.tick(); + this.level.getProfiler().pop(); +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/JumpControl.java b/src/main/java/net/minecraft/world/entity/ai/control/JumpControl.java +index 5f7ad2b57d8d8f0f6a7d880f55e08b52f017cf51..09d1cda50ce9076e9236d124aa7766a26a50dae1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/JumpControl.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/JumpControl.java +@@ -15,6 +15,7 @@ public class JumpControl { + this.jump = true; + } + ++ public final void jumpIfSet() { this.tick(); } // Paper - OBFHELPER + public void tick() { + this.mob.setJumping(this.jump); + this.jump = false; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java +index 7ea5cb5a92ff3b66859ebcd53031aa06689bd329..790b5646683247ef757095a0763dc52701afe97b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java +@@ -11,15 +11,18 @@ public class FloatGoal extends Goal { + + public FloatGoal(Mob mob) { + this.mob = mob; ++ if (mob.getCommandSenderWorld().paperConfig.nerfedMobsShouldJump) mob.goalFloat = this; // Paper + this.setFlags(EnumSet.of(Goal.Flag.JUMP)); + mob.getNavigation().setCanFloat(true); + } + ++ public final boolean validConditions() { return this.canUse(); } // Paper - OBFHELPER + @Override + public boolean canUse() { + return this.mob.isInWater() && this.mob.getFluidHeight((Tag) FluidTags.WATER) > this.mob.getFluidJumpThreshold() || this.mob.isInLava(); + } + ++ public void update() { this.tick(); } // Paper - OBFHELPER + @Override + public void tick() { + if (this.mob.getRandom().nextFloat() < 0.8F) { diff --git a/Remapped-Spigot-Server-Patches/0015-Add-configurable-despawn-distances-for-living-entiti.patch b/Remapped-Spigot-Server-Patches/0015-Add-configurable-despawn-distances-for-living-entiti.patch new file mode 100644 index 000000000..4a3d5d60e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0015-Add-configurable-despawn-distances-for-living-entiti.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Suddenly +Date: Tue, 1 Mar 2016 13:51:54 -0600 +Subject: [PATCH] Add configurable despawn distances for living entities + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b41e7922dd96c3358eb849ab39982a75736e3476..2f0d582baf0eb2bb477944d0cb1369db6ca33956 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -102,4 +102,20 @@ public class PaperWorldConfig { + private void nerfedMobsShouldJump() { + nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", false); + } ++ ++ public int softDespawnDistance; ++ public int hardDespawnDistance; ++ private void despawnDistances() { ++ softDespawnDistance = getInt("despawn-ranges.soft", 32); // 32^2 = 1024, Minecraft Default ++ hardDespawnDistance = getInt("despawn-ranges.hard", 128); // 128^2 = 16384, Minecraft Default ++ ++ if (softDespawnDistance > hardDespawnDistance) { ++ softDespawnDistance = hardDespawnDistance; ++ } ++ ++ log("Living Entity Despawn Ranges: Soft: " + softDespawnDistance + " Hard: " + hardDespawnDistance); ++ ++ softDespawnDistance = softDespawnDistance*softDespawnDistance; ++ hardDespawnDistance = hardDespawnDistance*hardDespawnDistance; ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 151ebcffc1f2ae02fa55ab83d2ae7d8a0057f29d..4d3000067ae3d46b7ed4dda6146a21993199c6d9 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -762,16 +762,16 @@ public abstract class Mob extends LivingEntity { + int i = this.getType().getCategory().getDespawnDistance(); + int j = i * i; + +- if (d0 > (double) j) { // CraftBukkit - remove isTypeNotPersistent() check ++ if (d0 > (double) level.paperConfig.hardDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances + this.remove(); + } + + int k = this.getType().getCategory().getNoDespawnDistance(); + int l = k * k; + +- if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > (double) l) { // CraftBukkit - remove isTypeNotPersistent() check ++ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > level.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances + this.remove(); +- } else if (d0 < (double) l) { ++ } else if (d0 < level.paperConfig.softDespawnDistance) { // Paper - custom despawn distances + this.noActionTime = 0; + } + } diff --git a/Remapped-Spigot-Server-Patches/0016-Allow-for-toggling-of-spawn-chunks.patch b/Remapped-Spigot-Server-Patches/0016-Allow-for-toggling-of-spawn-chunks.patch new file mode 100644 index 000000000..91cd6d02b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0016-Allow-for-toggling-of-spawn-chunks.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 3 Mar 2016 03:53:43 -0600 +Subject: [PATCH] Allow for toggling of spawn chunks + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2f0d582baf0eb2bb477944d0cb1369db6ca33956..89e76dd73811fd0f6f8c8e7e5af804d5a4bb5a75 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -118,4 +118,10 @@ public class PaperWorldConfig { + softDespawnDistance = softDespawnDistance*softDespawnDistance; + hardDespawnDistance = hardDespawnDistance*hardDespawnDistance; + } ++ ++ public boolean keepSpawnInMemory; ++ private void keepSpawnInMemory() { ++ keepSpawnInMemory = getBoolean("keep-spawn-loaded", true); ++ log("Keep spawn chunk loaded: " + keepSpawnInMemory); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index c153df1f4dea3dc0ae744bde01e334b3bd3b50af..832abf73bdab2488c5814ea6e57888aac1b26154 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -217,6 +217,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + }); + // CraftBukkit end + timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings ++ this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } diff --git a/Remapped-Spigot-Server-Patches/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/Remapped-Spigot-Server-Patches/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch new file mode 100644 index 000000000..91e53448a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 14:14:15 -0600 +Subject: [PATCH] Drop falling block and tnt entities at the specified height + +* Dec 2, 2020 Added tnt nerf for tnt minecarts - Machine_Maker + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 89e76dd73811fd0f6f8c8e7e5af804d5a4bb5a75..d16ae924bcbe31c964f7fb448757c748e5c4418c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -124,4 +124,14 @@ public class PaperWorldConfig { + keepSpawnInMemory = getBoolean("keep-spawn-loaded", true); + log("Keep spawn chunk loaded: " + keepSpawnInMemory); + } ++ ++ public int fallingBlockHeightNerf; ++ public int entityTNTHeightNerf; ++ private void heightNerfs() { ++ fallingBlockHeightNerf = getInt("falling-block-height-nerf", 0); ++ entityTNTHeightNerf = getInt("tnt-entity-height-nerf", 0); ++ ++ if (fallingBlockHeightNerf != 0) log("Falling Block Height Limit set to Y: " + fallingBlockHeightNerf); ++ if (entityTNTHeightNerf != 0) log("TNT Entity Height Limit set to Y: " + entityTNTHeightNerf); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 584e83441a9fef88eb1b0a29bec8bda29d6a0c9c..706417f44c1eebc7cc5e8e7053fa0ab21f4caeba 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1849,6 +1849,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.spawnAtLocation(stack, 0.0F); + } + ++ @Nullable public final ItemEntity dropItem(ItemStack itemstack, float offset) { return this.spawnAtLocation(itemstack, offset); } // Paper - OBFHELPER + @Nullable + public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { + if (stack.isEmpty()) { +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 8a9e2316b9f5756503dc06e27981525d2cd7d1a5..5394bc6336cb84025c1c748fb5b3d38e0648a590 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -122,6 +122,17 @@ public class FallingBlockEntity extends Entity { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ ++ // Paper start - Configurable EntityFallingBlock height nerf ++ if (this.level.paperConfig.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig.fallingBlockHeightNerf) { ++ if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ++ this.spawnAtLocation(block); ++ } ++ ++ this.remove(); ++ return; ++ } ++ // Paper end + if (!this.level.isClientSide) { + blockposition = this.blockPosition(); + boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; +diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +index ff15a372b7cad5fa88b7ef6de1f3441d93f9c67e..4c4262b8f0cb44b8cea8cb46194a6e70d4ce56f4 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -69,6 +69,12 @@ public class PrimedTnt extends Entity { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ // Paper start - Configurable TNT entity height nerf ++ if (this.level.paperConfig.entityTNTHeightNerf != 0 && this.getY() > this.level.paperConfig.entityTNTHeightNerf) { ++ this.remove(); ++ return; ++ } ++ // Paper end + this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); + if (this.onGround) { + this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java +index c2ed3ba42d29a50386c94b109fdd3b2f2f1b433b..3b5e96f2325e14a94de0fb2d6da86812cecc7395 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java +@@ -47,6 +47,12 @@ public class MinecartTNT extends AbstractMinecart { + public void tick() { + super.tick(); + if (this.fuse > 0) { ++ // Paper start - Configurable TNT entity height nerf ++ if (this.level.paperConfig.entityTNTHeightNerf != 0 && this.getY() > this.level.paperConfig.entityTNTHeightNerf) { ++ this.remove(); ++ return; ++ } ++ // Paper end + --this.fuse; + this.level.addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D); + } else if (this.fuse == 0) { diff --git a/Remapped-Spigot-Server-Patches/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/Remapped-Spigot-Server-Patches/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch new file mode 100644 index 000000000..e00adead2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 14:32:43 -0600 +Subject: [PATCH] Show 'Paper' in client crashes, server lists, and Mojang + stats + + +diff --git a/src/main/java/net/minecraft/server/Eula.java b/src/main/java/net/minecraft/server/Eula.java +index 6934b0fdfe11ef673a3e4ae7564d04acee169252..9f104b1bd05d9f344579f086b2b9c00af1750690 100644 +--- a/src/main/java/net/minecraft/server/Eula.java ++++ b/src/main/java/net/minecraft/server/Eula.java +@@ -72,7 +72,7 @@ public class Eula { + Properties properties = new Properties(); + + properties.setProperty("eula", "false"); +- properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); ++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a456b9cbf0e5eea4e888e0e3d07ed17558650371..fa29790600021809f31092a90e1a3a9b84d5e0c4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1340,7 +1340,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // CraftBukkit - cb > vanilla! ++ return "Paper"; //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + } + + public CrashReport fillReport(CrashReport report) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f7f5457d20586e0ba72368e64ff6025f6755e61e..f81def94a1a7ab3a24b74a8bbd5f3e8ebae2c0d5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -224,7 +224,7 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + + public final class CraftServer implements Server { +- private final String serverName = "CraftBukkit"; ++ private final String serverName = "Paper"; // Paper + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index ce9f10f890a5866ab6208c7253b15b09fe323a81..e8c225fcd1a3fa5a7e1971683b1876dd6462a1e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -206,12 +206,25 @@ public class Main { + deadline.add(Calendar.DAY_OF_YEAR, -28); + if (buildDate.before(deadline.getTime())) { + System.err.println("*** Error, this build is outdated ***"); +- System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); ++ System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper + System.err.println("*** Server will start in 20 seconds ***"); + Thread.sleep(TimeUnit.SECONDS.toMillis(20)); + } + } + ++ // Paper start - Log Java and OS versioning to help with debugging plugin issues ++ java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean(); ++ java.lang.management.OperatingSystemMXBean osMX = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); ++ if (runtimeMX != null && osMX != null) { ++ String javaInfo = "Java " + runtimeMX.getSpecVersion() + " (" + runtimeMX.getVmName() + " " + runtimeMX.getVmVersion() + ")"; ++ String osInfo = "Host: " + osMX.getName() + " " + osMX.getVersion() + " (" + osMX.getArch() + ")"; ++ ++ System.out.println("System Info: " + javaInfo + " " + osInfo); ++ } else { ++ System.out.println("Unable to read system info"); ++ } ++ // Paper end ++ + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 9a695b80ef57d677fbdee1bfc59f0f9125a7ebd4..21d7b483920841456707fe3f08b180c1f072b7f7 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -19,7 +19,7 @@ public class WatchdogThread extends Thread + + private WatchdogThread(long timeoutTime, boolean restart) + { +- super( "Spigot Watchdog Thread" ); ++ super( "Paper Watchdog Thread" ); + this.timeoutTime = timeoutTime; + this.restart = restart; + } +@@ -65,14 +65,14 @@ public class WatchdogThread extends Thread + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); ++ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); + log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); + log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); + log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); +- log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://www.spigotmc.org/" ); ++ log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); + log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); +- log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); ++ log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); + // + if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) + { +@@ -82,7 +82,7 @@ public class WatchdogThread extends Thread + } + // + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); ++ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/Remapped-Spigot-Server-Patches/0019-Implement-Paper-VersionChecker.patch b/Remapped-Spigot-Server-Patches/0019-Implement-Paper-VersionChecker.patch new file mode 100644 index 000000000..66c45ee1f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0019-Implement-Paper-VersionChecker.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 27 May 2019 03:40:05 -0500 +Subject: [PATCH] Implement Paper VersionChecker + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1a1b50e475b9ede544b2f6d0d36632b24b68898c +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -0,0 +1,122 @@ ++package com.destroystokyo.paper; ++ ++import com.destroystokyo.paper.util.VersionFetcher; ++import com.google.common.base.Charsets; ++import com.google.common.io.Resources; ++import com.google.gson.*; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.io.*; ++import java.net.HttpURLConnection; ++import java.net.URL; ++import java.util.stream.StreamSupport; ++ ++public class PaperVersionFetcher implements VersionFetcher { ++ private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end ++ private static final String GITHUB_BRANCH_NAME = "master"; ++ private static @Nullable String mcVer; ++ ++ @Override ++ public long getCacheTime() { ++ return 720000; ++ } ++ ++ @Nonnull ++ @Override ++ public Component getVersionMessage(@Nonnull String serverVersion) { ++ String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); ++ return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ } ++ ++ private static @Nullable String getMinecraftVersion() { ++ if (mcVer == null) { ++ java.util.regex.Matcher matcher = VER_PATTERN.matcher(org.bukkit.Bukkit.getBukkitVersion()); ++ if (matcher.find()) { ++ String result = matcher.group(); ++ mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-' ++ } else { ++ org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!"); ++ org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString()); ++ org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion()); ++ } ++ } ++ ++ return mcVer; ++ } ++ ++ private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { ++ int distance; ++ try { ++ int jenkinsBuild = Integer.parseInt(versionInfo); ++ distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); ++ } catch (NumberFormatException ignored) { ++ versionInfo = versionInfo.replace("\"", ""); ++ distance = fetchDistanceFromGitHub(repo, branch, versionInfo); ++ } ++ ++ switch (distance) { ++ case -1: ++ return Component.text("Error obtaining version information", NamedTextColor.YELLOW); ++ case 0: ++ return Component.text("You are running the latest version", NamedTextColor.GREEN); ++ case -2: ++ return Component.text("Unknown version", NamedTextColor.YELLOW); ++ default: ++ return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW); ++ } ++ } ++ ++ private static int fetchDistanceFromSiteApi(int jenkinsBuild, @Nullable String siteApiVersion) { ++ if (siteApiVersion == null) { return -1; } ++ try { ++ try (BufferedReader reader = Resources.asCharSource( ++ new URL("https://papermc.io/api/v2/projects/paper/versions/" + siteApiVersion), ++ Charsets.UTF_8 ++ ).openBufferedStream()) { ++ JsonObject json = new Gson().fromJson(reader, JsonObject.class); ++ JsonArray builds = json.getAsJsonArray("builds"); ++ int latest = StreamSupport.stream(builds.spliterator(), false) ++ .mapToInt(e -> e.getAsInt()) ++ .max() ++ .getAsInt(); ++ return latest - jenkinsBuild; ++ } catch (JsonSyntaxException ex) { ++ ex.printStackTrace(); ++ return -1; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++ ++ // Contributed by Techcable in GH-65 ++ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { ++ try { ++ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); ++ connection.connect(); ++ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit ++ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { ++ JsonObject obj = new Gson().fromJson(reader, JsonObject.class); ++ String status = obj.get("status").getAsString(); ++ switch (status) { ++ case "identical": ++ return 0; ++ case "behind": ++ return obj.get("behind_by").getAsInt(); ++ default: ++ return -1; ++ } ++ } catch (JsonSyntaxException | NumberFormatException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 2d4faef5a2b9c4fe8b65ff4f1346b8375e0e02c8..21052d0e88351b075733331d71e07b086354b820 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -368,6 +368,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getTimingsServerName() { + return com.destroystokyo.paper.PaperConfig.timingsServerName; + } ++ ++ @Override ++ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { ++ return new com.destroystokyo.paper.PaperVersionFetcher(); ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0020-Add-version-history-to-version-command.patch b/Remapped-Spigot-Server-Patches/0020-Add-version-history-to-version-command.patch new file mode 100644 index 000000000..4985c3e74 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0020-Add-version-history-to-version-command.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kyle Wood +Date: Thu, 1 Mar 2018 19:37:52 -0600 +Subject: [PATCH] Add version history to version command + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +index 1a1b50e475b9ede544b2f6d0d36632b24b68898c..580bae0d414d371a07a6bfeefc41fdd989dc0083 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -5,7 +5,9 @@ import com.google.common.base.Charsets; + import com.google.common.io.Resources; + import com.google.gson.*; + import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextComponent; + import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; +@@ -28,7 +30,10 @@ public class PaperVersionFetcher implements VersionFetcher { + @Override + public Component getVersionMessage(@Nonnull String serverVersion) { + String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); +- return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ final Component history = getHistory(); ++ ++ return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; + } + + private static @Nullable String getMinecraftVersion() { +@@ -119,4 +124,19 @@ public class PaperVersionFetcher implements VersionFetcher { + return -1; + } + } ++ ++ @Nullable ++ private Component getHistory() { ++ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); ++ if (data == null) { ++ return null; ++ } ++ ++ final String oldVersion = data.getOldVersion(); ++ if (oldVersion == null) { ++ return null; ++ } ++ ++ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aac3f66cb23d260729c2a48d8710a9de2346aa22 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +@@ -0,0 +1,145 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.MoreObjects; ++import com.google.gson.Gson; ++import com.google.gson.JsonSyntaxException; ++import java.io.BufferedReader; ++import java.io.BufferedWriter; ++import java.io.IOException; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.Paths; ++import java.nio.file.StandardOpenOption; ++import java.util.Objects; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import org.bukkit.Bukkit; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++ ++public enum VersionHistoryManager { ++ INSTANCE; ++ ++ private final Gson gson = new Gson(); ++ ++ private final Logger logger = Bukkit.getLogger(); ++ ++ private VersionData currentData = null; ++ ++ VersionHistoryManager() { ++ final Path path = Paths.get("version_history.json"); ++ ++ if (Files.exists(path)) { ++ // Basic file santiy checks ++ if (!Files.isRegularFile(path)) { ++ if (Files.isDirectory(path)) { ++ logger.severe(path + " is a directory, cannot be used for version history"); ++ } else { ++ logger.severe(path + " is not a regular file, cannot be used for version history"); ++ } ++ // We can't continue ++ return; ++ } ++ ++ try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { ++ currentData = gson.fromJson(reader, VersionData.class); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); ++ return; ++ } catch (final JsonSyntaxException e) { ++ logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); ++ return; ++ } ++ ++ final String version = Bukkit.getVersion(); ++ if (version == null) { ++ logger.severe("Failed to retrieve current version"); ++ return; ++ } ++ ++ if (!version.equals(currentData.getCurrentVersion())) { ++ // The version appears to have changed ++ currentData.setOldVersion(currentData.getCurrentVersion()); ++ currentData.setCurrentVersion(version); ++ writeFile(path); ++ } ++ } else { ++ // File doesn't exist, start fresh ++ currentData = new VersionData(); ++ // oldVersion is null ++ currentData.setCurrentVersion(Bukkit.getVersion()); ++ writeFile(path); ++ } ++ } ++ ++ private void writeFile(@Nonnull final Path path) { ++ try (final BufferedWriter writer = Files.newBufferedWriter( ++ path, ++ StandardCharsets.UTF_8, ++ StandardOpenOption.WRITE, ++ StandardOpenOption.CREATE, ++ StandardOpenOption.TRUNCATE_EXISTING ++ )) { ++ gson.toJson(currentData, writer); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to write to version history file", e); ++ } ++ } ++ ++ @Nullable ++ public VersionData getVersionData() { ++ return currentData; ++ } ++ ++ public static class VersionData { ++ private String oldVersion; ++ ++ private String currentVersion; ++ ++ @Nullable ++ public String getOldVersion() { ++ return oldVersion; ++ } ++ ++ public void setOldVersion(@Nullable String oldVersion) { ++ this.oldVersion = oldVersion; ++ } ++ ++ @Nullable ++ public String getCurrentVersion() { ++ return currentVersion; ++ } ++ ++ public void setCurrentVersion(@Nullable String currentVersion) { ++ this.currentVersion = currentVersion; ++ } ++ ++ @Override ++ public String toString() { ++ return MoreObjects.toStringHelper(this) ++ .add("oldVersion", oldVersion) ++ .add("currentVersion", currentVersion) ++ .toString(); ++ } ++ ++ @Override ++ public boolean equals(@Nullable Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final VersionData versionData = (VersionData) o; ++ return Objects.equals(oldVersion, versionData.oldVersion) && ++ Objects.equals(currentVersion, versionData.currentVersion); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(oldVersion, currentVersion); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 40fe03c844c8bf6a9c4c5ae028b259f01a81eead..c7655883262f122b373ac30a33ddb4c06cd9aebe 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -193,6 +193,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + return false; + } + com.destroystokyo.paper.PaperConfig.registerCommands(); ++ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now + // Paper end + + this.setPvpAllowed(dedicatedserverproperties.pvp); diff --git a/Remapped-Spigot-Server-Patches/0021-Player-affects-spawning-API.patch b/Remapped-Spigot-Server-Patches/0021-Player-affects-spawning-API.patch new file mode 100644 index 000000000..08d7ac1c4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0021-Player-affects-spawning-API.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Tue, 1 Mar 2016 14:47:52 -0600 +Subject: [PATCH] Player affects spawning API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 706417f44c1eebc7cc5e8e7053fa0ab21f4caeba..392f2f2d67b688d5b37f77c8e4b3036348472d77 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1353,6 +1353,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return Mth.sqrt(f * f + f1 * f1 + f2 * f2); + } + ++ public double getDistanceSquared(double x, double y, double z) { return distanceToSqr(x, y, z); } // Paper - OBFHELPER + public double distanceToSqr(double x, double y, double z) { + double d3 = this.getX() - x; + double d4 = this.getY() - y; +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index f8c13881f59ccaccf8d8e5496d2f8f49ba7d7343..a3bad391a719363077740aa810c9412df34b4ae5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -29,6 +29,12 @@ public final class EntitySelector { + return !entity.isSpectator(); + }; + ++ // Paper start ++ public static final Predicate affectsSpawning = (entity) -> { ++ return !entity.isSpectator() && entity.isAlive() && (entity instanceof EntityPlayer) && ((EntityPlayer) entity).affectsSpawning; ++ }; ++ // Paper end ++ + public static Predicate withinDistance(double x, double y, double z, double d3) { + double d4 = d3 * d3; + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 4d3000067ae3d46b7ed4dda6146a21993199c6d9..09d39b73e8a3987e58a502bd914a6451b807421b 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -755,7 +755,7 @@ public abstract class Mob extends LivingEntity { + if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.remove(); + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Player entityhuman = this.level.getNearestPlayer(this, -1.0D); ++ Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper + + if (entityhuman != null) { + double d0 = entityhuman.distanceToSqr((Entity) this); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +index 4ce9e37d7334ba0557c397c0ebd2cb7928c7c564..cfdbaec1de6add7a189c26eb66701dfa5f40fe4f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +@@ -122,7 +122,7 @@ public class Silverfish extends Monster { + if (checkAnyLightMonsterSpawnRules(type, world, spawnReason, pos, random)) { + Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true); + +- return entityhuman == null; ++ return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API + } else { + return false; + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 91f605c803c021c8743de87b67dcb0fb9fc807e9..3b451e75a7f49ea6b543aee9f0a51c0be3c4dfba 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -161,6 +161,9 @@ public abstract class Player extends LivingEntity { + private final ItemCooldowns cooldowns; + @Nullable + public FishingHook fishing; ++ // Paper start ++ public boolean affectsSpawning = true; ++ // Paper end + + // CraftBukkit start + public boolean fauxSleeping; +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 98f85d59bc48451ef6381a47fe341f77b9920981..10058d3c3565382faa893b79119c5caf845bf29a 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -66,7 +66,7 @@ public abstract class BaseSpawner { + private boolean isNearPlayer() { + BlockPos blockposition = this.getPos(); + +- return this.getLevel().hasNearbyAlivePlayer((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (double) this.requiredPlayerRange); ++ return this.getLevel().isAffectsSpawningPlayerNearby((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper + } + + public void tick() { +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 7e7a58b9a9ececdcc37fc33b33703428eb1d5faf..66681b9f0e2531d3da25629e44180417b32b4d66 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -92,8 +92,9 @@ public interface EntityGetter { + } + } + +- @Nullable +- default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { ++ default Player findNearbyPlayer(Entity entity, double d0, @Nullable Predicate predicate) { return this.findNearbyPlayer(entity.getX(), entity.getY(), entity.getZ(), d0, predicate); } // Paper ++ @Nullable default Player findNearbyPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { return getNearestPlayer(d0, d1, d2, d3, predicate); } // Paper - OBFHELPER ++ @Nullable default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { // Paper + double d4 = -1.0D; + Player entityhuman = null; + Iterator iterator = this.players().iterator(); +@@ -126,6 +127,27 @@ public interface EntityGetter { + return this.getNearestPlayer(x, y, z, maxDistance, predicate); + } + ++ // Paper end ++ default boolean isAffectsSpawningPlayerNearby(double d0, double d1, double d2, double d3) { ++ Iterator iterator = this.players().iterator(); ++ double d4; ++ do { ++ Player entityhuman; ++ do { ++ if (!iterator.hasNext()) { ++ return false; ++ } ++ ++ entityhuman = (Player) iterator.next(); ++ } while (!EntitySelector.affectsSpawning.test(entityhuman)); ++ ++ d4 = entityhuman.getDistanceSquared(d0, d1, d2); ++ } while (d3 >= 0.0D && d4 >= d3 * d3); ++ ++ return true; ++ } ++ // Paper end ++ + default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { + Iterator iterator = this.players().iterator(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 50d11611702e3d1f0e980fb8f2280b05b891167b..e6c39c822c6a910f63e9b4899d53b7d75e1b77cf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1768,8 +1768,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public String getLocale() { + return getHandle().locale; ++ ++ } ++ ++ // Paper start ++ public void setAffectsSpawning(boolean affects) { ++ this.getHandle().affectsSpawning = affects; + } + ++ @Override ++ public boolean getAffectsSpawning() { ++ return this.getHandle().affectsSpawning; ++ } ++ // Paper end ++ + @Override + public void updateCommands() { + if (getHandle().connection == null) return; diff --git a/Remapped-Spigot-Server-Patches/0022-Remove-invalid-mob-spawner-tile-entities.patch b/Remapped-Spigot-Server-Patches/0022-Remove-invalid-mob-spawner-tile-entities.patch new file mode 100644 index 000000000..23323c980 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0022-Remove-invalid-mob-spawner-tile-entities.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 15:08:03 -0600 +Subject: [PATCH] Remove invalid mob spawner tile entities + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index f30793b81dfd9018b4879d655c7c18a9f9c25267..300749822d52f9f973e71c6ec9c8bf29d6a6938e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -41,7 +41,9 @@ import net.minecraft.world.level.TickList; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.EntityBlock; ++import net.minecraft.world.level.block.SpawnerBlock; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.levelgen.DebugLevelSource; + import net.minecraft.world.level.levelgen.Heightmap; +@@ -647,6 +649,10 @@ public class LevelChunk implements ChunkAccess { + } + + // CraftBukkit start ++ // Paper start - Remove invalid mob spawner tile entities ++ } else if (blockEntity instanceof SpawnerBlockEntity && !(getBlockData(pos.getX(), pos.getY(), pos.getZ()).getBlock() instanceof SpawnerBlock)) { ++ this.blockEntities.remove(pos); ++ // Paper end + } else { + System.out.println("Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + "," + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ() + + " (" + getBlockState(pos) + ") where there was no entity tile!"); diff --git a/Remapped-Spigot-Server-Patches/0023-Optimize-TileEntity-Ticking.patch b/Remapped-Spigot-Server-Patches/0023-Optimize-TileEntity-Ticking.patch new file mode 100644 index 000000000..db2d2e1d0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0023-Optimize-TileEntity-Ticking.patch @@ -0,0 +1,279 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 8 Mar 2015 22:55:25 -0600 +Subject: [PATCH] Optimize TileEntity Ticking + + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 94adf0275a2e7093c152cc3b8b0a5747b3a13a86..5bcf9cefc29eb20e2cfbfb49e2b2662ec394a87e 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -112,7 +112,7 @@ public class TimingsExport extends Thread { + pair("end", System.currentTimeMillis() / 1000), + pair("online-mode", Bukkit.getServer().getOnlineMode()), + pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), +- pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedIds(), pack -> { ++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedPacks(), pack -> { + // Don't feel like obf helper'ing these, non fatal if its temp missed. + return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true))); + })) +@@ -151,8 +151,8 @@ public class TimingsExport extends Thread { + ); + + parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getAllLevels(), world -> { +- if (world.getWorldData().getName().equals("worldeditregentempworld")) return null; +- return pair(world.getWorldData().getName(), createObject( ++ if (world.getWorld().getName().equals("worldeditregentempworld")) return null; ++ return pair(world.getWorld().getName(), createObject( + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +index 56656bf34db07bc717ace8ae9c1b60f9bfd7ff05..1bda9a158eb4372b9ab7cf3097732e64810aefc6 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +@@ -54,8 +54,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; + public class ChestBlock extends AbstractChestBlock implements SimpleWaterloggedBlock { + + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; +- public static final EnumProperty TYPE = BlockStateProperties.CHEST_TYPE; +- public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; ++ public static final EnumProperty TYPE = BlockStateProperties.CHEST_TYPE; public static final EnumProperty CHEST_TYPE_PROPERTY = TYPE; // Paper - OBFHELPER ++ public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final BooleanProperty waterlogged() { return WATERLOGGED; } // Paper OBFHELPER + protected static final VoxelShape NORTH_AABB = Block.box(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D); + protected static final VoxelShape SOUTH_AABB = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D); + protected static final VoxelShape WEST_AABB = Block.box(0.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +index 7b08ee35d2d8dc3fe783d773bf6686a5197006b8..17289d28b6d0023279a573715ee3d182988dd651 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -8,6 +8,7 @@ import net.minecraft.core.NonNullList; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -32,7 +33,7 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.entity.HumanEntity; + // CraftBukkit end + +-public class ChestBlockEntity extends RandomizableContainerBlockEntity implements TickableBlockEntity { ++public class ChestBlockEntity extends RandomizableContainerBlockEntity { // Paper - Remove ITickable + + private NonNullList items; + protected float openness; +@@ -110,14 +111,20 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + return tag; + } + +- @Override + public void tick() { + int i = this.worldPosition.getX(); + int j = this.worldPosition.getY(); + int k = this.worldPosition.getZ(); + + ++this.tickInterval; +- this.openCount = getOpenCount(this.level, this, this.tickInterval, i, j, k, this.openCount); ++ } ++ ++ public void doOpenLogic() { ++ int i = this.worldPosition.getX(); ++ int j = this.worldPosition.getY(); ++ int k = this.worldPosition.getZ(); ++ ++ //this.viewingCount = a(this.world, this, this.j, i, j, k, this.viewingCount); // Paper - check is faulty given our logic is called before active container set + this.oOpenness = this.openness; + float f = 0.1F; + +@@ -131,25 +138,31 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + if (this.openCount > 0 && this.openness == 0.0F) { + this.playSound(SoundEvents.CHEST_OPEN); + } ++ } + +- if (this.openCount == 0 && this.openness > 0.0F || this.openCount > 0 && this.openness < 1.0F) { +- float f1 = this.openness; ++ public void doCloseLogic() { ++ if (this.openCount == 0 /* && this.a > 0.0F || this.viewingCount > 0 && this.a < 1.0F */) { // Paper - disable all but player count check ++ /* // Paper - disable animation stuff ++ float f1 = this.a; + +- if (this.openCount > 0) { +- this.openness += 0.1F; ++ if (this.viewingCount > 0) { ++ this.a += 0.1F; + } else { +- this.openness -= 0.1F; ++ this.a -= 0.1F; + } + +- if (this.openness > 1.0F) { +- this.openness = 1.0F; ++ if (this.a > 1.0F) { ++ this.a = 1.0F; + } + + float f2 = 0.5F; + +- if (this.openness < 0.5F && f1 >= 0.5F) { ++ if (this.a < 0.5F && f1 >= 0.5F) { ++ */ ++ MCUtil.scheduleTask(10, () -> { + this.playSound(SoundEvents.CHEST_CLOSE); +- } ++ }, "Chest Sounds"); ++ //} // Paper end + + if (this.openness < 0.0F) { + this.openness = 0.0F; +@@ -188,6 +201,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + } + + public void playSound(SoundEvent soundeffect) { ++ if (!this.getBlockState().contains(ChestBlock.CHEST_TYPE_PROPERTY)) { return; } // Paper - this can be delayed, double check exists - Fixes GH-2074 + ChestType blockpropertychesttype = (ChestType) this.getBlockState().getValue(ChestBlock.TYPE); + + if (blockpropertychesttype != ChestType.LEFT) { +@@ -226,6 +240,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + + ++this.openCount; + if (this.level == null) return; // CraftBukkit ++ doOpenLogic(); // Paper + + // CraftBukkit start - Call redstone event + if (this.getBlockState().getBlock() == Blocks.TRAPPED_CHEST) { +@@ -248,6 +263,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + --this.openCount; + + // CraftBukkit start - Call redstone event ++ doCloseLogic(); // Paper + if (this.getBlockState().getBlock() == Blocks.TRAPPED_CHEST) { + int newPower = Math.max(0, Math.min(15, this.openCount)); + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +index b26337770e13c20f57a4e74282710ce697ac0d41..8f0477d9620ef71e10855bbca07f9b6984d5d794 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +@@ -1,11 +1,12 @@ + package net.minecraft.world.level.block.entity; + ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.block.Blocks; + +-public class EnderChestBlockEntity extends BlockEntity implements TickableBlockEntity { ++public class EnderChestBlockEntity extends BlockEntity { // Paper - Remove ITickable + + public float openness; + public float oOpenness; +@@ -16,18 +17,28 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE + super(BlockEntityType.ENDER_CHEST); + } + +- @Override + public void tick() { + if (++this.tickInterval % 20 * 4 == 0) { + this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount); + } + + this.oOpenness = this.openness; ++ /* // Paper ++ int i = this.position.getX(); ++ int j = this.position.getY(); ++ int k = this.position.getZ(); ++ float f = 0.1F; ++ double d0; ++ // Paper start ++ */ ++ } ++ ++ private void doOpenLogic() { + int i = this.worldPosition.getX(); + int j = this.worldPosition.getY(); + int k = this.worldPosition.getZ(); +- float f = 0.1F; + double d0; ++ // Paper end + + if (this.openCount > 0 && this.openness == 0.0F) { + double d1 = (double) i + 0.5D; +@@ -35,28 +46,40 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE + d0 = (double) k + 0.5D; + this.level.playSound((Player) null, d1, (double) j + 0.5D, d0, SoundEvents.ENDER_CHEST_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); + } ++ // Paper start ++ } + +- if (this.openCount == 0 && this.openness > 0.0F || this.openCount > 0 && this.openness < 1.0F) { +- float f1 = this.openness; ++ private void doCloseLogic() { ++ int i = this.worldPosition.getX(); ++ int j = this.worldPosition.getY(); ++ int k = this.worldPosition.getZ(); ++ double d0; ++ ++ if (this.openCount == 0) { /* && this.a > 0.0F || this.c > 0 && this.a < 1.0F) { ++ // Paper end ++ float f1 = this.a; + +- if (this.openCount > 0) { +- this.openness += 0.1F; ++ if (this.c > 0) { ++ this.a += 0.1F; + } else { +- this.openness -= 0.1F; ++ this.a -= 0.1F; + } + +- if (this.openness > 1.0F) { +- this.openness = 1.0F; ++ if (this.a > 1.0F) { ++ this.a = 1.0F; + } + + float f2 = 0.5F; + +- if (this.openness < 0.5F && f1 >= 0.5F) { ++ if (this.a < 0.5F && f1 >= 0.5F) { ++ // Paper start ++ */ + d0 = (double) i + 0.5D; + double d2 = (double) k + 0.5D; + ++ MCUtil.scheduleTask(10, () -> { + this.level.playSound((Player) null, d0, (double) j + 0.5D, d2, SoundEvents.ENDER_CHEST_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); +- } ++ }, "Chest Sounds"); + + if (this.openness < 0.0F) { + this.openness = 0.0F; +@@ -84,11 +107,13 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE + public void startOpen() { + ++this.openCount; + this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount); ++ doOpenLogic(); // Paper + } + + public void stopOpen() { + --this.openCount; + this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount); ++ doCloseLogic(); // Paper + } + + public boolean stillValid(Player entityhuman) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +index 60ce75c7f94c995d3753c40bc8d1ec09b4d37b1a..ac10fb9cd4701f0f6477a86bec73cb5ac6496725 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +@@ -84,6 +84,7 @@ public abstract class StateHolder { + return Collections.unmodifiableCollection(this.values.keySet()); + } + ++ public > boolean contains(Property iblockstate) { return this.hasProperty(iblockstate); } // Paper - OBFHELPER + public > boolean hasProperty(Property property) { + return this.values.containsKey(property); + } diff --git a/Remapped-Spigot-Server-Patches/0024-Further-improve-server-tick-loop.patch b/Remapped-Spigot-Server-Patches/0024-Further-improve-server-tick-loop.patch new file mode 100644 index 000000000..ee0ab66ab --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0024-Further-improve-server-tick-loop.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 Mar 2016 23:09:29 -0600 +Subject: [PATCH] Further improve server tick loop + +Improves how the catchup buffer is handled, allowing it to roll both ways +increasing the effeciency of the thread sleep so it only will sleep once. + +Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions + +Previous implementation did not calculate TPS correctly. +Switch to a realistic rolling average and factor in std deviation as an extra reporting variable + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fa29790600021809f31092a90e1a3a9b84d5e0c4..526d6c0fa45bfba92a3f964f72e4965fd5c841c1 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -251,7 +251,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public Commands vanillaCommandDispatcher; +@@ -260,7 +260,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 5000L && this.nextTickTime - this.lastOverloadWarning >= 30000L) { // CraftBukkit + long j = i / 50L; + + if (server.getWarnOnOverload()) // CraftBukkit +- MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j); ++ MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j); + this.nextTickTime += j * 50L; + this.lastOverloadWarning = this.nextTickTime; + } + +- if ( tickCount++ % SAMPLE_INTERVAL == 0 ) ++ if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 ) + { +- double currentTps = 1E3 / ( curTime - tickSection ) * SAMPLE_INTERVAL; +- recentTps[0] = calcTps( recentTps[0], 0.92, currentTps ); // 1/exp(5sec/1min) +- recentTps[1] = calcTps( recentTps[1], 0.9835, currentTps ); // 1/exp(5sec/5min) +- recentTps[2] = calcTps( recentTps[2], 0.9945, currentTps ); // 1/exp(5sec/15min) ++ final long diff = curTime - tickSection; ++ java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP); ++ tps1.add(currentTps, diff); ++ tps5.add(currentTps, diff); ++ tps15.add(currentTps, diff); ++ // Backwards compat with bad plugins ++ recentTps[0] = tps1.getAverage(); ++ recentTps[1] = tps5.getAverage(); ++ recentTps[2] = tps15.getAverage(); ++ // Paper end + tickSection = curTime; + } + // Spigot end + +- MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit ++ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time ++ lastTick = curTime; + this.nextTickTime += 50L; + SingleTickProfiler gameprofilertick = SingleTickProfiler.createTickProfiler("Server"); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f81def94a1a7ab3a24b74a8bbd5f3e8ebae2c0d5..6fa31ca31128b1094eebd5f848c5b506dfeedeeb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2121,6 +2121,17 @@ public final class CraftServer implements Server { + return CraftMagicNumbers.INSTANCE; + } + ++ // Paper - Add getTPS API - Further improve tick loop ++ @Override ++ public double[] getTPS() { ++ return new double[] { ++ net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), ++ net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), ++ net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() ++ }; ++ } ++ // Paper end ++ + // Spigot start + private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() + { +diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +index f5b6dec1cbe7501ce2ee9125920e810bc94670cc..e62890433ffbe0b4e48942fe6c38b599a19e58fd 100644 +--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java ++++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +@@ -24,22 +24,30 @@ public class TicksPerSecondCommand extends Command + return true; + } + +- StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " ); +- for ( double tps : MinecraftServer.getServer().recentTps ) +- { +- sb.append( format( tps ) ); +- sb.append( ", " ); ++ // Paper start - Further improve tick handling ++ double[] tps = org.bukkit.Bukkit.getTPS(); ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int i = 0; i < tps.length; i++) { ++ tpsAvg[i] = format( tps[i] ); ++ } ++ sender.sendMessage(ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " + org.apache.commons.lang.StringUtils.join(tpsAvg, ", ")); ++ if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { ++ sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); ++ if (!hasShownMemoryWarning) { ++ sender.sendMessage(ChatColor.RED + "Warning: " + ChatColor.GOLD + " Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention."); ++ hasShownMemoryWarning = true; ++ } + } +- sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); +- sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " +- + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); ++ // Paper end + + return true; + } + +- private String format(double tps) ++ private boolean hasShownMemoryWarning; // Paper ++ private static String format(double tps) // Paper - Made static + { + return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() +- + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ + ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + } + } diff --git a/Remapped-Spigot-Server-Patches/0025-Only-refresh-abilities-if-needed.patch b/Remapped-Spigot-Server-Patches/0025-Only-refresh-abilities-if-needed.patch new file mode 100644 index 000000000..b310cdcaa --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0025-Only-refresh-abilities-if-needed.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 23:12:03 -0600 +Subject: [PATCH] Only refresh abilities if needed + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e6c39c822c6a910f63e9b4899d53b7d75e1b77cf..2920ba3d8eeb62670897ea19b50aaf395ab84c5a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1437,12 +1437,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setFlying(boolean value) { ++ boolean needsUpdate = getHandle().abilities.flying != value; // Paper - Only refresh abilities if needed + if (!getAllowFlight() && value) { + throw new IllegalArgumentException("Cannot make player fly if getAllowFlight() is false"); + } + + getHandle().abilities.flying = value; +- getHandle().onUpdateAbilities(); ++ if (needsUpdate) getHandle().onUpdateAbilities(); // Paper - Only refresh abilities if needed + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0026-Entity-Origin-API.patch b/Remapped-Spigot-Server-Patches/0026-Entity-Origin-API.patch new file mode 100644 index 000000000..cc2e7adc4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0026-Entity-Origin-API.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 23:45:08 -0600 +Subject: [PATCH] Entity Origin API + + +diff --git a/src/main/java/net/minecraft/nbt/ListTag.java b/src/main/java/net/minecraft/nbt/ListTag.java +index 084340dc73acb3d972e0717b48da820c027a5137..7927ebac41eb1f257738238500cfe0c06031fcaf 100644 +--- a/src/main/java/net/minecraft/nbt/ListTag.java ++++ b/src/main/java/net/minecraft/nbt/ListTag.java +@@ -190,6 +190,7 @@ public class ListTag extends CollectionTag { + return new int[0]; + } + ++ public final double getDoubleAt(int i) { return this.getDouble(i); } // Paper - OBFHELPER + public double getDouble(int index) { + if (index >= 0 && index < this.list.size()) { + Tag nbtbase = (Tag) this.list.get(index); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0cc86ca4ea4a2e1b5acc3c0507397eef85dec0c1..d2bb9385fbc21cdef6cef06680fac685d3da3570 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1240,6 +1240,11 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.navigations.add(((Mob) entity).getNavigation()); + } + entity.valid = true; // CraftBukkit ++ // Paper start - Set origin location when the entity is being added to the world ++ if (entity.origin == null) { ++ entity.origin = entity.getBukkitEntity().getLocation(); ++ } ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 392f2f2d67b688d5b37f77c8e4b3036348472d77..fd5b41ceb97dc8aa975f1c0ae05b58d0b09f2cd6 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -246,6 +246,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean forceExplosionKnockback; // SPIGOT-949 + public boolean persistentInvisibility = false; ++ public org.bukkit.Location origin; // Paper + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; +@@ -1624,6 +1625,12 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.bukkitEntity.storeBukkitValues(tag); + } + // CraftBukkit end ++ // Paper start - Save the entity's origin location ++ if (this.origin != null) { ++ tag.setUUID("Paper.OriginWorld", origin.getWorld().getUID()); ++ tag.put("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); ++ } ++ // Paper end + return tag; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); +@@ -1746,6 +1753,17 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + // CraftBukkit end + ++ // Paper start - Restore the entity's origin location ++ ListTag originTag = tag.getList("Paper.Origin", 6); ++ if (!originTag.isEmpty()) { ++ org.bukkit.World originWorld = level.getWorld(); ++ if (tag.contains("Paper.OriginWorld")) { ++ originWorld = Bukkit.getWorld(tag.getUUID("Paper.OriginWorld")); ++ } ++ origin = new org.bukkit.Location(originWorld, originTag.getDoubleAt(0), originTag.getDoubleAt(1), originTag.getDoubleAt(2)); ++ } ++ // Paper end ++ + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); +@@ -1807,6 +1825,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + protected abstract void addAdditionalSaveData(CompoundTag tag); + ++ protected final ListTag createList(double... adouble) { return newDoubleList(adouble); } // Paper - OBFHELPER + protected ListTag newDoubleList(double... values) { + ListTag nbttaglist = new ListTag(); + double[] adouble1 = values; +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 5394bc6336cb84025c1c748fb5b3d38e0648a590..1d87717cc9002ea202ee2ca614aaa8a4c7ea3cb2 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -291,6 +291,14 @@ public class FallingBlockEntity extends Entity { + this.blockState = Blocks.SAND.defaultBlockState(); + } + ++ // Paper start - Try and load origin location from the old NBT tags for backwards compatibility ++ if (tag.contains("SourceLoc_x")) { ++ int srcX = tag.getInt("SourceLoc_x"); ++ int srcY = tag.getInt("SourceLoc_y"); ++ int srcZ = tag.getInt("SourceLoc_z"); ++ origin = new org.bukkit.Location(level.getWorld(), srcX, srcY, srcZ); ++ } ++ // Paper end + } + + public void setHurtsEntities(boolean hurtEntities) { +diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +index 4c4262b8f0cb44b8cea8cb46194a6e70d4ce56f4..661848084fd986321ef782317934dac19ed4dce3 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -119,6 +119,14 @@ public class PrimedTnt extends Entity { + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + this.setFuse(tag.getShort("Fuse")); ++ // Paper start - Try and load origin location from the old NBT tags for backwards compatibility ++ if (tag.contains("SourceLoc_x")) { ++ int srcX = tag.getInt("SourceLoc_x"); ++ int srcY = tag.getInt("SourceLoc_y"); ++ int srcZ = tag.getInt("SourceLoc_z"); ++ origin = new org.bukkit.Location(level.getWorld(), srcX, srcY, srcZ); ++ } ++ // Paper end + } + + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 53c231925ef1b17e48c5863570e3c54124874621..e7a59a8e0424a0839dfa73fc65f44c5b04bd3dec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1062,4 +1062,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @Override ++ public Location getOrigin() { ++ Location origin = getHandle().origin; ++ return origin == null ? null : origin.clone(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0027-Prevent-tile-entity-and-entity-crashes.patch b/Remapped-Spigot-Server-Patches/0027-Prevent-tile-entity-and-entity-crashes.patch new file mode 100644 index 000000000..cf3cf1bea --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0027-Prevent-tile-entity-and-entity-crashes.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 Mar 2016 23:52:34 -0600 +Subject: [PATCH] Prevent tile entity and entity crashes + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 832abf73bdab2488c5814ea6e57888aac1b26154..870843254d1c1fc49bc101a49cdf9d300ae3ca1b 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -737,11 +737,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + gameprofilerfiller.pop(); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking block entity"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block entity being ticked"); +- +- tileentity.fillCrashReportCategory(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent tile entity and entity crashes ++ System.err.println("TileEntity threw exception at " + tileentity.level.getWorld().getName() + ":" + tileentity.worldPosition.getX() + "," + tileentity.worldPosition.getY() + "," + tileentity.worldPosition.getZ()); ++ throwable.printStackTrace(); ++ tilesThisCycle--; ++ this.tickableBlockEntities.remove(tileTickPosition--); ++ continue; ++ // Paper end + // Spigot start + } finally { + tileentity.tickTimer.stopTiming(); +@@ -806,11 +808,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + try { + tickConsumer.accept(entity); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked"); +- +- entity.fillCrashReportCategory(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent tile entity and entity crashes ++ System.err.println("Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ()); ++ throwable.printStackTrace(); ++ entity.removed = true; ++ return; ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index d445a1b7b7605eed66923789c5d8e2199c31c5ac..13115d1b28dfa2d87b45a50bd0feaa7f57769122 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -208,7 +208,12 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + return Registry.BLOCK_ENTITY_TYPE.getKey(this.getType()) + " // " + this.getClass().getCanonicalName(); + }); + if (this.level != null) { +- CrashReportCategory.populateBlockDetails(crashreportsystemdetails, this.worldPosition, this.getBlockState()); ++ // Paper start - Prevent TileEntity and Entity crashes ++ BlockState block = this.getBlockState(); ++ if (block != null) { ++ CrashReportCategory.populateBlockDetails(crashreportsystemdetails, this.worldPosition, block); ++ } ++ // Paper end + CrashReportCategory.populateBlockDetails(crashreportsystemdetails, this.worldPosition, this.level.getBlockState(this.worldPosition)); + } + } diff --git a/Remapped-Spigot-Server-Patches/0028-Configurable-top-of-nether-void-damage.patch b/Remapped-Spigot-Server-Patches/0028-Configurable-top-of-nether-void-damage.patch new file mode 100644 index 000000000..805929cc3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0028-Configurable-top-of-nether-void-damage.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 23:58:50 -0600 +Subject: [PATCH] Configurable top of nether void damage + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index d16ae924bcbe31c964f7fb448757c748e5c4418c..4bba6977a0287837b8927718c040ac61463f0469 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -134,4 +134,19 @@ public class PaperWorldConfig { + if (fallingBlockHeightNerf != 0) log("Falling Block Height Limit set to Y: " + fallingBlockHeightNerf); + if (entityTNTHeightNerf != 0) log("TNT Entity Height Limit set to Y: " + entityTNTHeightNerf); + } ++ ++ public int netherVoidTopDamageHeight; ++ public boolean doNetherTopVoidDamage() { return netherVoidTopDamageHeight > 0; } ++ private void netherVoidTopDamageHeight() { ++ netherVoidTopDamageHeight = getInt("nether-ceiling-void-damage-height", 0); ++ log("Top of the nether void damage height: " + netherVoidTopDamageHeight); ++ ++ if (PaperConfig.version < 18) { ++ boolean legacy = getBoolean("nether-ceiling-void-damage", false); ++ if (legacy) { ++ netherVoidTopDamageHeight = 128; ++ set("nether-ceiling-void-damage-height", netherVoidTopDamageHeight); ++ } ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fd5b41ceb97dc8aa975f1c0ae05b58d0b09f2cd6..f3f48c268639937874dd39eea9bd8e119eebdce7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -499,9 +499,16 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.fallDistance *= 0.5F; + } + +- if (this.getY() < -64.0D) { +- this.outOfWorld(); ++ // Paper start - Configurable nether ceiling damage ++ ++ // Extracted to own function ++ /* ++ if (this.locY() < -64.0D) { ++ this.an(); + } ++ */ ++ this.performVoidDamage(); ++ // Paper end + + if (!this.level.isClientSide) { + this.setSharedFlag(0, this.remainingFireTicks > 0); +@@ -594,6 +601,17 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.setRemainingFireTicks(0); + } + ++ // Paper start ++ protected void performVoidDamage() { ++ if (this.getY() < -64.0D || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER ++ && level.paperConfig.doNetherTopVoidDamage() ++ && this.getY() >= level.paperConfig.netherVoidTopDamageHeight)) { ++ this.doVoidDamage(); ++ } ++ } ++ // Paper end ++ ++ protected final void doVoidDamage() { this.outOfWorld(); } // Paper - OBFHELPER + protected void outOfWorld() { + this.remove(); + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 9503376895d90e8db0d4f7b164e2d813dd1a4a3a..7ba74b0a9319e29077b5afe3019a463ed3004813 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -329,9 +329,15 @@ public abstract class AbstractMinecart extends Entity { + this.setDamage(this.getDamage() - 1.0F); + } + +- if (this.getY() < -64.0D) { +- this.outOfWorld(); ++ // Paper start - Configurable nether ceiling damage ++ // Extracted to own function ++ /* ++ if (this.locY() < -64.0D) { ++ this.an(); + } ++ */ ++ this.performVoidDamage(); ++ // Paper end + + // this.doPortalTick(); // CraftBukkit - handled in postTick + if (this.level.isClientSide) { diff --git a/Remapped-Spigot-Server-Patches/0029-Check-online-mode-before-converting-and-renaming-pla.patch b/Remapped-Spigot-Server-Patches/0029-Check-online-mode-before-converting-and-renaming-pla.patch new file mode 100644 index 000000000..56a96d8cf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0029-Check-online-mode-before-converting-and-renaming-pla.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 00:03:55 -0600 +Subject: [PATCH] Check online mode before converting and renaming player data + + +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index 067c5acd4aad346ac9ccf6d1b5aa6691b0ccd348..60fe01e824e4657d2601797d7858d5de339ab255 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -56,7 +56,7 @@ public class PlayerDataStorage { + File file = new File(this.playerDir, entityhuman.getStringUUID() + ".dat"); + // Spigot Start + boolean usingWrongFile = false; +- if ( !file.exists() ) ++ if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first + { + file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getScoreboardName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); + if ( file.exists() ) diff --git a/Remapped-Spigot-Server-Patches/0030-Always-tick-falling-blocks.patch b/Remapped-Spigot-Server-Patches/0030-Always-tick-falling-blocks.patch new file mode 100644 index 000000000..29eba4b28 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0030-Always-tick-falling-blocks.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 00:32:25 -0600 +Subject: [PATCH] Always tick falling blocks + + +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 9bb35ec64e1538aabec9ff7831706c4717239449..0a9bd85e0308e962df3b24a74bd5aac919744d6d 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -91,6 +91,7 @@ public class ActivationRange + || entity instanceof AbstractHurtingProjectile + || entity instanceof LightningBolt + || entity instanceof PrimedTnt ++ || entity instanceof EntityFallingBlock // Paper - Always tick falling blocks + || entity instanceof EndCrystal + || entity instanceof FireworkRocketEntity + || entity instanceof ThrownTrident ) diff --git a/Remapped-Spigot-Server-Patches/0031-Configurable-end-credits.patch b/Remapped-Spigot-Server-Patches/0031-Configurable-end-credits.patch new file mode 100644 index 000000000..201a2e728 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0031-Configurable-end-credits.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DoctorDark +Date: Wed, 16 Mar 2016 02:21:39 -0500 +Subject: [PATCH] Configurable end credits + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4bba6977a0287837b8927718c040ac61463f0469..e6e18f309dc09ea9416ea37dcc697ddc2b571a96 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -149,4 +149,10 @@ public class PaperWorldConfig { + } + } + } ++ ++ public boolean disableEndCredits; ++ private void disableEndCredits() { ++ disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); ++ log("End credits disabled: " + disableEndCredits); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ca647b3afbe8da5847dc8fa890ae9ca5c18e03d9..f3797bd761c2c6782cce3fca25bc9ef37e5c4978 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -188,7 +188,7 @@ public class ServerPlayer extends Player implements ContainerListener { + private long lastActionTime = Util.getMillis(); + private Entity camera; + public boolean isChangingDimension; +- private boolean seenCredits; ++ private boolean seenCredits; private void setHasSeenCredits(boolean has) { this.seenCredits = has; } // Paper - OBFHELPER + private final ServerRecipeBook recipeBook = new ServerRecipeBook(); + private Vec3 levitationStartPos; + private int levitationStartTime; +@@ -893,6 +893,7 @@ public class ServerPlayer extends Player implements ContainerListener { + this.unRide(); + this.getLevel().removePlayerImmediately(this); + if (!this.wonGame) { ++ if (level.paperConfig.disableEndCredits) this.setHasSeenCredits(true); // Paper - Toggle to always disable end credits + this.wonGame = true; + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, this.seenCredits ? 0.0F : 1.0F)); + this.seenCredits = true; diff --git a/Remapped-Spigot-Server-Patches/0032-Fix-lag-from-explosions-processing-dead-entities.patch b/Remapped-Spigot-Server-Patches/0032-Fix-lag-from-explosions-processing-dead-entities.patch new file mode 100644 index 000000000..a9fb2be3b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0032-Fix-lag-from-explosions-processing-dead-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Iceee +Date: Wed, 2 Mar 2016 01:39:52 -0600 +Subject: [PATCH] Fix lag from explosions processing dead entities + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 54c3bfead8497f64c183f5612676803d91fc557b..8d6cd2a5b16d99cb8e754ce04b2d12fee7ffb4d0 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -180,7 +180,7 @@ public class Explosion { + int i1 = Mth.floor(this.y + (double) f2 + 1.0D); + int j1 = Mth.floor(this.z - (double) f2 - 1.0D); + int k1 = Mth.floor(this.z + (double) f2 + 1.0D); +- List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1)); ++ List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities + Vec3 vec3d = new Vec3(this.x, this.y, this.z); + + for (int l1 = 0; l1 < list.size(); ++l1) { diff --git a/Remapped-Spigot-Server-Patches/0033-Optimize-explosions.patch b/Remapped-Spigot-Server-Patches/0033-Optimize-explosions.patch new file mode 100644 index 000000000..ec1e34fc3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0033-Optimize-explosions.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 11:59:48 -0600 +Subject: [PATCH] Optimize explosions + +The process of determining an entity's exposure from explosions can be +expensive when there are hundreds or more entities in range. + +This patch adds a per-tick cache that is used for storing and retrieving +an entity's exposure during an explosion. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e6e18f309dc09ea9416ea37dcc697ddc2b571a96..4881b03d470646843bad1bc343eb6a6ab9072d8e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -155,4 +155,10 @@ public class PaperWorldConfig { + disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); + log("End credits disabled: " + disableEndCredits); + } ++ ++ public boolean optimizeExplosions; ++ private void optimizeExplosions() { ++ optimizeExplosions = getBoolean("optimize-explosions", false); ++ log("Optimize explosions: " + optimizeExplosions); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 526d6c0fa45bfba92a3f964f72e4965fd5c841c1..901d5497667706c049718dc4fca37a1bc489c465 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1324,6 +1324,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>> 32)); ++ temp = Double.doubleToLongBits(posY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ return result; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 870843254d1c1fc49bc101a49cdf9d300ae3ca1b..f71b56fa079e2c7b2123061a8e1a7cb41935bab6 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -136,6 +136,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; + private int tileTickPosition; ++ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + + public CraftWorld getWorld() { + return this.world; diff --git a/Remapped-Spigot-Server-Patches/0034-Disable-explosion-knockback.patch b/Remapped-Spigot-Server-Patches/0034-Disable-explosion-knockback.patch new file mode 100644 index 000000000..d9f4ff967 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0034-Disable-explosion-knockback.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:48:03 -0600 +Subject: [PATCH] Disable explosion knockback + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4881b03d470646843bad1bc343eb6a6ab9072d8e..2222c1bb5f8625eee4d88946e4bfdfa2fe598977 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -161,4 +161,9 @@ public class PaperWorldConfig { + optimizeExplosions = getBoolean("optimize-explosions", false); + log("Optimize explosions: " + optimizeExplosions); + } ++ ++ public boolean disableExplosionKnockback; ++ private void disableExplosionKnockback(){ ++ disableExplosionKnockback = getBoolean("disable-explosion-knockback", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e5f8cee6726ea9a90c540bb10fd8594a35bb5e40..afd114e1ce00db72534d470fed12101bb237f266 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1280,6 +1280,7 @@ public abstract class LivingEntity extends Entity { + } + } + ++ boolean knockbackCancelled = level.paperConfig.disableExplosionKnockback && source.isExplosion() && this instanceof net.minecraft.world.entity.player.Player; // Paper - Disable explosion knockback + if (flag1) { + if (flag) { + this.level.broadcastEntityEvent(this, (byte) 29); +@@ -1298,6 +1299,7 @@ public abstract class LivingEntity extends Entity { + b0 = 2; + } + ++ if (!knockbackCancelled) // Paper - Disable explosion knockback + this.level.broadcastEntityEvent(this, b0); + } + +@@ -1321,6 +1323,7 @@ public abstract class LivingEntity extends Entity { + } + } + ++ if (knockbackCancelled) this.level.broadcastEntityEvent(this, (byte) 2); // Paper - Disable explosion knockback + if (this.isDeadOrDying()) { + if (!this.checkTotemDeathProtection(source)) { + SoundEvent soundeffect = this.getDeathSound(); +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index db46caaa5ad5f129d313c65c5006cb24853768be..45a75f7be308678336e192828becf6cf5c9047bc 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -215,14 +215,14 @@ public class Explosion { + double d14 = d13; + + if (entity instanceof LivingEntity) { +- d14 = ProtectionEnchantment.getExplosionKnockbackAfterDampener((LivingEntity) entity, d13); ++ d14 = entity instanceof Player && level.paperConfig.disableExplosionKnockback ? 0 : ProtectionEnchantment.getExplosionKnockbackAfterDampener((LivingEntity) entity, d13); // Paper - Disable explosion knockback + } + + entity.setDeltaMovement(entity.getDeltaMovement().add(d8 * d14, d9 * d14, d10 * d14)); + if (entity instanceof Player) { + Player entityhuman = (Player) entity; + +- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.abilities.flying)) { ++ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.abilities.flying) && !level.paperConfig.disableExplosionKnockback) { // Paper - Disable explosion knockback + this.hitPlayers.put(entityhuman, new Vec3(d8 * d13, d9 * d13, d10 * d13)); + } + } diff --git a/Remapped-Spigot-Server-Patches/0035-Disable-thunder.patch b/Remapped-Spigot-Server-Patches/0035-Disable-thunder.patch new file mode 100644 index 000000000..d5971380b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0035-Disable-thunder.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:52:43 -0600 +Subject: [PATCH] Disable thunder + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2222c1bb5f8625eee4d88946e4bfdfa2fe598977..083e421f8496b5336af473b108498ed28b984774 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -166,4 +166,9 @@ public class PaperWorldConfig { + private void disableExplosionKnockback(){ + disableExplosionKnockback = getBoolean("disable-explosion-knockback", false); + } ++ ++ public boolean disableThunder; ++ private void disableThunder() { ++ disableThunder = getBoolean("disable-thunder", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index d2bb9385fbc21cdef6cef06680fac685d3da3570..3fc8fb197400c63bc85f57ff484803659619f775 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -580,7 +580,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + gameprofilerfiller.push("thunder"); + BlockPos blockposition; + +- if (flag && this.isThundering() && this.random.nextInt(100000) == 0) { ++ if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder + blockposition = this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); diff --git a/Remapped-Spigot-Server-Patches/0036-Disable-ice-and-snow.patch b/Remapped-Spigot-Server-Patches/0036-Disable-ice-and-snow.patch new file mode 100644 index 000000000..9d2d87a6b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0036-Disable-ice-and-snow.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:57:24 -0600 +Subject: [PATCH] Disable ice and snow + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 083e421f8496b5336af473b108498ed28b984774..2f7a5a4a5a7b29750cfd777e0bc5d19a14e93fa2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -171,4 +171,9 @@ public class PaperWorldConfig { + private void disableThunder() { + disableThunder = getBoolean("disable-thunder", false); + } ++ ++ public boolean disableIceAndSnow; ++ private void disableIceAndSnow(){ ++ disableIceAndSnow = getBoolean("disable-ice-and-snow", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3fc8fb197400c63bc85f57ff484803659619f775..6c6098731752d61b5241710b075d4ffe3826daac 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -604,7 +604,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + gameprofilerfiller.popPush("iceandsnow"); +- if (this.random.nextInt(16) == 0) { ++ if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow + blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15)); + BlockPos blockposition1 = blockposition.below(); + Biome biomebase = this.getBiome(blockposition); diff --git a/Remapped-Spigot-Server-Patches/0037-Configurable-mob-spawner-tick-rate.patch b/Remapped-Spigot-Server-Patches/0037-Configurable-mob-spawner-tick-rate.patch new file mode 100644 index 000000000..454050d2c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0037-Configurable-mob-spawner-tick-rate.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 15:03:53 -0600 +Subject: [PATCH] Configurable mob spawner tick rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2f7a5a4a5a7b29750cfd777e0bc5d19a14e93fa2..4de86b09c6bc3c1974ce61b550ccb73d37f6f170 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -176,4 +176,9 @@ public class PaperWorldConfig { + private void disableIceAndSnow(){ + disableIceAndSnow = getBoolean("disable-ice-and-snow", false); + } ++ ++ public int mobSpawnerTickRate; ++ private void mobSpawnerTickRate() { ++ mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 1); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 10058d3c3565382faa893b79119c5caf845bf29a..ed631d5bfba5d2543e8eed017a7c484ad3ddb453 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -41,6 +41,7 @@ public abstract class BaseSpawner { + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; ++ private int tickDelay = 0; // Paper + + public BaseSpawner() {} + +@@ -70,6 +71,10 @@ public abstract class BaseSpawner { + } + + public void tick() { ++ // Paper start - Configurable mob spawner tick rate ++ if (spawnDelay > 0 && --tickDelay > 0) return; ++ tickDelay = this.getLevel().paperConfig.mobSpawnerTickRate; ++ // Paper end + if (!this.isNearPlayer()) { + this.oSpin = this.spin; + } else { +@@ -84,18 +89,18 @@ public abstract class BaseSpawner { + world.addParticle(ParticleTypes.SMOKE, d0, d1, d2, 0.0D, 0.0D, 0.0D); + world.addParticle(ParticleTypes.FLAME, d0, d1, d2, 0.0D, 0.0D, 0.0D); + if (this.spawnDelay > 0) { +- --this.spawnDelay; ++ this.spawnDelay -= tickDelay; // Paper + } + + this.oSpin = this.spin; + this.spin = (this.spin + (double) (1000.0F / ((float) this.spawnDelay + 200.0F))) % 360.0D; + } else { +- if (this.spawnDelay == -1) { ++ if (this.spawnDelay < -tickDelay) { // Paper + this.delay(); + } + + if (this.spawnDelay > 0) { +- --this.spawnDelay; ++ this.spawnDelay -= tickDelay; // Paper + return; + } + diff --git a/Remapped-Spigot-Server-Patches/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch b/Remapped-Spigot-Server-Patches/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch new file mode 100644 index 000000000..c77faa199 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Wed, 2 Mar 2016 23:13:07 -0600 +Subject: [PATCH] Send absolute position the first time an entity is seen + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 59a5f82c9f57d760ba4959a040ce8cbf0f49e4aa..d1bc927c8b429f43de2cdad98f8b329ff4c8b4db 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1301,10 +1301,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final Entity entity; + private final int range; + private SectionPos lastSectionPos; +- public final Set seenBy = Sets.newHashSet(); ++ // Paper start ++ // Replace trackedPlayers Set with a Map. The value is true until the player receives ++ // their first update (which is forced to have absolute coordinates), false afterward. ++ public java.util.Map trackedPlayerMap = new java.util.HashMap<>(); ++ public Set seenBy = trackedPlayerMap.keySet(); + + public TrackedEntity(Entity entity, int i, int j, boolean flag) { +- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, seenBy); // CraftBukkit ++ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, trackedPlayerMap); // CraftBukkit // Paper + this.entity = entity; + this.range = i; + this.lastSectionPos = SectionPos.of(entity); +@@ -1386,7 +1390,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + player.entitiesToRemove.remove(Integer.valueOf(this.entity.getId())); + // CraftBukkit end + +- if (flag1 && this.seenBy.add(player)) { ++ if (flag1 && this.trackedPlayerMap.putIfAbsent(player, true) == null) { // Paper + this.serverEntity.addPairing(player); + } + } else if (this.seenBy.remove(player)) { +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 3d386627b6d3d33da76372e4a14d0c5000eb8ffc..fa6893055fa5617742bfb4b7eff60c8139395cb6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -4,6 +4,7 @@ import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Pair; + import java.util.Collection; + import java.util.Collections; ++import java.util.HashSet; + import java.util.Iterator; + import java.util.List; + import java.util.Set; +@@ -51,7 +52,7 @@ public class ServerEntity { + private final Entity entity; + private final int updateInterval; + private final boolean trackDelta; +- private final Consumer> broadcast; ++ private final Consumer> broadcast; private Consumer> getPacketConsumer() { return broadcast; } // Paper - OBFHELPER + private long xp; + private long yp; + private long zp; +@@ -66,8 +67,23 @@ public class ServerEntity { + private boolean wasOnGround; + // CraftBukkit start + private final Set trackedPlayers; ++ // Paper start ++ private java.util.Map trackedPlayerMap = null; ++ ++ /** ++ * Requested in https://github.com/PaperMC/Paper/issues/1537 to allow intercepting packets ++ */ ++ public void sendPlayerPacket(ServerPlayer player, Packet packet) { ++ player.connection.send(packet); ++ } ++ ++ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, java.util.Map trackedPlayers) { ++ this(worldserver, entity, i, flag, consumer, trackedPlayers.keySet()); ++ trackedPlayerMap = trackedPlayers; ++ } + + public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { ++ // Paper end + this.trackedPlayers = trackedPlayers; + // CraftBukkit end + this.ap = Vec3.ZERO; +@@ -188,7 +204,25 @@ public class ServerEntity { + } + + if (packet1 != null) { +- this.broadcast.accept(packet1); ++ // paper start ++ if (trackedPlayerMap == null || packet1 instanceof ClientboundTeleportEntityPacket) { ++ this.broadcast.accept((packet1)); ++ } else { ++ ClientboundTeleportEntityPacket teleportPacket = null; ++ ++ for (java.util.Map.Entry viewer : trackedPlayerMap.entrySet()) { ++ if (viewer.getValue()) { ++ viewer.setValue(false); ++ if (teleportPacket == null) { ++ teleportPacket = new ClientboundTeleportEntityPacket(this.entity); ++ } ++ sendPlayerPacket(viewer.getKey(), teleportPacket); ++ } else { ++ sendPlayerPacket(viewer.getKey(), packet1); ++ } ++ } ++ } ++ // Paper end + } + + this.sendDirtyEntityData(); diff --git a/Remapped-Spigot-Server-Patches/0039-Add-BeaconEffectEvent.patch b/Remapped-Spigot-Server-Patches/0039-Add-BeaconEffectEvent.patch new file mode 100644 index 000000000..d61c3cadc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0039-Add-BeaconEffectEvent.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 23:30:53 -0600 +Subject: [PATCH] Add BeaconEffectEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index c91f7bcfab2da6a23114a3cff63ca31dab443393..5f75c6d653a31f65fcf9c0e280d796e15d059c00 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -25,7 +25,6 @@ import net.minecraft.world.effect.MobEffect; + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.effect.MobEffects; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.BeaconMenu; + import net.minecraft.world.inventory.ContainerData; +@@ -41,6 +40,11 @@ import net.minecraft.world.phys.AABB; + import org.bukkit.craftbukkit.potion.CraftPotionUtil; + import org.bukkit.potion.PotionEffect; + // CraftBukkit end ++// Paper start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.Player; ++import com.destroystokyo.paper.event.block.BeaconEffectEvent; ++// Paper end + + public class BeaconBlockEntity extends BlockEntity implements MenuProvider, TickableBlockEntity { + +@@ -260,21 +264,37 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + double d0 = (double) (this.levels * 10 + 10); + + AABB axisalignedbb = (new AABB(this.worldPosition)).inflate(d0).expandTowards(0.0D, (double) this.level.getMaxBuildHeight(), 0.0D); +- List list = this.level.getEntitiesOfClass(Player.class, axisalignedbb); ++ List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.player.Player.class, axisalignedbb); + + return list; + } + } + + private void applyEffect(List list, MobEffect effects, int i, int b0) { ++ // Paper - BeaconEffectEvent ++ applyEffect(list, effects, i, b0, true); ++ } ++ ++ private void applyEffect(List list, MobEffect effects, int i, int b0, boolean isPrimary) { ++ // Paper - BeaconEffectEvent + { + Iterator iterator = list.iterator(); + +- Player entityhuman; ++ net.minecraft.world.entity.player.Player entityhuman; ++ ++ // Paper start - BeaconEffectEvent ++ org.bukkit.block.Block block = level.getWorld().getBlockAt(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); ++ PotionEffect effect = CraftPotionUtil.toBukkit(new MobEffectInstance(effects, i, b0, true, true)); ++ // Paper end + + while (iterator.hasNext()) { +- entityhuman = (Player) iterator.next(); +- entityhuman.addEffect(new MobEffectInstance(effects, i, b0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON); ++ entityhuman = (net.minecraft.world.entity.player.Player) iterator.next(); ++ ++ // Paper start - BeaconEffectEvent ++ BeaconEffectEvent event = new BeaconEffectEvent(block, effect, (Player) entityhuman.getBukkitEntity(), isPrimary); ++ if (CraftEventFactory.callEvent(event).isCancelled()) continue; ++ entityhuman.addEffect(new MobEffectInstance(CraftPotionUtil.fromBukkit(event.getEffect())), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON); ++ // Paper end + } + } + } +@@ -297,10 +317,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + int i = getLevelCb(); + List list = getHumansInRange(); + +- applyEffect(list, this.primaryPower, i, b0); ++ applyEffect(list, this.primaryPower, i, b0, true); // Paper - BeaconEffectEvent + + if (hasSecondaryEffect()) { +- applyEffect(list, this.secondaryPower, i, 0); ++ applyEffect(list, this.secondaryPower, i, 0, false); // Paper - BeaconEffectEvent + } + } + +@@ -308,7 +328,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + // CraftBukkit end + + public void playSound(SoundEvent soundeffect) { +- this.level.playSound((Player) null, this.worldPosition, soundeffect, SoundSource.BLOCKS, 1.0F, 1.0F); ++ this.level.playSound((net.minecraft.world.entity.player.Player) null, this.worldPosition, soundeffect, SoundSource.BLOCKS, 1.0F, 1.0F); + } + + public int getLevels() { +@@ -368,7 +388,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + + @Nullable + @Override +- public AbstractContainerMenu createMenu(int syncId, Inventory inv, Player player) { ++ public AbstractContainerMenu createMenu(int syncId, Inventory inv, net.minecraft.world.entity.player.Player player) { + return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, inv, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; + } + diff --git a/Remapped-Spigot-Server-Patches/0040-Configurable-container-update-tick-rate.patch b/Remapped-Spigot-Server-Patches/0040-Configurable-container-update-tick-rate.patch new file mode 100644 index 000000000..3344a8345 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0040-Configurable-container-update-tick-rate.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 23:34:44 -0600 +Subject: [PATCH] Configurable container update tick rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4de86b09c6bc3c1974ce61b550ccb73d37f6f170..5a4c3a8c511f22c8c3240c9c7cd83a65119c1054 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -181,4 +181,9 @@ public class PaperWorldConfig { + private void mobSpawnerTickRate() { + mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 1); + } ++ ++ public int containerUpdateTickRate; ++ private void containerUpdateTickRate() { ++ containerUpdateTickRate = getInt("container-update-tick-rate", 1); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f3797bd761c2c6782cce3fca25bc9ef37e5c4978..ffad931c72e52855a3f139354f5e85c460e2a80b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -207,6 +207,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public boolean ignoreSlotUpdateHack; + public int latency; + public boolean wonGame; ++ private int containerUpdateDelay; // Paper + + // CraftBukkit start + public String displayName; +@@ -531,7 +532,12 @@ public class ServerPlayer extends Player implements ContainerListener { + --this.invulnerableTime; + } + +- this.containerMenu.broadcastChanges(); ++ // Paper start - Configurable container update tick rate ++ if (--containerUpdateDelay <= 0) { ++ this.containerMenu.broadcastChanges(); ++ containerUpdateDelay = level.paperConfig.containerUpdateTickRate; ++ } ++ // Paper end + if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) { + this.closeContainer(); + this.containerMenu = this.inventoryMenu; diff --git a/Remapped-Spigot-Server-Patches/0041-Use-UserCache-for-player-heads.patch b/Remapped-Spigot-Server-Patches/0041-Use-UserCache-for-player-heads.patch new file mode 100644 index 000000000..1c9a8dab7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0041-Use-UserCache-for-player-heads.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Wed, 2 Mar 2016 23:42:37 -0600 +Subject: [PATCH] Use UserCache for player heads + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 11baf6fd5f7e408a570d5a48ae6b2fc05cd7e243..313ddd6b64e395a8caab77b3da005e52006ab2d7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -166,7 +166,13 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + if (name == null) { + setProfile(null); + } else { +- setProfile(new GameProfile(null, name)); ++ // Paper start - Use Online Players Skull ++ GameProfile newProfile = null; ++ net.minecraft.server.EntityPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayerByName(name); ++ if (player != null) newProfile = player.getProfile(); ++ if (newProfile == null) newProfile = new GameProfile(null, name); ++ setProfile(newProfile); ++ // Paper end + } + + return true; diff --git a/Remapped-Spigot-Server-Patches/0042-Disable-spigot-tick-limiters.patch b/Remapped-Spigot-Server-Patches/0042-Disable-spigot-tick-limiters.patch new file mode 100644 index 000000000..703b1b077 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0042-Disable-spigot-tick-limiters.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 23:45:17 -0600 +Subject: [PATCH] Disable spigot tick limiters + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index f71b56fa079e2c7b2123061a8e1a7cb41935bab6..e25666328dbf433b8358f2637d93b4128034bbaa 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -707,9 +707,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // Spigot start + // Iterator iterator = this.tileEntityListTick.iterator(); + int tilesThisCycle = 0; +- for (tileLimiter.initTick(); +- tilesThisCycle < tickableBlockEntities.size() && (tilesThisCycle % 10 != 0 || tileLimiter.shouldContinue()); +- tileTickPosition++, tilesThisCycle++) { ++ for (tileTickPosition = 0; tileTickPosition < tickableBlockEntities.size(); tileTickPosition++) { // Paper - Disable tick limiters + tileTickPosition = (tileTickPosition < tickableBlockEntities.size()) ? tileTickPosition : 0; + BlockEntity tileentity = (BlockEntity) this.tickableBlockEntities.get(tileTickPosition); + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0043-Add-PlayerInitialSpawnEvent.patch b/Remapped-Spigot-Server-Patches/0043-Add-PlayerInitialSpawnEvent.patch new file mode 100644 index 000000000..7e6b23846 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0043-Add-PlayerInitialSpawnEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Steve Anton +Date: Thu, 3 Mar 2016 00:09:38 -0600 +Subject: [PATCH] Add PlayerInitialSpawnEvent + +For modifying a player's initial spawn location as they join the server + +This is a duplicate API from spigot, so use our duplicate subclass and +improve setPosition to use raw + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 8cdecaf2f63c78196e0c5046fe2431b40e072c8a..a63babe123fad398b07685ec57cd88756435457c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -209,7 +209,7 @@ public abstract class PlayerList { + + // Spigot start - spawn location event + Player bukkitPlayer = player.getBukkitEntity(); +- org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(bukkitPlayer, bukkitPlayer.getLocation()); ++ org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new com.destroystokyo.paper.event.player.PlayerInitialSpawnEvent(bukkitPlayer, bukkitPlayer.getLocation()); // Paper use our duplicate event + cserver.getPluginManager().callEvent(ev); + + Location loc = ev.getSpawnLocation(); +@@ -217,7 +217,10 @@ public abstract class PlayerList { + + player.setLevel(worldserver1); + player.gameMode.setLevel((ServerLevel) player.level); +- player.absMoveTo(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); ++ // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world) ++ player.setPosRaw(loc.getX(), loc.getY(), loc.getZ()); ++ player.setRot(loc.getYaw(), loc.getPitch()); ++ // Paper end + // Spigot end + + // CraftBukkit - Moved message to after join +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f3f48c268639937874dd39eea9bd8e119eebdce7..72eb40f748c33572c2828f48ebd1ca7d5d5712c8 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -397,7 +397,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return d1 * d1 + d2 * d2 + d3 * d3 < radius * radius; + } + +- protected void setRot(float yaw, float pitch) { ++ public void setRot(float yaw, float pitch) { // Paper - protected -> public + // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0 + if (Float.isNaN(yaw)) { + yaw = 0; diff --git a/Remapped-Spigot-Server-Patches/0044-Configurable-Disabling-Cat-Chest-Detection.patch b/Remapped-Spigot-Server-Patches/0044-Configurable-Disabling-Cat-Chest-Detection.patch new file mode 100644 index 000000000..88836aa6a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0044-Configurable-Disabling-Cat-Chest-Detection.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 01:13:45 -0600 +Subject: [PATCH] Configurable Disabling Cat Chest Detection + +Offers a gameplay feature to stop cats from blocking chests + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5a4c3a8c511f22c8c3240c9c7cd83a65119c1054..70e074cdf2087e638af8e0f3878d0ef8eb7305cc 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -186,4 +186,9 @@ public class PaperWorldConfig { + private void containerUpdateTickRate() { + containerUpdateTickRate = getInt("container-update-tick-rate", 1); + } ++ ++ public boolean disableChestCatDetection; ++ private void disableChestCatDetection() { ++ disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +index 1bda9a158eb4372b9ab7cf3097732e64810aefc6..6b95cd2e2af66eef324dfcc8f7642da2f9e39d4e 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +@@ -312,6 +312,11 @@ public class ChestBlock extends AbstractChestBlock implements + } + + private static boolean isCatSittingOnChest(LevelAccessor world, BlockPos pos) { ++ // Paper start - Option to disable chest cat detection ++ if (((Level) world).paperConfig.disableChestCatDetection) { ++ return false; ++ } ++ // Paper end + List list = world.getEntitiesOfClass(Cat.class, new AABB((double) pos.getX(), (double) (pos.getY() + 1), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 2), (double) (pos.getZ() + 1))); + + if (!list.isEmpty()) { diff --git a/Remapped-Spigot-Server-Patches/0045-Ensure-commands-are-not-ran-async.patch b/Remapped-Spigot-Server-Patches/0045-Ensure-commands-are-not-ran-async.patch new file mode 100644 index 000000000..b0605d0e7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0045-Ensure-commands-are-not-ran-async.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 01:17:12 -0600 +Subject: [PATCH] Ensure commands are not ran async + +Plugins calling Player.chat("/foo") or Server.dispatchCommand() could +trigger the server to execute a command while on another thread. + +These commands would then process EXPECTING to be on the main thread, leaving to +very hard to trace concurrency issues. + +This change will synchronize the command execution back to the main thread, causing a +big slowdown in execution but throwing an exception at same time to raise awareness +that it is happening so that plugin authors can fix their code to stop executing commands async. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 016e91a6ca1c8457e3e367ac0597b73e81919b68..00689dc07625a02781052c5df2e466e8abe85708 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1852,6 +1852,29 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + if (!async && s.startsWith("/")) { ++ // Paper Start ++ if (!org.spigotmc.AsyncCatcher.shuttingDown && !org.bukkit.Bukkit.isPrimaryThread()) { ++ final String fCommandLine = s; ++ MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Command Dispatched Async: " + fCommandLine); ++ MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable()); ++ Waitable wait = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ chat(fCommandLine, false); ++ return null; ++ } ++ }; ++ server.processQueue.add(wait); ++ try { ++ wait.get(); ++ return; ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (Exception e) { ++ throw new RuntimeException("Exception processing chat command", e.getCause()); ++ } ++ } ++ // Paper End + this.handleCommand(s); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { + // Do nothing, this is coming from a plugin +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6fa31ca31128b1094eebd5f848c5b506dfeedeeb..783da25e189c0264ebf31e244677a6b653ff7b26 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -759,6 +759,29 @@ public final class CraftServer implements Server { + Validate.notNull(commandLine, "CommandLine cannot be null"); + org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot + ++ // Paper Start ++ if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) { ++ final CommandSender fSender = sender; ++ final String fCommandLine = commandLine; ++ Bukkit.getLogger().log(Level.SEVERE, "Command Dispatched Async: " + commandLine); ++ Bukkit.getLogger().log(Level.SEVERE, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable()); ++ org.bukkit.craftbukkit.util.Waitable wait = new org.bukkit.craftbukkit.util.Waitable() { ++ @Override ++ protected Boolean evaluate() { ++ return dispatchCommand(fSender, fCommandLine); ++ } ++ }; ++ net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait); ++ try { ++ return wait.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (Exception e) { ++ throw new RuntimeException("Exception processing dispatch command", e.getCause()); ++ } ++ } ++ // Paper End ++ + if (commandMap.dispatch(sender, commandLine)) { + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index ddef523ea8762c927f37f7d16d581e43367e8c6b..70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -13,6 +13,7 @@ public class ServerShutdownThread extends Thread { + public void run() { + try { + org.spigotmc.AsyncCatcher.enabled = false; // Spigot ++ org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper + server.close(); + } finally { + try { +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index aeed7697254af17ffefe8e578353ad216e15f9f3..9f7d2ef932ab41cef5d3d0736d20a7c7e4a2c888 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -6,6 +6,7 @@ public class AsyncCatcher + { + + public static boolean enabled = true; ++ public static boolean shuttingDown = false; // Paper + + public static void catchOp(String reason) + { +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index a4223094802a7e996cc57c617df92d23bc48f5b5..04ae5fec376af006ec828d1ae568338af5cfe6ce 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -43,6 +43,7 @@ public class RestartCommand extends Command + private static void restart(final String restartScript) + { + AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us ++ org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper + try + { + String[] split = restartScript.split( " " ); diff --git a/Remapped-Spigot-Server-Patches/0046-All-chunks-are-slime-spawn-chunks-toggle.patch b/Remapped-Spigot-Server-Patches/0046-All-chunks-are-slime-spawn-chunks-toggle.patch new file mode 100644 index 000000000..cee1933ce --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0046-All-chunks-are-slime-spawn-chunks-toggle.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: vemacs +Date: Thu, 3 Mar 2016 01:19:22 -0600 +Subject: [PATCH] All chunks are slime spawn chunks toggle + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 70e074cdf2087e638af8e0f3878d0ef8eb7305cc..416a6760883cb40367535c7c5acd779742bb8af5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -191,4 +191,9 @@ public class PaperWorldConfig { + private void disableChestCatDetection() { + disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); + } ++ ++ public boolean allChunksAreSlimeChunks; ++ private void allChunksAreSlimeChunks() { ++ allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 8ff8c19f0b258623b9f0a3cfd0ad5595a92f5899..fc8f26e988f1e4826dcfdcf071293bb356163e62 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -323,7 +323,7 @@ public class Slime extends Mob implements Enemy { + } + + ChunkPos chunkcoordintpair = new ChunkPos(pos); +- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getLevel().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot ++ boolean flag = world.getLevel().paperConfig.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getLevel().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + + if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { + return checkMobSpawnRules(type, world, spawnReason, pos, random); diff --git a/Remapped-Spigot-Server-Patches/0047-Expose-server-CommandMap.patch b/Remapped-Spigot-Server-Patches/0047-Expose-server-CommandMap.patch new file mode 100644 index 000000000..c633af229 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0047-Expose-server-CommandMap.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 3 Mar 2016 02:15:57 -0600 +Subject: [PATCH] Expose server CommandMap + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 783da25e189c0264ebf31e244677a6b653ff7b26..95d32f37db663a37f8fde927bdf9d3d4802ba1b4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1760,6 +1760,7 @@ public final class CraftServer implements Server { + return helpMap; + } + ++ @Override // Paper - add override + public SimpleCommandMap getCommandMap() { + return commandMap; + } diff --git a/Remapped-Spigot-Server-Patches/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch b/Remapped-Spigot-Server-Patches/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch new file mode 100644 index 000000000..b47fa1266 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 3 Mar 2016 02:18:39 -0600 +Subject: [PATCH] Be a bit more informative in maxHealth exception + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index d8bfb0953f0b23c64f4e27fc84a6c5f3eb0cc8b8..3afdcb3013263a7e06876821d7d889fa48404041 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -99,7 +99,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setHealth(double health) { + health = (float) health; + if ((health < 0) || (health > getMaxHealth())) { +- throw new IllegalArgumentException("Health must be between 0 and " + getMaxHealth() + "(" + health + ")"); ++ // Paper - Be more informative ++ throw new IllegalArgumentException("Health must be between 0 and " + getMaxHealth() + ", but was " + health ++ + ". (attribute base value: " + this.getHandle().getAttribute(Attributes.MAX_HEALTH).getBaseValue() ++ + (this instanceof CraftPlayer ? ", player: " + this.getName() + ')' : ')')); + } + + getHandle().setHealth((float) health); diff --git a/Remapped-Spigot-Server-Patches/0049-Player-Tab-List-and-Title-APIs.patch b/Remapped-Spigot-Server-Patches/0049-Player-Tab-List-and-Title-APIs.patch new file mode 100644 index 000000000..c26e87ac6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0049-Player-Tab-List-and-Title-APIs.patch @@ -0,0 +1,173 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Thu, 3 Mar 2016 02:32:10 -0600 +Subject: [PATCH] Player Tab List and Title APIs + + +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index 59788eaef0dae5ee01ceba1bf45e85cb07f88e53..b4542ce6a8c37ab31e6ecaeb4cbad4742cca0f9b 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -170,6 +170,11 @@ public class FriendlyByteBuf extends ByteBuf { + public FriendlyByteBuf writeComponent(final net.kyori.adventure.text.Component component) { + return this.writeUtf(PaperAdventure.asJsonString(component, this.adventure$locale), 262144); + } ++ ++ @Deprecated ++ public FriendlyByteBuf writeComponent(final net.md_5.bungee.api.chat.BaseComponent[] component) { ++ return this.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(component), 262144); ++ } + // Paper end + + public FriendlyByteBuf writeComponent(Component text) { +diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java +index 54d186a195aca6d0a4c412ed609d8c86dcc76072..06e9246f05e130be6a63ebb0c9def10c6c9675b7 100644 +--- a/src/main/java/net/minecraft/network/chat/Component.java ++++ b/src/main/java/net/minecraft/network/chat/Component.java +@@ -363,6 +363,7 @@ public interface Component extends Message, FormattedText, Iterable { + return Component.Serializer.GSON.toJsonTree(text); + } + ++ @Nullable public static Component jsonToComponent(String json) { return fromJson(json);} // Paper - OBFHELPER + @Nullable + public static MutableComponent fromJson(String json) { + return (MutableComponent) GsonHelper.fromJson(Component.Serializer.GSON, json, MutableComponent.class, false); +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java +index 69ff8df7340e60c476803256750a48f0b43414d3..df444daeb181ff78170f7b92bd02f1f1862dfa2e 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTitlesPacket.java +@@ -47,6 +47,17 @@ public class ClientboundSetTitlesPacket implements Packet +Date: Thu, 3 Mar 2016 02:33:53 -0600 +Subject: [PATCH] Ensure inv drag is in bounds + + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index c377a425dc3274b8aa25f94ce8f76efda2652def..72b0cfcc5aab03e14e63440c734436e9c1432111 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -238,7 +238,7 @@ public abstract class AbstractContainerMenu { + this.resetQuickCraft(); + } + } else if (this.quickcraftStatus == 1) { +- Slot slot = (Slot) this.slots.get(i); ++ Slot slot = i < this.slots.size() ? this.slots.get(i) : null; // Paper - Ensure drag in bounds + + itemstack1 = playerinventory.getCarried(); + if (slot != null && canItemQuickReplace(slot, itemstack1, true) && slot.isAllowed(itemstack1) && (this.quickcraftType == 2 || itemstack1.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { diff --git a/Remapped-Spigot-Server-Patches/0051-Change-implementation-of-tile-entity-removal-list.patch b/Remapped-Spigot-Server-Patches/0051-Change-implementation-of-tile-entity-removal-list.patch new file mode 100644 index 000000000..6e0641f0d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0051-Change-implementation-of-tile-entity-removal-list.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:39:54 -0600 +Subject: [PATCH] Change implementation of (tile)entity removal list + +use sets for faster removal + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 6c6098731752d61b5241710b075d4ffe3826daac..89472b6e8f38921db50440d0213e40ac893892f1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1122,7 +1122,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + // Spigot End +- this.blockEntitiesToUnload.addAll(chunk.getBlockEntities().values()); ++ this.tileEntityListUnload.addAll(chunk.getBlockEntities().values()); + List[] aentityslice = chunk.getEntitySlices(); // Spigot + int i = aentityslice.length; + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index e25666328dbf433b8358f2637d93b4128034bbaa..7b4475807cca0e92ea9ae6ea49a82a8634cc0ff5 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -89,7 +89,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final List blockEntityList = Lists.newArrayList(); + public final List tickableBlockEntities = Lists.newArrayList(); + protected final List pendingBlockEntities = Lists.newArrayList(); +- protected final List blockEntitiesToUnload = Lists.newArrayList(); ++ protected final java.util.Set tileEntityListUnload = com.google.common.collect.Sets.newHashSet(); + public final Thread thread; + private final boolean isDebug; + private int skyDarken; +@@ -697,10 +697,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + gameprofilerfiller.push("blockEntities"); + timings.tileEntityTick.startTiming(); // Spigot +- if (!this.blockEntitiesToUnload.isEmpty()) { +- this.tickableBlockEntities.removeAll(this.blockEntitiesToUnload); +- this.blockEntityList.removeAll(this.blockEntitiesToUnload); +- this.blockEntitiesToUnload.clear(); ++ if (!this.tileEntityListUnload.isEmpty()) { ++ this.tickableBlockEntities.removeAll(this.tileEntityListUnload); ++ this.blockEntityList.removeAll(this.tileEntityListUnload); ++ this.tileEntityListUnload.clear(); + } + + this.updatingBlockEntities = true; diff --git a/Remapped-Spigot-Server-Patches/0052-Add-configurable-portal-search-radius.patch b/Remapped-Spigot-Server-Patches/0052-Add-configurable-portal-search-radius.patch new file mode 100644 index 000000000..8bbb23e40 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0052-Add-configurable-portal-search-radius.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:46:17 -0600 +Subject: [PATCH] Add configurable portal search radius + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 416a6760883cb40367535c7c5acd779742bb8af5..670efbe53241a0ae32d618c83da601ccc1f26e37 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -196,4 +196,13 @@ public class PaperWorldConfig { + private void allChunksAreSlimeChunks() { + allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); + } ++ ++ public int portalSearchRadius; ++ public int portalCreateRadius; ++ public boolean portalSearchVanillaDimensionScaling; ++ private void portalSearchRadius() { ++ portalSearchRadius = getInt("portal-search-radius", 128); ++ portalCreateRadius = getInt("portal-create-radius", 16); ++ portalSearchVanillaDimensionScaling = getBoolean("portal-search-vanilla-dimension-scaling", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 72eb40f748c33572c2828f48ebd1ca7d5d5712c8..a6f2e671cc9b2ef086dfa3d127a7b33272acbd56 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2617,7 +2617,13 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + double d4 = DimensionType.getTeleportationScale(this.level.dimensionType(), destination.dimensionType()); + BlockPos blockposition = new BlockPos(Mth.clamp(this.getX() * d4, d0, d2), this.getY(), Mth.clamp(this.getZ() * d4, d1, d3)); + // CraftBukkit start +- CraftPortalEvent event = callPortalEvent(this, destination, blockposition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, flag2 ? 16 : 128, 16); ++ // Paper start ++ int portalSearchRadius = destination.paperConfig.portalSearchRadius; ++ if (level.paperConfig.portalSearchVanillaDimensionScaling && flag2) { // == THE_NETHER ++ portalSearchRadius = (int) (portalSearchRadius / destination.dimensionType().coordinateScale()); ++ } ++ // Paper end ++ CraftPortalEvent event = callPortalEvent(this, destination, blockposition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, destination.paperConfig.portalCreateRadius); // Paper start - configurable portal radius + if (event == null) { + return null; + } +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index 948d031627435bfce442b1fe7d3eff4addc85bc4..21c01302635d23bc21e6bb373cbe277ea1eb6a56 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -30,7 +30,7 @@ public class PortalForcer { + + public Optional findPortalAround(BlockPos blockposition, boolean flag) { + // CraftBukkit start +- return findPortalAround(blockposition, flag ? 16 : 128); // Search Radius ++ return findPortalAround(blockposition, flag ? level.paperConfig.portalCreateRadius : level.paperConfig.portalSearchRadius); // Paper - search Radius + } + + public Optional findPortal(BlockPos blockposition, int i) { diff --git a/Remapped-Spigot-Server-Patches/0053-Add-velocity-warnings.patch b/Remapped-Spigot-Server-Patches/0053-Add-velocity-warnings.patch new file mode 100644 index 000000000..3a1107873 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0053-Add-velocity-warnings.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:48:12 -0600 +Subject: [PATCH] Add velocity warnings + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 95d32f37db663a37f8fde927bdf9d3d4802ba1b4..35d3df7ded4904414a9a61895950b56be530d244 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -260,6 +260,7 @@ public final class CraftServer implements Server { + public boolean ignoreVanillaPermissions = false; + private final List playerView; + public int reloadCount; ++ public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index e7a59a8e0424a0839dfa73fc65f44c5b04bd3dec..b028946de7c8f52091635fe154c816453f1ddc93 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -424,10 +424,41 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public void setVelocity(Vector velocity) { + Preconditions.checkArgument(velocity != null, "velocity"); + velocity.checkFinite(); ++ // Paper start - Warn server owners when plugins try to set super high velocities ++ if (!(this instanceof org.bukkit.entity.Projectile) && isUnsafeVelocity(velocity)) { ++ CraftServer.excessiveVelEx = new Exception("Excessive velocity set detected: tried to set velocity of entity " + entity.getScoreboardName() + " id #" + getEntityId() + " to (" + velocity.getX() + "," + velocity.getY() + "," + velocity.getZ() + ")."); ++ } ++ // Paper end ++ + entity.setDeltaMovement(CraftVector.toNMS(velocity)); + entity.hurtMarked = true; + } + ++ // Paper start ++ /** ++ * Checks if the given velocity is not necessarily safe in all situations. ++ * This function returning true does not mean the velocity is dangerous or to be avoided, only that it may be ++ * a detriment to performance on the server. ++ * ++ * It is not to be used as a hard rule of any sort. ++ * Paper only uses it to warn server owners in watchdog crashes. ++ * ++ * @param vel incoming velocity to check ++ * @return if the velocity has the potential to be a performance detriment ++ */ ++ private static boolean isUnsafeVelocity(Vector vel) { ++ final double x = vel.getX(); ++ final double y = vel.getY(); ++ final double z = vel.getZ(); ++ ++ if (x > 4 || x < -4 || y > 4 || y < -4 || z > 4 || z < -4) { ++ return true; ++ } ++ ++ return false; ++ } ++ // Paper end ++ + @Override + public double getHeight() { + return getHandle().getBbHeight(); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 21d7b483920841456707fe3f08b180c1f072b7f7..0ed95268364ea7f6a92a39b726a1e03bc815be07 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -80,7 +80,19 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed" ); + log.log( Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem ); + } +- // ++ // Paper start - Warn in watchdog if an excessive velocity was ever set ++ if ( org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null ) ++ { ++ log.log( Level.SEVERE, "------------------------------" ); ++ log.log( Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity" ); ++ log.log( Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated" ); ++ log.log( Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); ++ for ( StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace() ) ++ { ++ log.log( Level.SEVERE, "\t\t" + stack ); ++ } ++ } ++ // Paper end + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); diff --git a/Remapped-Spigot-Server-Patches/0054-Configurable-inter-world-teleportation-safety.patch b/Remapped-Spigot-Server-Patches/0054-Configurable-inter-world-teleportation-safety.patch new file mode 100644 index 000000000..c0826d9b3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0054-Configurable-inter-world-teleportation-safety.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Thu, 3 Mar 2016 02:50:31 -0600 +Subject: [PATCH] Configurable inter-world teleportation safety + +People are able to abuse the way Bukkit handles teleportation across worlds since it provides a built in teleportation +safety check. + +To abuse the safety check, players are required to get into a location deemed unsafe by Bukkit e.g. be within a chest +or door block. While they are in this block, they accept a teleport request from a player within a different world. Once +the player teleports, Minecraft will recursively search upwards for a safe location, this could eventually land within a +player's skybase. + +Example setup to perform the glitch: http://puu.sh/ng3PC/cf072dcbdb.png +The wanted destination was on top of the emerald block however the player ended on top of the diamond block. +This only is the case if the player is teleporting between worlds. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 670efbe53241a0ae32d618c83da601ccc1f26e37..abbbe1786eb68af02f9d39650aad730ac44aac8a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -205,4 +205,9 @@ public class PaperWorldConfig { + portalCreateRadius = getInt("portal-create-radius", 16); + portalSearchVanillaDimensionScaling = getBoolean("portal-search-vanilla-dimension-scaling", true); + } ++ ++ public boolean disableTeleportationSuffocationCheck; ++ private void disableTeleportationSuffocationCheck() { ++ disableTeleportationSuffocationCheck = getBoolean("disable-teleportation-suffocation-check", false); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 63933bd455ad72a772d4db160e946600b84a1791..3b9d61b524441f65646edf7d403b6c5b5345b1e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -861,7 +861,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (fromWorld == toWorld) { + entity.connection.teleport(to); + } else { +- server.getHandle().moveToWorld(entity, toWorld, true, to, true); ++ server.getHandle().moveToWorld(entity, toWorld, true, to, !toWorld.paperConfig.disableTeleportationSuffocationCheck); // Paper + } + return true; + } diff --git a/Remapped-Spigot-Server-Patches/0055-Add-exception-reporting-event.patch b/Remapped-Spigot-Server-Patches/0055-Add-exception-reporting-event.patch new file mode 100644 index 000000000..7b87e0d39 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0055-Add-exception-reporting-event.patch @@ -0,0 +1,264 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 03:15:41 -0600 +Subject: [PATCH] Add exception reporting event + + +diff --git a/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f699ce18ca044f813e194ef2786b7ea853ea86e7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java +@@ -0,0 +1,38 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.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/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index d1bc927c8b429f43de2cdad98f8b329ff4c8b4db..0597c0c3e881dd43cf91bd3088ed30dfecfe8098 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -813,6 +813,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return true; + } catch (Exception exception) { + ChunkMap.LOGGER.error("Failed to save chunk {},{}", chunkcoordintpair.x, chunkcoordintpair.z, exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + return false; + } + } +diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +index c167d2fd99a7a352e69e2930551678bd9c9def83..09c5fa2dbcbed05da51ef2d63e6d6112d22d7877 100644 +--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java ++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.players; + ++import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.io.Files; +@@ -360,6 +361,7 @@ public class OldUsersConverter { + root = NbtIo.readCompressed(new java.io.FileInputStream(file5)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + if (root != null) { +@@ -373,6 +375,7 @@ public class OldUsersConverter { + NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java +index 78cea15142f9fd7988f5df397061b90625070eef..f50774f022c78813982bfe08f764b54bde779e04 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.entity.ai.village; + ++import com.destroystokyo.paper.exception.ServerInternalException; ++ + import java.util.Iterator; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; +@@ -119,6 +121,7 @@ public class VillageSiege implements CustomSpawner { + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.EVENT, (SpawnGroupData) null, (CompoundTag) null); + } catch (Exception exception) { + VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); ++ ServerInternalException.reportInternalException(exception); // Paper + return; + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7b4475807cca0e92ea9ae6ea49a82a8634cc0ff5..94e268a05b4601c29b6d2845f0fc2311643a161f 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1,5 +1,10 @@ + package net.minecraft.world.level; + ++import co.aikar.timings.Timing; ++import co.aikar.timings.Timings; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerInternalException; ++import com.google.common.base.MoreObjects; + import com.google.common.collect.Lists; + import com.mojang.serialization.Codec; + import java.io.IOException; +@@ -737,8 +742,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + gameprofilerfiller.pop(); + } catch (Throwable throwable) { + // Paper start - Prevent tile entity and entity crashes +- System.err.println("TileEntity threw exception at " + tileentity.level.getWorld().getName() + ":" + tileentity.worldPosition.getX() + "," + tileentity.worldPosition.getY() + "," + tileentity.worldPosition.getZ()); ++ String msg = "TileEntity threw exception at " + tileentity.getLevel().getWorld().getName() + ":" + tileentity.getBlockPos().getX() + "," + tileentity.getBlockPos().getY() + "," + tileentity.getBlockPos().getZ(); ++ System.err.println(msg); + throwable.printStackTrace(); ++ getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); ++ // Paper end + tilesThisCycle--; + this.tickableBlockEntities.remove(tileTickPosition--); + continue; +@@ -808,8 +816,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + tickConsumer.accept(entity); + } catch (Throwable throwable) { + // Paper start - Prevent tile entity and entity crashes +- System.err.println("Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ()); ++ String msg = "Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ(); ++ System.err.println(msg); + throwable.printStackTrace(); ++ getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); + entity.removed = true; + return; + // Paper end +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index fc134b916e95231af8478a4f97bf11a0f37f7f0b..a19ac1cb7e4d8d478648a048b2bfa0daf85a80c9 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -299,6 +299,7 @@ public final class NaturalSpawner { + } + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + return null; + } + } +@@ -405,6 +406,7 @@ public final class NaturalSpawner { + entity = biomesettingsmobs_c.type.create((Level) worldaccess.getLevel()); + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 300749822d52f9f973e71c6ec9c8bf29d6a6938e..9ca05aa06696883adc8b67a68ca6d2d850e95d25 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.chunk; + ++import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +@@ -654,10 +655,15 @@ public class LevelChunk implements ChunkAccess { + this.blockEntities.remove(pos); + // Paper end + } else { +- System.out.println("Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + "," + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ() +- + " (" + getBlockState(pos) + ") where there was no entity tile!"); +- System.out.println("Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16)); +- new Exception().printStackTrace(); ++ // Paper start ++ ServerInternalException e = new ServerInternalException( ++ "Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + "," ++ + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ() ++ + " (" + getBlockState(pos) + ") where there was no entity tile!\n" + ++ "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16)); ++ e.printStackTrace(); ++ ServerInternalException.reportInternalException(e); ++ // Paper end + // CraftBukkit end + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 60f410a4f838048bbfd2cde52caa7c4c9434b0ba..1598da3449ee1c559cf503e1b20a0daaf6a033dd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -265,6 +265,7 @@ public class RegionFile implements AutoCloseable { + return true; + } + } catch (IOException ioexception) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper + return false; + } + } +@@ -337,6 +338,7 @@ public class RegionFile implements AutoCloseable { + filechannel.write(bytebuffer); + } catch (Throwable throwable1) { + throwable = throwable1; ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper + throw throwable1; + } finally { + if (filechannel != null) { +diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +index 60b7fdf9c092e8105d41f4af02a08651624f3eb9..99cfd693ea705d45a5eab181cb80c354a2d1159f 100644 +--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -150,6 +150,7 @@ public class DimensionDataStorage { + } + } catch (Throwable throwable6) { + throwable = throwable6; ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper + throw throwable6; + } finally { + if (fileinputstream != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index ffe9cc1011226d604dc5499e7692e9a9a5132b72..9b6d9373abb59a30c2835ca891282d07559281f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -16,6 +16,9 @@ import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicReference; + import java.util.function.Consumer; + import java.util.logging.Level; ++import com.destroystokyo.paper.ServerSchedulerReportingWrapper; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerSchedulerException; + import org.apache.commons.lang.Validate; + import org.bukkit.plugin.IllegalPluginAccessException; + import org.bukkit.plugin.Plugin; +@@ -419,6 +422,8 @@ public class CraftScheduler implements BukkitScheduler { + msg, + throwable); + } ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))); + // Paper end + } finally { + currentTask = null; +@@ -426,7 +431,7 @@ public class CraftScheduler implements BukkitScheduler { + parsePending(); + } else { + debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); +- executor.execute(task); ++ executor.execute(new ServerSchedulerReportingWrapper(task)); // 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) + } diff --git a/Remapped-Spigot-Server-Patches/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch b/Remapped-Spigot-Server-Patches/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch new file mode 100644 index 000000000..f1642d7a3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Tue, 8 Mar 2016 18:28:43 -0800 +Subject: [PATCH] Don't nest if we don't need to when cerealising text + components + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +index e47102cadb40ed8a9c011386445f15fd30de7596..f13da9e7d014bc00fbabf0a495b548bba2f59468 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +@@ -40,7 +40,14 @@ public class ClientboundChatPacket implements Packet { + // Paper end + // Spigot start + if (components != null) { +- buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(components)); ++ //packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below ++ // Paper start - don't nest if we don't need to so that we can preserve formatting ++ if (this.components.length == 1) { ++ buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0])); ++ } else { ++ buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components)); ++ } ++ // Paper end + } else { + buf.writeComponent(this.message); + } diff --git a/Remapped-Spigot-Server-Patches/0057-Disable-Scoreboards-for-non-players-by-default.patch b/Remapped-Spigot-Server-Patches/0057-Disable-Scoreboards-for-non-players-by-default.patch new file mode 100644 index 000000000..d1cae6e60 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0057-Disable-Scoreboards-for-non-players-by-default.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 8 Mar 2016 23:25:45 -0500 +Subject: [PATCH] Disable Scoreboards for non players by default + +Entities collision is checking for scoreboards setting. +This is very heavy to do map lookups for every collision to check +this setting. + +So avoid looking up scoreboards and short circuit to the "not on a team" +logic which is most likely to be true. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index abbbe1786eb68af02f9d39650aad730ac44aac8a..3ac2ac3db9b1c271b3c21930bb13716669ff64d3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -210,4 +210,9 @@ public class PaperWorldConfig { + private void disableTeleportationSuffocationCheck() { + disableTeleportationSuffocationCheck = getBoolean("disable-teleportation-suffocation-check", false); + } ++ ++ public boolean nonPlayerEntitiesOnScoreboards = false; ++ private void nonPlayerEntitiesOnScoreboards() { ++ nonPlayerEntitiesOnScoreboards = getBoolean("allow-non-player-entities-on-scoreboards", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a6f2e671cc9b2ef086dfa3d127a7b33272acbd56..93d3408231a177cf6d2086594756adffe3efa702 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2288,6 +2288,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + @Nullable + public Team getTeam() { ++ if (!this.level.paperConfig.nonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper + return this.level.getScoreboard().getPlayerTeam(this.getScoreboardName()); + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index afd114e1ce00db72534d470fed12101bb237f266..d483d552092c901fec262c43e488784d9cd8acb9 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -738,6 +738,7 @@ public abstract class LivingEntity extends Entity { + if (tag.contains("Team", 8)) { + String s = tag.getString("Team"); + PlayerTeam scoreboardteam = this.level.getScoreboard().getTeam(s); ++ if (!level.paperConfig.nonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper + boolean flag = scoreboardteam != null && this.level.getScoreboard().addPlayerToTeam(this.getStringUUID(), scoreboardteam); + + if (!flag) { diff --git a/Remapped-Spigot-Server-Patches/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/Remapped-Spigot-Server-Patches/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch new file mode 100644 index 000000000..e0d575923 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mrapple +Date: Sun, 25 Nov 2012 13:43:39 -0600 +Subject: [PATCH] Add methods for working with arrows stuck in living entities + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 3afdcb3013263a7e06876821d7d889fa48404041..d8cd88d62f9abfc7960c187dd74239f61267ca57 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -677,4 +677,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + getHandle().persistentInvisibility = invisible; + getHandle().setSharedFlag(5, invisible); + } ++ ++ // Paper start ++ @Override ++ public int getArrowsStuck() { ++ return getHandle().getArrowCount(); ++ } ++ ++ @Override ++ public void setArrowsStuck(int arrows) { ++ getHandle().setArrowCount(arrows); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0059-Complete-resource-pack-API.patch b/Remapped-Spigot-Server-Patches/0059-Complete-resource-pack-API.patch new file mode 100644 index 000000000..6be95196f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0059-Complete-resource-pack-API.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 4 Apr 2015 23:17:52 -0400 +Subject: [PATCH] Complete resource pack API + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 00689dc07625a02781052c5df2e466e8abe85708..73683ba59d0aff3a61f555b4ae15753e9e4e6141 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1603,7 +1603,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit start + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +- this.craftServer.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()])); ++ // Paper start ++ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()]; ++ player.getBukkitEntity().setResourcePackStatus(packStatus); ++ this.craftServer.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), packStatus)); ++ // Paper end + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3b9d61b524441f65646edf7d403b6c5b5345b1e5..dd29038778d73fae84df360515f3c670915f1d48 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -136,6 +136,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private double health = 20; + private boolean scaledHealth = false; + private double healthScale = 20; ++ // Paper start ++ private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; ++ private String resourcePackHash; ++ // Paper end + + public CraftPlayer(CraftServer server, ServerPlayer entity) { + super(server, entity); +@@ -1872,6 +1876,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public boolean getAffectsSpawning() { + return this.getHandle().affectsSpawning; + } ++ ++ @Override ++ public void setResourcePack(String url, String hash) { ++ Validate.notNull(url, "Resource pack URL cannot be null"); ++ Validate.notNull(hash, "Hash cannot be null"); ++ this.getHandle().sendTexturePack(url, hash); ++ } ++ ++ @Override ++ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus() { ++ return this.resourcePackStatus; ++ } ++ ++ @Override ++ public String getResourcePackHash() { ++ return this.resourcePackHash; ++ } ++ ++ @Override ++ public boolean hasResourcePack() { ++ return this.resourcePackStatus == org.bukkit.event.player.PlayerResourcePackStatusEvent.Status.SUCCESSFULLY_LOADED; ++ } ++ ++ public void setResourcePackStatus(org.bukkit.event.player.PlayerResourcePackStatusEvent.Status status) { ++ this.resourcePackStatus = status; ++ } + // Paper end + + @Override diff --git a/Remapped-Spigot-Server-Patches/0060-Chunk-Save-Reattempt.patch b/Remapped-Spigot-Server-Patches/0060-Chunk-Save-Reattempt.patch new file mode 100644 index 000000000..db35b1446 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0060-Chunk-Save-Reattempt.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 Mar 2013 23:46:10 -0500 +Subject: [PATCH] Chunk Save Reattempt + +We commonly have "Stream Closed" errors on chunk saving, so this code should re-try to save the chunk in the event of failure and hopefully prevent rollbacks. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 1598da3449ee1c559cf503e1b20a0daaf6a033dd..1aa4d342b97f8be71c108194a6f1e0e2828aa364 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -265,7 +265,7 @@ public class RegionFile implements AutoCloseable { + return true; + } + } catch (IOException ioexception) { +- com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioexception); // Paper - we want the upper try/catch to retry this + return false; + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 1e49d17b54704e1b99c3ded458c4bc6842bd32bd..97a58da9d64d812942ceb71426d35b490bbbe817 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -11,6 +11,7 @@ import java.io.IOException; + import javax.annotation.Nullable; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtIo; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionCollector; + import net.minecraft.world.level.ChunkPos; + +@@ -92,6 +93,7 @@ public final class RegionFileStorage implements AutoCloseable { + + protected void write(ChunkPos pos, CompoundTag tag) throws IOException { + RegionFile regionfile = this.getFile(pos, false); // CraftBukkit ++ int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper + DataOutputStream dataoutputstream = regionfile.getChunkDataOutputStream(pos); + Throwable throwable = null; + +@@ -115,6 +117,18 @@ public final class RegionFileStorage implements AutoCloseable { + + } + ++ // Paper start ++ return; ++ } catch (Exception ex) { ++ laste = ex; ++ } ++ } ++ ++ if (laste != null) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste); ++ MinecraftServer.LOGGER.error("Failed to save chunk", laste); ++ } ++ // Paper end + } + + public void close() throws IOException { diff --git a/Remapped-Spigot-Server-Patches/0061-Default-loading-permissions.yml-before-plugins.patch b/Remapped-Spigot-Server-Patches/0061-Default-loading-permissions.yml-before-plugins.patch new file mode 100644 index 000000000..3df2a1e2a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0061-Default-loading-permissions.yml-before-plugins.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 13:17:38 -0400 +Subject: [PATCH] Default loading permissions.yml before plugins + +Under previous behavior, plugins were not able to check if a player had a permission +if it was defined in permissions.yml. there is no clean way for a plugin to fix that either. + +This will change the order so that by default, permissions.yml loads BEFORE plugins instead of after. + +This gives plugins expected permission checks. + +It also helps improve the expected logic, as servers should set the initial defaults, and then let plugins +modify that. Under the previous logic, plugins were unable (cleanly) override permissions.yml. + +A config option has been added for those who depend on the previous behavior, but I don't expect that. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 429b74474ced04d8dd8f038b8590b8dfe178bf4d..716f285e67019b8a62922d09c15883c99f9421aa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -222,4 +222,9 @@ public class PaperConfig { + private static void useDisplayNameInQuit() { + useDisplayNameInQuit = getBoolean("use-display-name-in-quit-message", useDisplayNameInQuit); + } ++ ++ public static boolean loadPermsBeforePlugins = true; ++ private static void loadPermsBeforePlugins() { ++ loadPermsBeforePlugins = getBoolean("settings.load-permissions-yml-before-plugins", true); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 35d3df7ded4904414a9a61895950b56be530d244..662fc88e2e118a57a6c35a8981d4622188adec3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -397,6 +397,7 @@ public final class CraftServer implements Server { + if (type == PluginLoadOrder.STARTUP) { + helpMap.clear(); + helpMap.initializeGeneralTopics(); ++ if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper + } + + Plugin[] plugins = pluginManager.getPlugins(); +@@ -416,7 +417,7 @@ public final class CraftServer implements Server { + commandMap.registerServerAliases(); + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); +- loadCustomPermissions(); ++ if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper + helpMap.initializeCommands(); + syncCommands(); + } diff --git a/Remapped-Spigot-Server-Patches/0062-Allow-Reloading-of-Custom-Permissions.patch b/Remapped-Spigot-Server-Patches/0062-Allow-Reloading-of-Custom-Permissions.patch new file mode 100644 index 000000000..e43351be6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0062-Allow-Reloading-of-Custom-Permissions.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William +Date: Fri, 18 Mar 2016 03:30:17 -0400 +Subject: [PATCH] Allow Reloading of Custom Permissions + +https://github.com/PaperMC/Paper/issues/49 + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 662fc88e2e118a57a6c35a8981d4622188adec3b..50da8e292c131176c263f0bc140ff4f6d890c737 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2252,5 +2252,23 @@ public final class CraftServer implements Server { + } + return this.adventure$audiences; + } ++ ++ @Override ++ public void reloadPermissions() { ++ pluginManager.clearPermissions(); ++ if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); ++ for (Plugin plugin : pluginManager.getPlugins()) { ++ for (Permission perm : plugin.getDescription().getPermissions()) { ++ try { ++ pluginManager.addPermission(perm); ++ } catch (IllegalArgumentException ex) { ++ getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); ++ } ++ } ++ } ++ if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); ++ DefaultPermissions.registerCorePermissions(); ++ CraftDefaultPermissions.registerCorePermissions(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0063-Remove-Metadata-on-reload.patch b/Remapped-Spigot-Server-Patches/0063-Remove-Metadata-on-reload.patch new file mode 100644 index 000000000..bad86d108 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0063-Remove-Metadata-on-reload.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 13:50:14 -0400 +Subject: [PATCH] Remove Metadata on reload + +Metadata is not meant to persist reload as things break badly with non primitive types +This will remove metadata on reload so it does not crash everything if a plugin uses it. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 50da8e292c131176c263f0bc140ff4f6d890c737..2828936fe294d9d6750a8838da49ec8398835214 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -870,8 +870,18 @@ public final class CraftServer implements Server { + world.paperConfig.init(); // Paper + } + ++ 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(); + reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot diff --git a/Remapped-Spigot-Server-Patches/0064-Handle-Item-Meta-Inconsistencies.patch b/Remapped-Spigot-Server-Patches/0064-Handle-Item-Meta-Inconsistencies.patch new file mode 100644 index 000000000..32698665b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0064-Handle-Item-Meta-Inconsistencies.patch @@ -0,0 +1,339 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 May 2015 23:00:19 -0400 +Subject: [PATCH] Handle Item Meta Inconsistencies + +First, Enchantment order would blow away seeing 2 items as the same, +however the Client forces enchantment list in a certain order, as well +as does the /enchant command. Anvils can insert it into forced order, +causing 2 same items to be considered different. + +This change makes unhandled NBT Tags and Enchantments use a sorted tree map, +so they will always be in a consistent order. + +Additionally, the old enchantment API was never updated when ItemMeta +was added, resulting in 2 different ways to modify an items enchantments. + +For consistency, the old API methods now forward to use the +ItemMeta API equivalents, and should deprecate the old API's. + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 3faf52e7ac5e7e22d09cfb73cfda6b9f622137d4..123025c6dc9a2eea56c7db5cb508cdfd7c6cc97b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -9,6 +9,8 @@ import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; + import java.text.DecimalFormat; + import java.text.DecimalFormatSymbols; ++import java.util.Collections; ++import java.util.Comparator; + import java.util.Locale; + import java.util.Objects; + import java.util.Optional; +@@ -118,6 +120,23 @@ public final class ItemStack { + private BlockInWorld cachedPlaceBlock; + private boolean cachedPlaceBlockResult; + ++ // Paper start ++ private static final java.util.Comparator enchantSorter = java.util.Comparator.comparing(o -> o.getString("id")); ++ private void processEnchantOrder(CompoundTag tag) { ++ if (tag == null || !tag.contains("Enchantments", 9)) { ++ return; ++ } ++ ListTag list = tag.getList("Enchantments", 10); ++ if (list.size() < 2) { ++ return; ++ } ++ try { ++ //noinspection unchecked ++ list.sort((Comparator) enchantSorter); // Paper ++ } catch (Exception ignored) {} ++ } ++ // Paper end ++ + public ItemStack(ItemLike item) { + this(item, 1); + } +@@ -160,6 +179,7 @@ public final class ItemStack { + if (nbttagcompound.contains("tag", 10)) { + // CraftBukkit start - make defensive copy as this data may be coming from the save thread + this.tag = (CompoundTag) nbttagcompound.getCompound("tag").copy(); ++ processEnchantOrder(this.tag); // Paper + this.getItem().verifyTagAfterLoad(this.tag); + // CraftBukkit end + } +@@ -678,6 +698,7 @@ public final class ItemStack { + // Paper end + public void setTag(@Nullable CompoundTag tag) { + this.tag = tag; ++ processEnchantOrder(this.tag); // Paper + if (this.getItem().canBeDepleted()) { + this.setDamageValue(this.getDamageValue()); + } +@@ -768,6 +789,7 @@ public final class ItemStack { + nbttagcompound.putString("id", String.valueOf(Registry.ENCHANTMENT.getKey(enchantment))); + nbttagcompound.putShort("lvl", (short) ((byte) level)); + nbttaglist.add(nbttagcompound); ++ processEnchantOrder(nbttagcompound); // Paper + } + + public boolean isEnchanted() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 01df5263d77771a296ca091a0feec620e6e37229..5f0ccdeb8565505278caa591f7390047eab49cf4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -6,7 +6,6 @@ import java.util.Map; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.world.item.Item; +-import net.minecraft.world.item.enchantment.EnchantmentHelper; + import org.apache.commons.lang.Validate; + import org.bukkit.Material; + import org.bukkit.configuration.serialization.DelegateDeserialization; +@@ -178,28 +177,11 @@ public final class CraftItemStack extends ItemStack { + public void addUnsafeEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Cannot add null enchantment"); + +- if (!makeTag(handle)) { +- return; +- } +- ListTag list = getEnchantmentList(handle); +- if (list == null) { +- list = new ListTag(); +- handle.getTag().put(ENCHANTMENTS.NBT, list); +- } +- int size = list.size(); +- +- for (int i = 0; i < size; i++) { +- CompoundTag tag = (CompoundTag) list.get(i); +- String id = tag.getString(ENCHANTMENTS_ID.NBT); +- if (id.equals(ench.getKey().toString())) { +- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); +- return; +- } +- } +- CompoundTag tag = new CompoundTag(); +- tag.putString(ENCHANTMENTS_ID.NBT, ench.getKey().toString()); +- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); +- list.add(tag); ++ // Paper start - Replace whole method ++ final ItemMeta itemMeta = getItemMeta(); ++ itemMeta.addEnchant(ench, level, true); ++ setItemMeta(itemMeta); ++ // Paper end + } + + static boolean makeTag(net.minecraft.world.item.ItemStack item) { +@@ -216,66 +198,33 @@ public final class CraftItemStack extends ItemStack { + + @Override + public boolean containsEnchantment(Enchantment ench) { +- return getEnchantmentLevel(ench) > 0; ++ return hasItemMeta() && getItemMeta().hasEnchant(ench); // Paper - use meta + } + + @Override + public int getEnchantmentLevel(Enchantment ench) { +- Validate.notNull(ench, "Cannot find null enchantment"); +- if (handle == null) { +- return 0; +- } +- return EnchantmentHelper.getItemEnchantmentLevel(CraftEnchantment.getRaw(ench), handle); ++ return hasItemMeta() ? getItemMeta().getEnchantLevel(ench) : 0; // Paper - replace entire method with meta + } + + @Override + public int removeEnchantment(Enchantment ench) { + Validate.notNull(ench, "Cannot remove null enchantment"); + +- ListTag list = getEnchantmentList(handle), listCopy; +- if (list == null) { +- return 0; +- } +- int index = Integer.MIN_VALUE; +- int level = Integer.MIN_VALUE; +- int size = list.size(); +- +- for (int i = 0; i < size; i++) { +- CompoundTag enchantment = (CompoundTag) list.get(i); +- String id = enchantment.getString(ENCHANTMENTS_ID.NBT); +- if (id.equals(ench.getKey().toString())) { +- index = i; +- level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); +- break; +- } +- } +- +- if (index == Integer.MIN_VALUE) { +- return 0; +- } +- if (size == 1) { +- handle.getTag().remove(ENCHANTMENTS.NBT); +- if (handle.getTag().isEmpty()) { +- handle.setTag(null); +- } +- return level; +- } +- +- // This is workaround for not having an index removal +- listCopy = new ListTag(); +- for (int i = 0; i < size; i++) { +- if (i != index) { +- listCopy.add(list.get(i)); +- } ++ // Paper start - replace entire method ++ final ItemMeta itemMeta = getItemMeta(); ++ int level = itemMeta.getEnchantLevel(ench); ++ if (level > 0) { ++ itemMeta.removeEnchant(ench); ++ setItemMeta(itemMeta); + } +- handle.getTag().put(ENCHANTMENTS.NBT, listCopy); ++ // Paper end + + return level; + } + + @Override + public Map getEnchantments() { +- return getEnchantments(handle); ++ return hasItemMeta() ? getItemMeta().getEnchants() : ImmutableMap.of(); // Paper - use Item Meta + } + + static Map getEnchantments(net.minecraft.world.item.ItemStack item) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index cca04daf84e506382365c0ba945cb024bd4d4475..521699615778c4b724d10edfee1d3915e036eb2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.ImmutableMultimap; + import com.google.common.collect.LinkedHashMultimap; ++import com.google.common.collect.ImmutableSortedMap; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Multimap; + import com.google.common.collect.SetMultimap; +@@ -22,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; ++import java.util.Comparator; // Paper + import java.util.EnumSet; + import java.util.HashMap; + import java.util.Iterator; +@@ -32,6 +34,7 @@ import java.util.Map; + import java.util.NoSuchElementException; + import java.util.Objects; + import java.util.Set; ++import java.util.TreeMap; // Paper + import java.util.logging.Level; + import java.util.logging.Logger; + import javax.annotation.Nonnull; +@@ -270,7 +273,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private List lore; // null and empty are two different states internally + private Integer customModelData; + private CompoundTag blockData; +- private Map enchantments; ++ private EnchantmentMap enchantments; // Paper + private Multimap attributeModifiers; + private int repairCost; + private int hideFlag; +@@ -281,7 +284,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + + private CompoundTag internalTag; +- private final Map unhandledTags = new HashMap(); ++ private final Map unhandledTags = new TreeMap<>(); // Paper + private CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); + + private int version = CraftMagicNumbers.INSTANCE.getDataVersion(); // Internal use only +@@ -302,7 +305,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.blockData = meta.blockData; + + if (meta.enchantments != null) { // Spigot +- this.enchantments = new LinkedHashMap(meta.enchantments); ++ this.enchantments = new EnchantmentMap(meta.enchantments); // Paper + } + + if (meta.hasAttributeModifiers()) { +@@ -385,13 +388,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static Map buildEnchantments(CompoundTag tag, ItemMetaKey key) { ++ static EnchantmentMap buildEnchantments(CompoundTag tag, ItemMetaKey key) { // Paper + if (!tag.contains(key.NBT)) { + return null; + } + + ListTag ench = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); +- Map enchantments = new LinkedHashMap(ench.size()); ++ EnchantmentMap enchantments = new EnchantmentMap(); // Paper + + for (int i = 0; i < ench.size(); i++) { + String id = ((CompoundTag) ench.get(i)).getString(ENCHANTMENTS_ID.NBT); +@@ -544,13 +547,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static Map buildEnchantments(Map map, ItemMetaKey key) { ++ static EnchantmentMap buildEnchantments(Map map, ItemMetaKey key) { // Paper + Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); + if (ench == null) { + return null; + } + +- Map enchantments = new LinkedHashMap(ench.size()); ++ EnchantmentMap enchantments = new EnchantmentMap(); // Paper + for (Map.Entry entry : ench.entrySet()) { + // Doctor older enchants + String enchantKey = entry.getKey().toString(); +@@ -826,14 +829,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public Map getEnchants() { +- return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.of(); ++ return hasEnchants() ? ImmutableSortedMap.copyOfSorted(enchantments) : ImmutableMap.of(); // Paper + } + + @Override + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + Validate.notNull(ench, "Enchantment cannot be null"); + if (enchantments == null) { +- enchantments = new LinkedHashMap(4); ++ enchantments = new EnchantmentMap(); // Paper + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { +@@ -1214,7 +1217,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.customModelData = this.customModelData; + clone.blockData = this.blockData; + if (this.enchantments != null) { +- clone.enchantments = new LinkedHashMap(this.enchantments); ++ clone.enchantments = new EnchantmentMap(this.enchantments); // Paper + } + if (this.hasAttributeModifiers()) { + clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); +@@ -1446,4 +1449,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return HANDLED_TAGS; + } + } ++ ++ // Paper start ++ private static class EnchantmentMap extends TreeMap { ++ private EnchantmentMap(Map enchantments) { ++ this(); ++ putAll(enchantments); ++ } ++ ++ private EnchantmentMap() { ++ super(Comparator.comparing(o -> o.getKey().toString())); ++ } ++ ++ public EnchantmentMap clone() { ++ return (EnchantmentMap) super.clone(); ++ } ++ } ++ // Paper end ++ + } diff --git a/Remapped-Spigot-Server-Patches/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch b/Remapped-Spigot-Server-Patches/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch new file mode 100644 index 000000000..a940748e3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 15:12:22 -0400 +Subject: [PATCH] Configurable Non Player Arrow Despawn Rate + +Can set a much shorter despawn rate for arrows that players can not pick up. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3ac2ac3db9b1c271b3c21930bb13716669ff64d3..3c78d3234054ce2dc46ef77decb6adb0cbd10620 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -215,4 +215,19 @@ public class PaperWorldConfig { + private void nonPlayerEntitiesOnScoreboards() { + nonPlayerEntitiesOnScoreboards = getBoolean("allow-non-player-entities-on-scoreboards", false); + } ++ ++ public int nonPlayerArrowDespawnRate = -1; ++ public int creativeArrowDespawnRate = -1; ++ private void nonPlayerArrowDespawnRate() { ++ nonPlayerArrowDespawnRate = getInt("non-player-arrow-despawn-rate", -1); ++ if (nonPlayerArrowDespawnRate == -1) { ++ nonPlayerArrowDespawnRate = spigotConfig.arrowDespawnRate; ++ } ++ creativeArrowDespawnRate = getInt("creative-arrow-despawn-rate", -1); ++ if (creativeArrowDespawnRate == -1) { ++ creativeArrowDespawnRate = spigotConfig.arrowDespawnRate; ++ } ++ log("Non Player Arrow Despawn Rate: " + nonPlayerArrowDespawnRate); ++ log("Creative Arrow Despawn Rate: " + creativeArrowDespawnRate); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 371fdcbf1f9c01f6a356393f6c3767511f230930..0dc5792d542658107c9c22c1f920986decd13920 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -281,7 +281,7 @@ public abstract class AbstractArrow extends Projectile { + + protected void tickDespawn() { + ++this.life; +- if (this.life >= ((this instanceof ThrownTrident) ? level.spigotConfig.tridentDespawnRate : level.spigotConfig.arrowDespawnRate)) { // Spigot ++ if (this.life >= (pickup == Pickup.CREATIVE_ONLY ? level.paperConfig.creativeArrowDespawnRate : (pickup == Pickup.DISALLOWED ? level.paperConfig.nonPlayerArrowDespawnRate : ((this instanceof ThrownTrident) ? level.spigotConfig.tridentDespawnRate : level.spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - TODO: Extract this to init? + this.remove(); + } + diff --git a/Remapped-Spigot-Server-Patches/0066-Add-World-Util-Methods.patch b/Remapped-Spigot-Server-Patches/0066-Add-World-Util-Methods.patch new file mode 100644 index 000000000..83f43bb54 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0066-Add-World-Util-Methods.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 20:16:03 -0400 +Subject: [PATCH] Add World Util Methods + +Methods that can be used for other patches to help improve logic. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 89472b6e8f38921db50440d0213e40ac893892f1..e1f9a12c7fb4818a785b9a4819f94fccde02b6a2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -191,7 +191,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + +- public LevelChunk getChunkIfLoaded(int x, int z) { ++ @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkSource.getChunk(x, z, false); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 94e268a05b4601c29b6d2845f0fc2311643a161f..799721ac63f0c08dd03a788b87eafa9a8cc976cc 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -297,11 +297,27 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + @Override +- public FluidState getFluidIfLoaded(BlockPos blockposition) { ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getFluidState(blockposition); + } ++ ++ public final boolean isLoadedAndInBounds(BlockPos blockposition) { // Paper - final for inline ++ return getWorldBorder().isInBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; ++ } ++ ++ public LevelChunk getChunkIfLoaded(int x, int z) { // Overridden in WorldServer for ABI compat which has final ++ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ } ++ public final LevelChunk getChunkIfLoaded(BlockPos blockposition) { ++ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ // reduces need to do isLoaded before getType ++ public final BlockState getTypeIfLoadedAndInBounds(BlockPos blockposition) { ++ return getWorldBorder().isInBounds(blockposition) ? getTypeIfLoaded(blockposition) : null; ++ } + // Paper end + + @Override +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 31f17956b3b031d1a47bda4d282554c8a7853097..0846f649dca3422dbab3bb0a4826e27430cc8186 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -31,6 +31,7 @@ public class WorldBorder { + + public WorldBorder() {} + ++ public final boolean isInBounds(BlockPos blockposition) { return this.isWithinBounds(blockposition); } // Paper - OBFHELPER + public boolean isWithinBounds(BlockPos pos) { + return (double) (pos.getX() + 1) > this.getMinX() && (double) pos.getX() < this.getMaxX() && (double) (pos.getZ() + 1) > this.getMinZ() && (double) pos.getZ() < this.getMaxZ(); + } diff --git a/Remapped-Spigot-Server-Patches/0067-Custom-replacement-for-eaten-items.patch b/Remapped-Spigot-Server-Patches/0067-Custom-replacement-for-eaten-items.patch new file mode 100644 index 000000000..2e91f18d2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0067-Custom-replacement-for-eaten-items.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sun, 21 Jun 2015 15:07:20 -0400 +Subject: [PATCH] Custom replacement for eaten items + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d483d552092c901fec262c43e488784d9cd8acb9..3c707ca6b56e89b671db6316d4db90a2903f33b4 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3202,9 +3202,10 @@ public abstract class LivingEntity extends Entity { + this.triggerItemUseEffects(this.useItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; ++ PlayerItemConsumeEvent event = null; // Paper + if (this instanceof ServerPlayer) { + org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem); +- PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem); ++ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem); // Paper + level.getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { +@@ -3218,6 +3219,13 @@ public abstract class LivingEntity extends Entity { + } else { + itemstack = this.useItem.finishUsingItem(this.level, this); + } ++ ++ // Paper start - save the default replacement item and change it if necessary ++ final ItemStack defaultReplacement = itemstack; ++ if (event != null && event.getReplacement() != null) { ++ itemstack = CraftItemStack.asNMSCopy(event.getReplacement()); ++ } ++ // Paper end + // CraftBukkit end + + if (itemstack != this.useItem) { +@@ -3225,6 +3233,11 @@ public abstract class LivingEntity extends Entity { + } + + this.stopUsingItem(); ++ // Paper start - if the replacement is anything but the default, update the client inventory ++ if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ ((ServerPlayer) this).getBukkitEntity().updateInventory(); ++ } ++ // Paper end + } + + } diff --git a/Remapped-Spigot-Server-Patches/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/Remapped-Spigot-Server-Patches/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch new file mode 100644 index 000000000..0d5d5d19a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 27 Sep 2015 01:18:02 -0400 +Subject: [PATCH] handle NaN health/absorb values and repair bad data + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 3c707ca6b56e89b671db6316d4db90a2903f33b4..a326e5b4ac055f2f8a95c6eaccd8d0a97762da1f 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -699,7 +699,13 @@ public abstract class LivingEntity extends Entity { + + @Override + public void readAdditionalSaveData(CompoundTag tag) { +- this.setAbsorptionAmount(tag.getFloat("AbsorptionAmount")); ++ // Paper start - jvm keeps optimizing the setter ++ float absorptionAmount = tag.getFloat("AbsorptionAmount"); ++ if (Float.isNaN(absorptionAmount)) { ++ absorptionAmount = 0; ++ } ++ this.setAbsorptionAmount(absorptionAmount); ++ // Paper end + if (tag.contains("Attributes", 9) && this.level != null && !this.level.isClientSide) { + this.getAttributes().load(tag.getList("Attributes", 10)); + } +@@ -1148,6 +1154,10 @@ public abstract class LivingEntity extends Entity { + } + + public void setHealth(float health) { ++ // Paper start ++ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) { ++ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set"); ++ } } // Paper end + // CraftBukkit start - Handle scaled health + if (this instanceof ServerPlayer) { + org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity(); +@@ -3042,7 +3052,7 @@ public abstract class LivingEntity extends Entity { + } + + public void setAbsorptionAmount(float amount) { +- if (amount < 0.0F) { ++ if (amount < 0.0F || Float.isNaN(amount)) { // Paper + amount = 0.0F; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index dd29038778d73fae84df360515f3c670915f1d48..b7d5a718375083a4162df4bb41de3acd57b297fb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1678,6 +1678,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setRealHealth(double health) { ++ if (Double.isNaN(health)) {return;} // Paper + this.health = health; + } + diff --git a/Remapped-Spigot-Server-Patches/0069-Use-a-Shared-Random-for-Entities.patch b/Remapped-Spigot-Server-Patches/0069-Use-a-Shared-Random-for-Entities.patch new file mode 100644 index 000000000..ddca7a2d4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0069-Use-a-Shared-Random-for-Entities.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 22 Mar 2016 00:33:47 -0400 +Subject: [PATCH] Use a Shared Random for Entities + +Reduces memory usage and provides ensures more randomness, Especially since a lot of garbage entity objects get created. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 93d3408231a177cf6d2086594756adffe3efa702..61048140cf0adca03bfb57193ada0adaee73b1bb 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -142,6 +142,21 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } + ++ // Paper start ++ public static Random SHARED_RANDOM = new Random() { ++ private boolean locked = false; ++ @Override ++ public synchronized void setSeed(long seed) { ++ if (locked) { ++ LogManager.getLogger().error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); ++ } else { ++ super.setSeed(seed); ++ locked = true; ++ } ++ } ++ }; ++ // Paper end ++ + private CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { +@@ -271,7 +286,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.stuckSpeedMultiplier = Vec3.ZERO; + this.nextStep = 1.0F; + this.nextFlap = 1.0F; +- this.random = new Random(); ++ this.random = SHARED_RANDOM; // Paper + this.remainingFireTicks = -this.getFireImmuneTicks(); + this.fluidHeight = new Object2DoubleArrayMap(2); + this.firstTick = true; diff --git a/Remapped-Spigot-Server-Patches/0070-Configurable-spawn-chances-for-skeleton-horses.patch b/Remapped-Spigot-Server-Patches/0070-Configurable-spawn-chances-for-skeleton-horses.patch new file mode 100644 index 000000000..59b6c7f72 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0070-Configurable-spawn-chances-for-skeleton-horses.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 22 Mar 2016 12:04:28 -0500 +Subject: [PATCH] Configurable spawn chances for skeleton horses + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3c78d3234054ce2dc46ef77decb6adb0cbd10620..cd64fb9d0c6d123e1c86cb33f12cd9cefc9f80d0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -230,4 +230,12 @@ public class PaperWorldConfig { + log("Non Player Arrow Despawn Rate: " + nonPlayerArrowDespawnRate); + log("Creative Arrow Despawn Rate: " + creativeArrowDespawnRate); + } ++ ++ public double skeleHorseSpawnChance; ++ private void skeleHorseSpawnChance() { ++ skeleHorseSpawnChance = getDouble("skeleton-horse-thunder-spawn-chance", 0.01D); ++ if (skeleHorseSpawnChance < 0) { ++ skeleHorseSpawnChance = 0.01D; // Vanilla value ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index e1f9a12c7fb4818a785b9a4819f94fccde02b6a2..22c687e3db79bcfbc512ce3993d6e8a6db062360 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -584,7 +584,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + blockposition = this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); +- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D; ++ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance; // Paper + + if (flag1) { + SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create((net.minecraft.world.level.Level) this); diff --git a/Remapped-Spigot-Server-Patches/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch b/Remapped-Spigot-Server-Patches/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch new file mode 100644 index 000000000..2c0ed8f7c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch @@ -0,0 +1,206 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 02:07:55 -0600 +Subject: [PATCH] Optimize isValidLocation, getType and getBlockData for + inlining + +Hot methods, so reduce # of instructions for the method. + +Move is valid location test to the BlockPosition class so that it can access local variables. + +Replace all calls to the new place to the unnecessary forward. + +Optimize getType and getBlockData to manually inline and optimize the calls + +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index 3e79b274b8e0406a3cbdd94c7cec091b583109ca..c22de593be404c4e921724bba6a69c13759a95fd 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -22,6 +22,15 @@ public class Vec3i implements Comparable { + private int y;public final void setY(final int y) { this.y = y; } // Paper - OBFHELPER + private int z;public final void setZ(final int z) { this.z = z; } // Paper - OBFHELPER + ++ // Paper start ++ public boolean isValidLocation() { ++ return getX() >= -30000000 && getZ() >= -30000000 && getX() < 30000000 && getZ() < 30000000 && getY() >= 0 && getY() < 256; ++ } ++ public boolean isInvalidYLocation() { ++ return y < 0 || y >= 256; ++ } ++ // Paper end ++ + public Vec3i(int x, int y, int z) { + this.x = x; + this.y = y; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 799721ac63f0c08dd03a788b87eafa9a8cc976cc..24a6429059f58f51c97386ca2823ca0910288dec 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -239,7 +239,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + public static boolean isInWorldBounds(BlockPos pos) { +- return !isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos); ++ return pos.isValidLocation(); // Paper - use better/optimized check + } + + public static boolean isInSpawnableBounds(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index 3ca6289ba4952b5036367451b50cd90a78c0f938..e6303cdb433ee2b6782e2a0bd6b03e4f6ecb18ba 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -25,6 +25,7 @@ import org.apache.logging.log4j.LogManager; + + public interface ChunkAccess extends BlockGetter, FeatureAccess { + ++ BlockState getType(final int x, final int y, final int z); // Paper + @Nullable + BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +index a26de06252207cf333ea4a8d73f0af6ddc239103..e369730ac6909ff5343468bd685c9ea2b6b3cfed 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -23,7 +23,7 @@ import net.minecraft.world.phys.AABB; + + public class EmptyLevelChunk extends LevelChunk { + +- private static final Biome[] BIOMES = (Biome[]) Util.make((Object) (new Biome[ChunkBiomeContainer.BIOMES_SIZE]), (abiomebase) -> { ++ private static final Biome[] BIOMES = Util.make((new Biome[ChunkBiomeContainer.BIOMES_SIZE]), (abiomebase) -> { // Paper - decompile error + Arrays.fill(abiomebase, Biomes.PLAINS); + }); + +@@ -31,6 +31,11 @@ public class EmptyLevelChunk extends LevelChunk { + super(world, pos, new ChunkBiomeContainer(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), EmptyLevelChunk.BIOMES)); + } + ++ // Paper start ++ @Override public BlockState getType(int x, int y, int z) { ++ return Blocks.VOID_AIR.defaultBlockState(); ++ } ++ // Paper end + @Override + public BlockState getBlockState(BlockPos pos) { + return Blocks.VOID_AIR.defaultBlockState(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +index 04940ab2814cf39157d234dc4615646d7c760460..17fa8b23d1000ae53f2b4f1a6e8817c1005c1c81 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +@@ -42,6 +42,11 @@ public class ImposterProtoChunk extends ProtoChunk { + public BlockState getBlockState(BlockPos pos) { + return this.wrapped.getBlockState(pos); + } ++ // Paper start ++ public final BlockState getType(final int x, final int y, final int z) { ++ return this.wrapped.getBlockData(x, y, z); ++ } ++ // Paper end + + @Override + public FluidState getFluidState(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 9ca05aa06696883adc8b67a68ca6d2d850e95d25..546fb2f42e6bf333582b504d0a29991698505df3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -347,12 +347,27 @@ public class LevelChunk implements ChunkAccess { + return this.sections; + } + +- @Override ++ // Paper start - Optimize getBlockData to reduce instructions ++ public final BlockState getBlockData(BlockPos pos) { return getBlockData(pos.getX(), pos.getY(), pos.getZ()); } // Paper + public BlockState getBlockState(BlockPos pos) { +- int i = pos.getX(); +- int j = pos.getY(); +- int k = pos.getZ(); ++ return this.getBlockData(pos.getX(), pos.getY(), pos.getZ()); ++ } + ++ public BlockState getType(final int x, final int y, final int z) { ++ return getBlockData(x, y, z); ++ } ++ public final BlockState getBlockData(final int x, final int y, final int z) { ++ // Method body / logic copied from below ++ final int i = y >> 4; ++ if (y < 0 || i >= this.sections.length || this.sections[i] == null || this.sections[i].nonEmptyBlockCount == 0) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ // Inlined ChunkSection.getType() and DataPaletteBlock.a(int,int,int) ++ return this.sections[i].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15); ++ } ++ ++ public BlockState getBlockData_unused(int i, int j, int k) { ++ // Paper end + if (this.world.isDebug()) { + BlockState iblockdata = null; + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index b54d82e0f41a03c91e0de8df8249a91da3c04d0e..f5db97fb0dac78e1d9aa68d0417aa13f39914f52 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -13,10 +13,10 @@ public class LevelChunkSection { + + public static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState()); + private final int bottomBlockY; +- private short nonEmptyBlockCount; ++ short nonEmptyBlockCount; // Paper - package-private + private short tickingBlockCount; + private short tickingFluidCount; +- private final PalettedContainer states; ++ final PalettedContainer states; // Paper - package-private + + public LevelChunkSection(int yOffset) { + this(yOffset, (short) 0, (short) 0, (short) 0); +@@ -30,8 +30,8 @@ public class LevelChunkSection { + this.states = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState()); + } + +- public BlockState getBlockState(int x, int y, int z) { +- return (BlockState) this.states.get(x, y, z); ++ public final BlockState getBlockState(int x, int y, int z) { // Paper ++ return this.states.get(y << 8 | z << 4 | x); // Paper - inline + } + + public FluidState getFluidState(int x, int y, int z) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index d4db27421736f665739436c1ac4d3c6d5cae95cd..6d3dcd19ce1abc9d502903b8008949b5174a13c3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -133,7 +133,7 @@ public class PalettedContainer implements PaletteResize { + } + + public T get(int x, int y, int z) { +- return this.get(getIndex(x, y, z)); ++ return this.get(y << 8 | z << 4 | x); // Paper - inline + } + + protected T get(int index) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 7cd3f89004b0a64772fc3dfbdd132ba5a850b63e..d8b7b210484079c9ca2c34831c84102cba6692f5 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -113,16 +113,18 @@ public class ProtoChunk implements ChunkAccess { + + @Override + public BlockState getBlockState(BlockPos pos) { +- int i = pos.getY(); +- +- if (Level.isOutsideBuildHeight(i)) { ++ return getType(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ // Paper start ++ public BlockState getType(final int x, final int y, final int z) { ++ if (y < 0 || y >= 256) { + return Blocks.VOID_AIR.defaultBlockState(); + } else { +- LevelChunkSection chunksection = this.getSections()[i >> 4]; +- +- return LevelChunkSection.isEmpty(chunksection) ? Blocks.AIR.defaultBlockState() : chunksection.getBlockState(pos.getX() & 15, i & 15, pos.getZ() & 15); ++ LevelChunkSection chunksection = this.getSections()[y >> 4]; ++ return chunksection == LevelChunk.EMPTY_CHUNK_SECTION || chunksection.isEmpty() ? Blocks.AIR.defaultBlockState() : chunksection.getBlockState(x & 15, y & 15, z & 15); + } + } ++ // Paper end + + @Override + public FluidState getFluidState(BlockPos pos) { diff --git a/Remapped-Spigot-Server-Patches/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/Remapped-Spigot-Server-Patches/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch new file mode 100644 index 000000000..1bc2cb3b6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 19:55:45 -0400 +Subject: [PATCH] Only process BlockPhysicsEvent if a plugin has a listener + +Saves on some object allocation and processing when no plugin listens to this + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 901d5497667706c049718dc4fca37a1bc489c465..f7763a773bce4d8d947c8c859fe84d8a601034c5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1290,6 +1290,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 22c687e3db79bcfbc512ce3993d6e8a6db062360..8b0a384caa09848d61b3a6259dd56590cd52d0a0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -190,6 +190,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + private int tickPosition; + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; ++ public boolean hasPhysicsEvent = true; // Paper + + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkSource.getChunk(x, z, false); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 24a6429059f58f51c97386ca2823ca0910288dec..d47ed15382f98aabd509e32a3c202a91088adf6b 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -458,7 +458,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit start + iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam + CraftWorld world = ((ServerLevel) this).getWorld(); +- if (world != null) { ++ if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getCraftServer().getPluginManager().callEvent(event); + +@@ -560,7 +560,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + try { + // CraftBukkit start + CraftWorld world = ((ServerLevel) this).getWorld(); +- if (world != null) { ++ if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(sourcePos.getX(), sourcePos.getY(), sourcePos.getZ()), CraftBlockData.fromData(iblockdata), world.getBlockAt(neighborPos.getX(), neighborPos.getY(), neighborPos.getZ())); + this.getCraftServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java +index d6cb341d4d8e20b77979a241dd2e4346455796d7..42635b6115187abeffb290ca040350fd97cf89f7 100644 +--- a/src/main/java/net/minecraft/world/level/block/BushBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block; + + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.LevelReader; +@@ -23,7 +24,7 @@ public class BushBlock extends Block { + public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor world, BlockPos pos, BlockPos posFrom) { + // CraftBukkit start + if (!state.canSurvive(world, pos)) { +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { ++ if (!(world instanceof ServerLevel && ((ServerLevel) world).hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { // Paper + return Blocks.AIR.defaultBlockState(); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +index db444689092f537dd736dc73c532bd540fadcf86..86c5025d1b21dc35782124eca66288c63626147a 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; +@@ -83,7 +84,7 @@ public class DoublePlantBlock extends BushBlock { + + protected static void preventCreativeDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { + // CraftBukkit start +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { ++ if (((ServerLevel)world).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { // Paper + return; + } + // CraftBukkit end diff --git a/Remapped-Spigot-Server-Patches/0073-Entity-AddTo-RemoveFrom-World-Events.patch b/Remapped-Spigot-Server-Patches/0073-Entity-AddTo-RemoveFrom-World-Events.patch new file mode 100644 index 000000000..0b9b2867e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0073-Entity-AddTo-RemoveFrom-World-Events.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:32:58 -0400 +Subject: [PATCH] Entity AddTo/RemoveFrom World Events + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 8b0a384caa09848d61b3a6259dd56590cd52d0a0..f7eddb39985072afeb79ec0cbfc084d7e84638e6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1208,7 +1208,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (entity instanceof Mob) { + this.navigations.remove(((Mob) entity).getNavigation()); + } +- ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + entity.valid = false; // CraftBukkit + } + +@@ -1246,6 +1246,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + entity.origin = entity.getBukkitEntity().getLocation(); + } + // Paper end ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + } + + } diff --git a/Remapped-Spigot-Server-Patches/0074-Configurable-Chunk-Inhabited-Time.patch b/Remapped-Spigot-Server-Patches/0074-Configurable-Chunk-Inhabited-Time.patch new file mode 100644 index 000000000..126102b49 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0074-Configurable-Chunk-Inhabited-Time.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:46:14 -0400 +Subject: [PATCH] Configurable Chunk Inhabited Time + +Vanilla stores how long a chunk has been active on a server, and dynamically scales some +aspects of vanilla gameplay to this factor. + +For people who want all chunks to be treated equally, you can chose a fixed value. + +This allows to fine-tune vanilla gameplay. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cd64fb9d0c6d123e1c86cb33f12cd9cefc9f80d0..74ba5dbb83c13ce1721619b755036a7864a1fb90 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -238,4 +238,14 @@ public class PaperWorldConfig { + skeleHorseSpawnChance = 0.01D; // Vanilla value + } + } ++ ++ public int fixedInhabitedTime; ++ private void fixedInhabitedTime() { ++ if (PaperConfig.version < 16) { ++ if (!config.getBoolean("world-settings.default.use-chunk-inhabited-timer", true)) config.set("world-settings.default.fixed-chunk-inhabited-time", 0); ++ if (!config.getBoolean("world-settings." + worldName + ".use-chunk-inhabited-timer", true)) config.set("world-settings." + worldName + ".fixed-chunk-inhabited-time", 0); ++ set("use-chunk-inhabited-timer", null); ++ } ++ fixedInhabitedTime = getInt("fixed-chunk-inhabited-time", -1); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 546fb2f42e6bf333582b504d0a29991698505df3..70f5b025c2b803df3de8a51cbcfafbe915866f42 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -1021,7 +1021,7 @@ public class LevelChunk implements ChunkAccess { + + @Override + public long getInhabitedTime() { +- return this.inhabitedTime; ++ return world.paperConfig.fixedInhabitedTime < 0 ? this.inhabitedTime : world.paperConfig.fixedInhabitedTime; // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0075-EntityPathfindEvent.patch b/Remapped-Spigot-Server-Patches/0075-EntityPathfindEvent.patch new file mode 100644 index 000000000..e784dacc5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0075-EntityPathfindEvent.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 21:22:26 -0400 +Subject: [PATCH] EntityPathfindEvent + +Fires when an Entity decides to start moving to a location. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +index 0af2c5dde41043a6fb2fcd07db96288c7f96e0c7..5e7e678c4469e34c7ae39656f547243fbcf1d0da 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +@@ -37,7 +37,7 @@ public class FlyingPathNavigation extends PathNavigation { + + @Override + public Path createPath(Entity entity, int distance) { +- return this.createPath(entity.blockPosition(), distance); ++ return this.a(entity.blockPosition(), entity, distance); // Paper - Forward target entity + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +index cd7cb7cbe55a36282de394efc95f4ba7cc6a75cf..01be1de9d9ca0a86d69b2e82693bd0fea61a969f 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +@@ -75,7 +75,7 @@ public class GroundPathNavigation extends PathNavigation { + + @Override + public Path createPath(Entity entity, int distance) { +- return this.createPath(entity.blockPosition(), distance); ++ return this.a(entity.blockPosition(), entity, distance); // Paper - Forward target entity + } + + private int getSurfaceY() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index 3cfd913e31236e35e7225ba19d292cacb8b4134a..ae8d430382b20ddd837c47e39515c7995f25312a 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -10,6 +10,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Position; + import net.minecraft.core.Vec3i; + import net.minecraft.network.protocol.game.DebugPackets; ++import net.minecraft.server.MCUtil; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.Mob; +@@ -28,7 +29,7 @@ import net.minecraft.world.phys.Vec3; + + public abstract class PathNavigation { + +- protected final Mob mob; ++ protected final Mob mob; public Entity getEntity() { return mob; } // Paper - OBFHELPER + protected final Level level; + @Nullable + protected Path path; +@@ -115,36 +116,63 @@ public abstract class PathNavigation { + + @Nullable + public Path createPath(BlockPos target, int distance) { +- return this.createPath(ImmutableSet.of(target), 8, false, distance); ++ // Paper start - add target parameter ++ return this.a(target, null, distance); ++ } ++ @Nullable public Path a(BlockPos blockposition, Entity target, int i) { ++ return this.a(ImmutableSet.of(blockposition), target, 8, false, i); ++ // Paper end + } + + @Nullable + public Path createPath(Entity entity, int distance) { +- return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, distance); ++ return this.a(ImmutableSet.of(entity.blockPosition()), entity, 16, true, distance); // Paper + } + + @Nullable ++ // Paper start - Add target + protected Path createPath(Set positions, int range, boolean flag, int distance) { +- if (positions.isEmpty()) { ++ return this.a(positions, null, range, flag, distance); ++ } ++ @Nullable protected Path a(Set set, Entity target, int i, boolean flag, int j) { ++ // Paper end ++ if (set.isEmpty()) { + return null; + } else if (this.mob.getY() < 0.0D) { + return null; + } else if (!this.canUpdatePath()) { + return null; +- } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) { ++ } else if (this.path != null && !this.path.isDone() && set.contains(this.targetPos)) { + return this.path; + } else { ++ // Paper start - Pathfind event ++ boolean copiedSet = false; ++ for (BlockPos possibleTarget : set) { ++ if (!new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), ++ MCUtil.toLocation(getEntity().level, possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) { ++ if (!copiedSet) { ++ copiedSet = true; ++ set = new java.util.HashSet<>(set); ++ } ++ // note: since we copy the set this remove call is safe, since we're iterating over the old copy ++ set.remove(possibleTarget); ++ if (set.isEmpty()) { ++ return null; ++ } ++ } ++ } ++ // Paper end + this.level.getProfiler().push("pathfind"); + float f = (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); + BlockPos blockposition = flag ? this.mob.blockPosition().above() : this.mob.blockPosition(); +- int k = (int) (f + (float) range); ++ int k = (int) (f + (float) i); + PathNavigationRegion chunkcache = new PathNavigationRegion(this.level, blockposition.offset(-k, -k, -k), blockposition.offset(k, k, k)); +- Path pathentity = this.pathFinder.findPath(chunkcache, this.mob, positions, f, distance, this.maxVisitedNodesMultiplier); ++ Path pathentity = this.pathFinder.findPath(chunkcache, this.mob, set, f, j, this.maxVisitedNodesMultiplier); + + this.level.getProfiler().pop(); + if (pathentity != null && pathentity.getTarget() != null) { + this.targetPos = pathentity.getTarget(); +- this.reachRange = distance; ++ this.reachRange = j; + this.resetStuckTimeout(); + } + diff --git a/Remapped-Spigot-Server-Patches/0076-Sanitise-RegionFileCache-and-make-configurable.patch b/Remapped-Spigot-Server-Patches/0076-Sanitise-RegionFileCache-and-make-configurable.patch new file mode 100644 index 000000000..95fc3262c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0076-Sanitise-RegionFileCache-and-make-configurable.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Antony Riley +Date: Tue, 29 Mar 2016 08:22:55 +0300 +Subject: [PATCH] Sanitise RegionFileCache and make configurable. + +RegionFileCache prior to this patch would close every single open region +file upon reaching a size of 256. +This patch modifies that behaviour so it closes the the least recently +used RegionFile. +The implementation uses a LinkedHashMap as an LRU cache (modified from HashMap). +The maximum size of the RegionFileCache is also made configurable. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 716f285e67019b8a62922d09c15883c99f9421aa..439dcc6effdc91830d2b7ede9063982998b37120 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -227,4 +227,9 @@ public class PaperConfig { + private static void loadPermsBeforePlugins() { + loadPermsBeforePlugins = getBoolean("settings.load-permissions-yml-before-plugins", true); + } ++ ++ public static int regionFileCacheSize = 256; ++ private static void regionFileCacheSize() { ++ regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 256), 4); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 97a58da9d64d812942ceb71426d35b490bbbe817..f33a5fc725d1d5e895f8878d82ebc4172237ad29 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -33,7 +33,7 @@ public final class RegionFileStorage implements AutoCloseable { + if (regionfile != null) { + return regionfile; + } else { +- if (this.regionCache.size() >= 256) { ++ if (this.regionCache.size() >= com.destroystokyo.paper.PaperConfig.regionFileCacheSize) { // Paper - configurable + ((RegionFile) this.regionCache.removeLast()).close(); + } + diff --git a/Remapped-Spigot-Server-Patches/0077-Do-not-load-chunks-for-Pathfinding.patch b/Remapped-Spigot-Server-Patches/0077-Do-not-load-chunks-for-Pathfinding.patch new file mode 100644 index 000000000..dcc5c72d1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0077-Do-not-load-chunks-for-Pathfinding.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 31 Mar 2016 19:17:58 -0400 +Subject: [PATCH] Do not load chunks for Pathfinding + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index ae8d430382b20ddd837c47e39515c7995f25312a..25bc3adfad956157cef0953e6e632b7b7e352f3a 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -48,7 +48,7 @@ public abstract class PathNavigation { + private BlockPos targetPos; + private int reachRange; + private float maxVisitedNodesMultiplier; +- private final PathFinder pathFinder; ++ private final PathFinder pathFinder; public PathFinder getPathfinder() { return this.pathFinder; } // Paper - OBFHELPER + private boolean isStuck; + + public PathNavigation(Mob mob, Level world) { +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +index 99f3f0b895295229b75d93e98141c0cd75789b69..ba8ee93032aabe7ec4ecf52d452e1a580d6ebc20 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -20,7 +20,7 @@ public class PathFinder { + + private final Node[] neighbors = new Node[32]; + private final int maxVisitedNodes; +- private final NodeEvaluator nodeEvaluator; ++ private final NodeEvaluator nodeEvaluator; public NodeEvaluator getPathfinder() { return this.nodeEvaluator; } // Paper - OBFHELPER + private final BinaryHeap openSet = new BinaryHeap(); + + public PathFinder(NodeEvaluator pathNodeMaker, int range) { +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index 0b378348cb9e9576e2a209e651264e2caccfd182..7ae24381b91c282745b7fe5f6897865e74bc0acf 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -479,7 +479,12 @@ public class WalkNodeEvaluator extends NodeEvaluator { + for (int j1 = -1; j1 <= 1; ++j1) { + if (l != 0 || j1 != 0) { + blockposition_mutableblockposition.set(i + l, j + i1, k + j1); +- BlockState iblockdata = iblockaccess.getBlockState(blockposition_mutableblockposition); ++ // Paper start ++ BlockState iblockdata = iblockaccess.getTypeIfLoaded(blockposition_mutableblockposition); ++ if (iblockdata == null) { ++ pathtype = BlockPathTypes.BLOCKED; ++ } else { ++ // Paper end + + if (iblockdata.is(Blocks.CACTUS)) { + return BlockPathTypes.DANGER_CACTUS; +@@ -496,6 +501,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + if (iblockaccess.getFluidState(blockposition_mutableblockposition).is((Tag) FluidTags.WATER)) { + return BlockPathTypes.WATER_BORDER; + } ++ } // Paper + } + } + } +@@ -505,7 +511,8 @@ public class WalkNodeEvaluator extends NodeEvaluator { + } + + protected static BlockPathTypes getBlockPathTypeRaw(BlockGetter iblockaccess, BlockPos blockposition) { +- BlockState iblockdata = iblockaccess.getBlockState(blockposition); ++ BlockState iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return BlockPathTypes.BLOCKED; // Paper + Block block = iblockdata.getBlock(); + Material material = iblockdata.getMaterial(); + diff --git a/Remapped-Spigot-Server-Patches/0078-Add-PlayerUseUnknownEntityEvent.patch b/Remapped-Spigot-Server-Patches/0078-Add-PlayerUseUnknownEntityEvent.patch new file mode 100644 index 000000000..c91bb4d9e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0078-Add-PlayerUseUnknownEntityEvent.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 2 Apr 2016 05:09:16 -0400 +Subject: [PATCH] Add PlayerUseUnknownEntityEvent + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java +index 9ff5b938f97da5ca1f13fd2bcbf3d13e8b8f760c..e1d219550006d22b0a8e949e820488c6ed96dc58 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java +@@ -11,7 +11,7 @@ import net.minecraft.world.phys.Vec3; + + public class ServerboundInteractPacket implements Packet { + +- private int entityId; ++ private int entityId; public int getEntityId() { return this.entityId; } // Paper - add accessor + private ServerboundInteractPacket.Action action; + private Vec3 location; + private InteractionHand hand; +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 73683ba59d0aff3a61f555b4ae15753e9e4e6141..e2bfe8e916c9e59af81627ea0ee449970527034d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2198,6 +2198,16 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + } + } ++ // Paper start - fire event ++ else { ++ this.craftServer.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerUseUnknownEntityEvent( ++ this.getPlayer(), ++ packet.getEntityId(), ++ packet.getAction() == ServerboundInteractPacket.Action.ATTACK, ++ packet.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND ++ )); ++ } ++ // Paper end + + } + diff --git a/Remapped-Spigot-Server-Patches/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch b/Remapped-Spigot-Server-Patches/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch new file mode 100644 index 000000000..d10d109cb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 2 Apr 2016 20:37:03 -0400 +Subject: [PATCH] Fix reducedDebugInfo not initialized on client + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a63babe123fad398b07685ec57cd88756435457c..aa440a6341a6d30aba8fd5f6bcd122bd5d8760cd 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -242,6 +242,7 @@ public abstract class PlayerList { + playerconnection.send(new ClientboundSetCarriedItemPacket(player.inventory.selected)); + playerconnection.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes())); + playerconnection.send(new ClientboundUpdateTagsPacket(this.server.getTags())); ++ playerconnection.send(new ClientboundEntityEventPacket(player, (byte) (worldserver1.getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23))); // Paper - fix this rule not being initialized on the client + this.sendPlayerPermissionLevel(player); + player.getStats().markAllDirty(); + player.getRecipeBook().sendInitialRecipeBook(player); diff --git a/Remapped-Spigot-Server-Patches/0080-Configurable-Grass-Spread-Tick-Rate.patch b/Remapped-Spigot-Server-Patches/0080-Configurable-Grass-Spread-Tick-Rate.patch new file mode 100644 index 000000000..8481cb94a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0080-Configurable-Grass-Spread-Tick-Rate.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 3 Apr 2016 16:28:17 -0400 +Subject: [PATCH] Configurable Grass Spread Tick Rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 74ba5dbb83c13ce1721619b755036a7864a1fb90..db2dddd12f54e6d15916c4cee623676541de37fb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -248,4 +248,10 @@ public class PaperWorldConfig { + } + fixedInhabitedTime = getInt("fixed-chunk-inhabited-time", -1); + } ++ ++ public int grassUpdateRate = 1; ++ private void grassUpdateRate() { ++ grassUpdateRate = Math.max(0, getInt("grass-spread-tick-rate", grassUpdateRate)); ++ log("Grass Spread Tick Rate: " + grassUpdateRate); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +index d54f097afc455a01486d7f7459b0cfc4ab4f3970..813a5b0598eca28aa173cd6e34bc16381f313604 100644 +--- a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import java.util.Random; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.tags.FluidTags; + import net.minecraft.tags.Tag; +@@ -41,6 +42,7 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock { + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { ++ if (this instanceof GrassBlock && world.paperConfig.grassUpdateRate != 1 && (world.paperConfig.grassUpdateRate < 1 || (MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig.grassUpdateRate != 0)) { return; } // Paper + if (!canBeGrass(state, (LevelReader) world, pos)) { + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { diff --git a/Remapped-Spigot-Server-Patches/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/Remapped-Spigot-Server-Patches/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch new file mode 100644 index 000000000..5d706f626 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 3 Apr 2016 17:48:50 -0400 +Subject: [PATCH] Fix Cancelling BlockPlaceEvent triggering physics + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index d47ed15382f98aabd509e32a3c202a91088adf6b..89a6a0b4235cfcc1d3ad68ff59a21fa60df4508f 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -518,6 +518,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public void setBlocksDirty(BlockPos pos, BlockState old, BlockState updated) {} + + public void updateNeighborsAt(BlockPos pos, Block block) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborChanged(pos.west(), block, pos); + this.neighborChanged(pos.east(), block, pos); + this.neighborChanged(pos.below(), block, pos); diff --git a/Remapped-Spigot-Server-Patches/0082-Optimize-DataBits.patch b/Remapped-Spigot-Server-Patches/0082-Optimize-DataBits.patch new file mode 100644 index 000000000..7b5057a4e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0082-Optimize-DataBits.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Apr 2016 21:38:58 -0400 +Subject: [PATCH] Optimize DataBits + +Remove Debug checks as these are super hot and causing noticeable hits + +Before: http://i.imgur.com/nQsMzAE.png +After: http://i.imgur.com/nJ46crB.png + +Optimize redundant converting of static fields into an unsigned long each call by precomputing it in ctor + +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index 97bde5f8402452e59b0da94edfe1b970cdb86748..dd84984f28484cf7129c294222696784e128221a 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -13,8 +13,8 @@ public class BitStorage { + private final long mask; + private final int size; + private final int valuesPerLong; +- private final int divideMul; +- private final int divideAdd; ++ private final int divideMul;private final long g_unsigned; // Paper - referenced in b(int) with 2 Integer.toUnsignedLong calls ++ private final int divideAdd;private final long h_unsigned; // Paper + private final int divideShift; + + public BitStorage(int elementBits, int size) { +@@ -29,8 +29,8 @@ public class BitStorage { + this.valuesPerLong = (char) (64 / elementBits); + int k = 3 * (this.valuesPerLong - 1); + +- this.divideMul = BitStorage.MAGIC[k + 0]; +- this.divideAdd = BitStorage.MAGIC[k + 1]; ++ this.divideMul = BitStorage.MAGIC[k + 0]; this.g_unsigned = Integer.toUnsignedLong(this.divideMul); // Paper ++ this.divideAdd = BitStorage.MAGIC[k + 1]; this.h_unsigned = Integer.toUnsignedLong(this.divideAdd); // Paper + this.divideShift = BitStorage.MAGIC[k + 2]; + int l = (size + this.valuesPerLong - 1) / this.valuesPerLong; + +@@ -47,15 +47,15 @@ public class BitStorage { + } + + private int cellIndex(int i) { +- long j = Integer.toUnsignedLong(this.divideMul); +- long k = Integer.toUnsignedLong(this.divideAdd); ++ //long j = Integer.toUnsignedLong(this.g); // Paper ++ //long k = Integer.toUnsignedLong(this.h); // Paper + +- return (int) ((long) i * j + k >> 32 >> this.divideShift); ++ return (int) ((long) i * this.g_unsigned + this.h_unsigned >> 32 >> this.divideShift); // Paper + } + +- public int getAndSet(int index, int value) { +- Validate.inclusiveBetween(0L, (long) (this.size - 1), (long) index); +- Validate.inclusiveBetween(0L, this.mask, (long) value); ++ public final int getAndSet(int index, int value) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper ++ //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper + int k = this.cellIndex(index); + long l = this.data[k]; + int i1 = (index - k * this.valuesPerLong) * this.bits; +@@ -65,9 +65,9 @@ public class BitStorage { + return j1; + } + +- public void set(int index, int value) { +- Validate.inclusiveBetween(0L, (long) (this.size - 1), (long) index); +- Validate.inclusiveBetween(0L, this.mask, (long) value); ++ public final void set(int index, int value) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper ++ //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper + int k = this.cellIndex(index); + long l = this.data[k]; + int i1 = (index - k * this.valuesPerLong) * this.bits; +@@ -75,8 +75,8 @@ public class BitStorage { + this.data[k] = l & ~(this.mask << i1) | ((long) value & this.mask) << i1; + } + +- public int get(int index) { +- Validate.inclusiveBetween(0L, (long) (this.size - 1), (long) index); ++ public final int get(int index) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper + int j = this.cellIndex(index); + long k = this.data[j]; + int l = (index - j * this.valuesPerLong) * this.bits; diff --git a/Remapped-Spigot-Server-Patches/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/Remapped-Spigot-Server-Patches/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch new file mode 100644 index 000000000..ac90c24b3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 6 Apr 2016 01:04:23 -0500 +Subject: [PATCH] Option to use vanilla per-world scoreboard coloring on names + +This change is basically a bandaid to fix CB's complete and utter lack +of support for vanilla scoreboard name modifications. + +In the future, finding a way to merge the vanilla expectations in with +bukkit's concept of a display name would be preferable. There was a PR +for this on CB at one point but I can't find it. We may need to do this +ourselves at some point in the future. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index db2dddd12f54e6d15916c4cee623676541de37fb..1942f5224aaebb18adb591d6f70a419cfc1a7bdd 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -254,4 +254,9 @@ public class PaperWorldConfig { + grassUpdateRate = Math.max(0, getInt("grass-spread-tick-rate", grassUpdateRate)); + log("Grass Spread Tick Rate: " + grassUpdateRate); + } ++ ++ public boolean useVanillaScoreboardColoring; ++ private void useVanillaScoreboardColoring() { ++ useVanillaScoreboardColoring = getBoolean("use-vanilla-world-scoreboard-name-coloring", false); ++ } + } +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +index a29b6aaafd529e56a83dd96c32211f21e4aad348..2039f83a718427d0969a1a2e2200f7922097449e 100644 +--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -16,7 +16,11 @@ import net.kyori.adventure.text.TextReplacementConfig; + import net.kyori.adventure.text.event.ClickEvent; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.scores.PlayerTeam; ++import net.minecraft.world.scores.Team; + import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.util.LazyPlayerSet; + import org.bukkit.craftbukkit.util.Waitable; +@@ -178,10 +182,22 @@ public final class ChatProcessor { + } + + private static String legacyDisplayName(final CraftPlayer player) { ++ if (((CraftWorld) player.getWorld()).getHandle().paperConfig.useVanillaScoreboardColoring) { ++ final ServerPlayer ep = player.getHandle(); ++ net.minecraft.network.chat.Component name = ep.getName(); ++ final Team team = ep.getTeam(); ++ if (team != null) { ++ name = team.getFormattedName(name); ++ } ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(PaperAdventure.asAdventure(name)) + ChatColor.RESET; ++ } + return player.getDisplayName(); + } + + private static Component displayName(final CraftPlayer player) { ++ if (((CraftWorld) player.getWorld()).getHandle().paperConfig.useVanillaScoreboardColoring) { ++ return PaperAdventure.asAdventure(PlayerTeam.formatNameForTeam(player.getHandle().getTeam(), player.getHandle().getName())); ++ } + return player.displayName(); + } + diff --git a/Remapped-Spigot-Server-Patches/0084-Workaround-for-setting-passengers-on-players.patch b/Remapped-Spigot-Server-Patches/0084-Workaround-for-setting-passengers-on-players.patch new file mode 100644 index 000000000..c2fdc7667 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0084-Workaround-for-setting-passengers-on-players.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 10 Apr 2016 03:23:32 -0500 +Subject: [PATCH] Workaround for setting passengers on players + +SPIGOT-1915 & GH-114 + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b7d5a718375083a4162df4bb41de3acd57b297fb..b264cbe5f91da9e31c5fd00ee285735a19aaad35 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -870,6 +870,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return true; + } + ++ // Paper start - Ugly workaround for SPIGOT-1915 & GH-114 ++ @Override ++ public boolean setPassenger(org.bukkit.entity.Entity passenger) { ++ boolean wasSet = super.setPassenger(passenger); ++ if (wasSet) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSetPassengersPacket(this.getHandle())); ++ } ++ return wasSet; ++ } ++ // Paper end ++ + @Override + public void setSneaking(boolean sneak) { + getHandle().setShiftKeyDown(sneak); diff --git a/Remapped-Spigot-Server-Patches/0085-Remove-unused-World-Tile-Entity-List.patch b/Remapped-Spigot-Server-Patches/0085-Remove-unused-World-Tile-Entity-List.patch new file mode 100644 index 000000000..d96a9f686 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0085-Remove-unused-World-Tile-Entity-List.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 00:25:28 -0400 +Subject: [PATCH] Remove unused World Tile Entity List + +Massive hit to performance and it is completely unnecessary. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index f7eddb39985072afeb79ec0cbfc084d7e84638e6..bb99d9fe5e274318d8480a6de2c45b0a57351f77 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1715,7 +1715,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + bufferedwriter.write(String.format("entities: %d\n", this.entitiesById.size())); +- bufferedwriter.write(String.format("block_entities: %d\n", this.blockEntityList.size())); ++ bufferedwriter.write(String.format("block_entities: %d\n", this.tickableBlockEntities.size())); // Paper - remove unused list + bufferedwriter.write(String.format("block_ticks: %d\n", this.getBlockTicks().size())); + bufferedwriter.write(String.format("fluid_ticks: %d\n", this.getLiquidTicks().size())); + bufferedwriter.write("distance_manager: " + playerchunkmap.getDistanceManager().getDebugStatus() + "\n"); +@@ -1854,7 +1854,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + private void dumpBlockEntities(Writer writer) throws IOException { + CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(writer); +- Iterator iterator = this.blockEntityList.iterator(); ++ Iterator iterator = this.tickableBlockEntities.iterator(); // Paper - remove unused list + + while (iterator.hasNext()) { + BlockEntity tileentity = (BlockEntity) iterator.next(); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 89a6a0b4235cfcc1d3ad68ff59a21fa60df4508f..8f0fec38b482465285057d3fd27d456cf036f2fd 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -91,7 +91,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public static final ResourceKey NETHER = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_nether")); + public static final ResourceKey END = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_end")); + private static final Direction[] DIRECTIONS = Direction.values(); +- public final List blockEntityList = Lists.newArrayList(); ++ //public final List tileEntityList = Lists.newArrayList(); // Paper - remove unused list + public final List tickableBlockEntities = Lists.newArrayList(); + protected final List pendingBlockEntities = Lists.newArrayList(); + protected final java.util.Set tileEntityListUnload = com.google.common.collect.Sets.newHashSet(); +@@ -683,9 +683,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + }, blockEntity::getBlockPos}); + } + +- boolean flag = this.blockEntityList.add(blockEntity); ++ boolean flag = true; // Paper - remove unused list + +- if (flag && blockEntity instanceof TickableBlockEntity) { ++ if (flag && blockEntity instanceof TickableBlockEntity && !this.tickableBlockEntities.contains(blockEntity)) { // Paper + this.tickableBlockEntities.add(blockEntity); + } + +@@ -721,7 +721,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + timings.tileEntityTick.startTiming(); // Spigot + if (!this.tileEntityListUnload.isEmpty()) { + this.tickableBlockEntities.removeAll(this.tileEntityListUnload); +- this.blockEntityList.removeAll(this.tileEntityListUnload); ++ //this.tileEntityList.removeAll(this.tileEntityListUnload); // Paper - remove unused list + this.tileEntityListUnload.clear(); + } + +@@ -781,7 +781,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + tilesThisCycle--; + this.tickableBlockEntities.remove(tileTickPosition--); + // Spigot end +- this.blockEntityList.remove(tileentity); ++ //this.tileEntityList.remove(tileentity); // Paper - remove unused list + if (this.hasChunkAt(tileentity.getBlockPos())) { + this.getChunkAt(tileentity.getBlockPos()).removeBlockEntity(tileentity.getBlockPos()); + } +@@ -811,7 +811,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.sendBlockUpdated(tileentity1.getBlockPos(), iblockdata, iblockdata, 3); + // CraftBukkit start + // From above, don't screw this up - SPIGOT-1746 +- if (!this.blockEntityList.contains(tileentity1)) { ++ if (true) { // Paper - remove unused list + this.addBlockEntity(tileentity1); + } + // CraftBukkit end +@@ -957,7 +957,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } else { + if (tileentity != null) { + this.pendingBlockEntities.remove(tileentity); +- this.blockEntityList.remove(tileentity); ++ //this.tileEntityList.remove(tileentity); // Paper - remove unused list + this.tickableBlockEntities.remove(tileentity); + } + diff --git a/Remapped-Spigot-Server-Patches/0086-Don-t-tick-Skulls-unused-code.patch b/Remapped-Spigot-Server-Patches/0086-Don-t-tick-Skulls-unused-code.patch new file mode 100644 index 000000000..42e534b70 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0086-Don-t-tick-Skulls-unused-code.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 00:30:10 -0400 +Subject: [PATCH] Don't tick Skulls - unused code + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +index 6a46517e4026971d8c050c685c710883b5976fa3..eebaeaccc3ba1a9ec089d84b8de6c9d36034868f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +@@ -31,7 +31,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.server.players.GameProfileCache; + import net.minecraft.util.StringUtil; + +-public class SkullBlockEntity extends BlockEntity implements TickableBlockEntity { ++public class SkullBlockEntity extends BlockEntity /*implements ITickable*/ { // Paper - remove tickable + + @Nullable + private static GameProfileCache profileCache; +@@ -134,7 +134,7 @@ public class SkullBlockEntity extends BlockEntity implements TickableBlockEntity + + } + +- @Override ++ // Paper - remove override + public void tick() { + BlockState iblockdata = this.getBlockState(); + diff --git a/Remapped-Spigot-Server-Patches/0087-Configurable-Player-Collision.patch b/Remapped-Spigot-Server-Patches/0087-Configurable-Player-Collision.patch new file mode 100644 index 000000000..3ed665914 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0087-Configurable-Player-Collision.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 02:10:49 -0400 +Subject: [PATCH] Configurable Player Collision + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 439dcc6effdc91830d2b7ede9063982998b37120..504efea7b6f50a0d17f4f353781953dfb18bdeca 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -232,4 +232,9 @@ public class PaperConfig { + private static void regionFileCacheSize() { + regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 256), 4); + } ++ ++ public static boolean enablePlayerCollisions = true; ++ private static void enablePlayerCollisions() { ++ enablePlayerCollisions = getBoolean("settings.enable-player-collisions", true); ++ } + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +index 53f284b720d97ba8ce8fac90bc26e7930dcec6b2..d70e7079ea2c84edbc2a8501f115194e2a4ef2e4 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +@@ -112,7 +112,7 @@ public class ClientboundSetPlayerTeamPacket implements Packet toRemove = scoreboard.getTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(PlayerTeam::getName).collect(java.util.stream.Collectors.toList()); ++ for (String teamName : toRemove) { ++ scoreboard.removeTeam(scoreboard.getTeam(teamName)); // Clean up after ourselves ++ } ++ ++ if (!com.destroystokyo.paper.PaperConfig.enablePlayerCollisions) { ++ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16); ++ PlayerTeam collideTeam = scoreboard.createTeam(this.getPlayerList().collideRuleTeamName); ++ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all ++ } ++ // Paper end ++ + this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); + this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); + this.connection.acceptConnections(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index aa440a6341a6d30aba8fd5f6bcd122bd5d8760cd..59fb19cfebe4f488fd02f02db31029d44b65e408 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -79,6 +79,7 @@ import net.minecraft.world.level.storage.PlayerDataStorage; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.Objective; + import net.minecraft.world.scores.PlayerTeam; ++import net.minecraft.world.scores.Scoreboard; + import net.minecraft.world.scores.Team; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -141,6 +142,7 @@ public abstract class PlayerList { + // CraftBukkit start + private CraftServer cserver; + private final Map playersByName = new java.util.HashMap<>(); ++ public @Nullable String collideRuleTeamName; // Paper - Team name used for collideRule + + public PlayerList(MinecraftServer server, RegistryAccess.RegistryHolder registryManager, PlayerDataStorage saveHandler, int maxPlayers) { + this.cserver = server.server = new CraftServer((DedicatedServer) server, this); +@@ -372,6 +374,13 @@ public abstract class PlayerList { + } + + player.initMenu(); ++ // Paper start - Add to collideRule team if needed ++ final Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam collideRuleTeam = scoreboard.getTeam(collideRuleTeamName); ++ if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { ++ scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); ++ } ++ // Paper end + // CraftBukkit - Moved from above, added world + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.worldDataServer.getLevelName(), player.getX(), player.getY(), player.getZ()); + } +@@ -492,6 +501,16 @@ public abstract class PlayerList { + entityplayer.doTick(); // SPIGOT-924 + // CraftBukkit end + ++ // Paper start - Remove from collideRule team if needed ++ if (this.collideRuleTeamName != null) { ++ final Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreBoard.getTeam(this.collideRuleTeamName); ++ if (entityplayer.getTeam() == team && team != null) { ++ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); ++ } ++ } ++ // Paper end ++ + this.save(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); +@@ -1140,6 +1159,13 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - Remove collideRule team if it exists ++ if (this.collideRuleTeamName != null) { ++ final Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreboard.getTeam(this.collideRuleTeamName); ++ if (team != null) scoreboard.removeTeam(team); ++ } ++ // Paper end + } + + // CraftBukkit start diff --git a/Remapped-Spigot-Server-Patches/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch b/Remapped-Spigot-Server-Patches/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch new file mode 100644 index 000000000..56a3be043 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 13 Apr 2016 20:21:38 -0700 +Subject: [PATCH] Add handshake event to allow plugins to handle client + handshaking logic themselves + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 55b6412bb978abb6f8eaff83a7dd40fbc1ed8b9a..e56ab94ce65e81bb0383a1626a1790c43bd6920e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -29,7 +29,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + // CraftBukkit end + private static final Component IGNORE_STATUS_REASON = new TextComponent("Ignoring status request"); + private final MinecraftServer server; +- private final Connection connection; ++ private final Connection connection; final Connection getNetworkManager() { return this.connection; } // Paper - OBFHELPER + + public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { + this.server = server; +@@ -88,8 +88,35 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + this.connection.disconnect(chatmessage); + } else { + this.connection.setListener(new ServerLoginPacketListenerImpl(this.server, this.connection)); ++ // Paper start - handshake event ++ boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee; ++ boolean handledByEvent = false; ++ // Try and handle the handshake through the event ++ if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me? ++ java.net.SocketAddress socketAddress = this.getNetworkManager().address; ++ String hostnameOfRemote = socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getHostString() : InetAddress.getLoopbackAddress().getHostAddress(); ++ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packet.hostName, hostnameOfRemote, !proxyLogicEnabled); ++ if (event.callEvent()) { ++ // If we've failed somehow, let the client know so and go no further. ++ if (event.isFailed()) { ++ chatmessage = new TranslatableComponent(event.getFailMessage()); ++ this.getNetworkManager().send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.getNetworkManager().disconnect(chatmessage); ++ return; ++ } ++ ++ if (event.getServerHostname() != null) packet.hostName = event.getServerHostname(); ++ if (event.getSocketAddressHostname() != null) this.getNetworkManager().address = new java.net.InetSocketAddress(event.getSocketAddressHostname(), socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ this.getNetworkManager().spoofedUUID = event.getUniqueId(); ++ this.getNetworkManager().spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class); ++ handledByEvent = true; // Hooray, we did it! ++ } ++ } ++ // Don't try and handle default logic if it's been handled by the event. ++ if (!handledByEvent && proxyLogicEnabled) { ++ // Paper end + // Spigot Start +- if (org.spigotmc.SpigotConfig.bungee) { ++ //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + String[] split = packet.hostName.split("\00"); + if ( ( split.length == 3 || split.length == 4 ) && ( HOST_PATTERN.matcher( split[1] ).matches() ) ) { + packet.hostName = split[0]; diff --git a/Remapped-Spigot-Server-Patches/0089-Configurable-RCON-IP-address.patch b/Remapped-Spigot-Server-Patches/0089-Configurable-RCON-IP-address.patch new file mode 100644 index 000000000..4bc15f3f2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0089-Configurable-RCON-IP-address.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 16 Apr 2016 00:39:33 -0400 +Subject: [PATCH] Configurable RCON IP address + +For servers with multiple IP's, ability to bind to a specific interface. + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index d10f2e5a13a9e86c32ef5dd8c6732ad8b51ed6a0..545096d9ba403396b6aaa7bb6d912f2de08a967e 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -64,6 +64,8 @@ public class DedicatedServerProperties extends Settings.MutableValue whiteList; + public final WorldGenSettings worldGenSettings; + ++ public final String rconIp; // Paper - Add rcon ip ++ + // CraftBukkit start + public DedicatedServerProperties(Properties properties, RegistryAccess iregistrycustom, OptionSet optionset) { + super(properties, optionset); +@@ -115,6 +117,10 @@ public class DedicatedServerProperties extends Settings> { + }; + } + +- @Nullable +- private String getStringRaw(String key) { ++ @Nullable String getSettingIfExists(final String path) { return this.getStringRaw(path); } // Paper - OBFHELPER ++ @Nullable private String getStringRaw(String key) { // Paper - OBFHELPER + return (String) getOverride(key, this.properties.getProperty(key)); // CraftBukkit + } + +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +index f2a94e9d9b57ece16873972bc5292f7cf3928848..ef9f659ae5f53a8effa807ecb955ef47d53aacd2 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +@@ -62,7 +62,7 @@ public class RconThread extends GenericThread { + @Nullable + public static RconThread create(ServerInterface server) { + DedicatedServerProperties dedicatedserverproperties = server.getProperties(); +- String s = server.getServerIp(); ++ String s = dedicatedserverproperties.rconIp; // Paper - Configurable rcon ip + + if (s.isEmpty()) { + s = "0.0.0.0"; diff --git a/Remapped-Spigot-Server-Patches/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch b/Remapped-Spigot-Server-Patches/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch new file mode 100644 index 000000000..04b43eed1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 17 Apr 2016 17:27:09 -0400 +Subject: [PATCH] Prevent Fire from loading chunks & wrongly spread + +This causes the nether to spam unload/reload chunks, plus overall +bad behavior. + +This also stops fire from spreading to illegal locations. + +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 700078c2fd536cc22351eadf51503efb9acd9df9..85170008de6e77cfb8e4f55ae440a8428d868af4 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -134,7 +134,7 @@ public class FireBlock extends BaseFireBlock { + BooleanProperty blockstateboolean = (BooleanProperty) FireBlock.PROPERTY_BY_DIRECTION.get(enumdirection); + + if (blockstateboolean != null) { +- iblockdata1 = (BlockState) iblockdata1.setValue(blockstateboolean, this.canBurn(world.getBlockState(pos.relative(enumdirection)))); ++ iblockdata1 = (BlockState) iblockdata1.setValue(blockstateboolean, this.canBurn(world.getTypeIfLoaded(pos.relative(enumdirection)))); // Paper - prevent chunk loads + } + } + +@@ -214,6 +214,7 @@ public class FireBlock extends BaseFireBlock { + } + + blockposition_mutableblockposition.setWithOffset((Vec3i) pos, l, j1, i1); ++ if (blockposition_mutableblockposition.isInvalidYLocation() || !world.hasChunkAt(blockposition_mutableblockposition)) continue; // Paper + int l1 = this.getFireOdds((LevelReader) world, (BlockPos) blockposition_mutableblockposition); + + if (l1 > 0) { +@@ -259,10 +260,16 @@ public class FireBlock extends BaseFireBlock { + } + + private void trySpread(Level world, BlockPos blockposition, int i, Random random, int j, BlockPos sourceposition) { // CraftBukkit add sourceposition +- int k = this.getBurnOdd(world.getBlockState(blockposition)); ++ // Paper start ++ final BlockState iblockdata = world.getTypeIfLoaded(blockposition); ++ if (iblockdata == null) { ++ return; ++ } ++ int k = this.getBurnOdd(iblockdata); ++ // Paper end + + if (random.nextInt(i) < k) { +- BlockState iblockdata = world.getBlockState(blockposition); ++ //IBlockData iblockdata = world.getType(blockposition); // Paper + + // CraftBukkit start + org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); +@@ -308,7 +315,7 @@ public class FireBlock extends BaseFireBlock { + for (int j = 0; j < i; ++j) { + Direction enumdirection = aenumdirection[j]; + +- if (this.canBurn(world.getBlockState(pos.relative(enumdirection)))) { ++ if (this.canBurn(world.getTypeIfLoaded(pos.relative(enumdirection)))) { // Paper - prevent chunk loads + return true; + } + } +@@ -326,7 +333,12 @@ public class FireBlock extends BaseFireBlock { + + for (int k = 0; k < j; ++k) { + Direction enumdirection = aenumdirection[k]; +- BlockState iblockdata = iworldreader.getBlockState(pos.relative(enumdirection)); ++ // Paper start ++ BlockState iblockdata = iworldreader.getTypeIfLoaded(pos.relative(enumdirection)); ++ if (iblockdata == null) { ++ continue; ++ } ++ // Paper end + + i = Math.max(this.getFlameOdds(iblockdata), i); + } +@@ -337,7 +349,7 @@ public class FireBlock extends BaseFireBlock { + + @Override + protected boolean canBurn(BlockState state) { +- return this.getFlameOdds(state) > 0; ++ return state != null && this.getFlameOdds(state) > 0; // Paper - iblockdata can be nullable if chunk is unloaded now + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0091-Implement-PlayerLocaleChangeEvent.patch b/Remapped-Spigot-Server-Patches/0091-Implement-PlayerLocaleChangeEvent.patch new file mode 100644 index 000000000..c52d99a31 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0091-Implement-PlayerLocaleChangeEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Isaac Moore +Date: Tue, 19 Apr 2016 14:09:31 -0500 +Subject: [PATCH] Implement PlayerLocaleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ffad931c72e52855a3f139354f5e85c460e2a80b..bd3d9182dfb2c0ae1d8c3b9aa360f94c33252592 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1692,7 +1692,7 @@ public class ServerPlayer extends Player implements ContainerListener { + return s; + } + +- public String locale = "en_us"; // CraftBukkit - add, lowercase ++ public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void updateOptions(ServerboundClientInformationPacket packet) { + // CraftBukkit start +@@ -1700,9 +1700,10 @@ public class ServerPlayer extends Player implements ContainerListener { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); + this.server.server.getPluginManager().callEvent(event); + } +- if (!this.locale.equals(packet.language)) { ++ if (this.locale == null || !this.locale.equals(packet.language)) { // Paper - check for null + PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(getBukkitEntity(), packet.language); + this.server.server.getPluginManager().callEvent(event); ++ new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.locale, packet.language).callEvent(); // Paper + } + this.locale = packet.language; + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b264cbe5f91da9e31c5fd00ee285735a19aaad35..fc19b4cacd223b928fbdf922b828beaed630bf2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1875,8 +1875,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public String getLocale() { +- return getHandle().locale; +- ++ // Paper start - Locale change event ++ final String locale = getHandle().locale; ++ return locale != null ? locale : "en_us"; ++ // Paper end + } + + // Paper start diff --git a/Remapped-Spigot-Server-Patches/0092-EntityRegainHealthEvent-isFastRegen-API.patch b/Remapped-Spigot-Server-Patches/0092-EntityRegainHealthEvent-isFastRegen-API.patch new file mode 100644 index 000000000..386b74cf5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0092-EntityRegainHealthEvent-isFastRegen-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 22 Apr 2016 01:43:11 -0500 +Subject: [PATCH] EntityRegainHealthEvent isFastRegen API + +Don't even get me started + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index a326e5b4ac055f2f8a95c6eaccd8d0a97762da1f..1131d86080b3100437aa18a00c6277fcea4b7ea8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1127,10 +1127,16 @@ public abstract class LivingEntity extends Entity { + } + + public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) { ++ // Paper start - Forward ++ heal(f, regainReason, false); ++ } ++ ++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) { ++ // Paper end + float f1 = this.getHealth(); + + if (f1 > 0.0F) { +- EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason); ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper + // Suppress during worldgen + if (this.valid) { + this.level.getCraftServer().getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java +index 269392592f5271b1bb8c37661fbe685e76e32b74..d18b7d2c22312fc6ec3977ce38a1f04e0b5c8ad4 100644 +--- a/src/main/java/net/minecraft/world/food/FoodData.java ++++ b/src/main/java/net/minecraft/world/food/FoodData.java +@@ -84,7 +84,7 @@ public class FoodData { + if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit + float f = Math.min(this.saturationLevel, 6.0F); + +- player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason ++ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen + // this.a(f); CraftBukkit - EntityExhaustionEvent + player.applyExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent + this.tickTimer = 0; diff --git a/Remapped-Spigot-Server-Patches/0093-Add-ability-to-configure-frosted_ice-properties.patch b/Remapped-Spigot-Server-Patches/0093-Add-ability-to-configure-frosted_ice-properties.patch new file mode 100644 index 000000000..e5bee625d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0093-Add-ability-to-configure-frosted_ice-properties.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 21 Apr 2016 23:51:55 -0700 +Subject: [PATCH] Add ability to configure frosted_ice properties + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1942f5224aaebb18adb591d6f70a419cfc1a7bdd..5baccb8d50c135ab20c38ffd0690f585514ce5af 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -259,4 +259,14 @@ public class PaperWorldConfig { + private void useVanillaScoreboardColoring() { + useVanillaScoreboardColoring = getBoolean("use-vanilla-world-scoreboard-name-coloring", false); + } ++ ++ public boolean frostedIceEnabled = true; ++ public int frostedIceDelayMin = 20; ++ public int frostedIceDelayMax = 40; ++ private void frostedIce() { ++ this.frostedIceEnabled = this.getBoolean("frosted-ice.enabled", this.frostedIceEnabled); ++ this.frostedIceDelayMin = this.getInt("frosted-ice.delay.min", this.frostedIceDelayMin); ++ this.frostedIceDelayMax = this.getInt("frosted-ice.delay.max", this.frostedIceDelayMax); ++ log("Frosted Ice: " + (this.frostedIceEnabled ? "enabled" : "disabled") + " / delay: min=" + this.frostedIceDelayMin + ", max=" + this.frostedIceDelayMax); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java +index 0727cc36c99cb5ca5019c71f4540de76b78c7a80..ae2f5acd008d5d7163b56cb4a2d29354299959ca 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java +@@ -30,6 +30,7 @@ public class FrostedIceBlock extends IceBlock { + + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { ++ if (!world.paperConfig.frostedIceEnabled) return; // Paper - add ability to disable frosted ice + if ((random.nextInt(3) == 0 || this.fewerNeigboursThan(world, pos, 4)) && world.getMaxLocalRawBrightness(pos) > 11 - (Integer) state.getValue(FrostedIceBlock.AGE) - state.getLightBlock((BlockGetter) world, pos) && this.slightlyMelt(state, (Level) world, pos)) { + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + Direction[] aenumdirection = Direction.values(); +@@ -42,12 +43,12 @@ public class FrostedIceBlock extends IceBlock { + BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition); + + if (iblockdata1.is((Block) this) && !this.slightlyMelt(iblockdata1, (Level) world, blockposition_mutableblockposition)) { +- world.getBlockTicks().scheduleTick(blockposition_mutableblockposition, this, Mth.nextInt(random, 20, 40)); ++ world.getBlockTicks().scheduleTick(blockposition_mutableblockposition, this, Mth.nextInt(random, world.paperConfig.frostedIceDelayMin, world.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay + } + } + + } else { +- world.getBlockTicks().scheduleTick(pos, this, Mth.nextInt(random, 20, 40)); ++ world.getBlockTicks().scheduleTick(pos, this, Mth.nextInt(random, world.paperConfig.frostedIceDelayMin, world.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay + } + } + diff --git a/Remapped-Spigot-Server-Patches/0094-remove-null-possibility-for-getServer-singleton.patch b/Remapped-Spigot-Server-Patches/0094-remove-null-possibility-for-getServer-singleton.patch new file mode 100644 index 000000000..2a17e8f4a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0094-remove-null-possibility-for-getServer-singleton.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 Apr 2016 00:57:27 -0400 +Subject: [PATCH] remove null possibility for getServer singleton + +to stop IDE complaining about potential NPE + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d639ead95c36985fa0f5a9c51898c4237e373f0e..4e468cb7ccf683b8fc9e04a48cfc25779775e25f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -178,6 +178,7 @@ import org.spigotmc.SlackActivityAccountant; // Spigot + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements SnooperPopulator, CommandSource, AutoCloseable { + ++ private static MinecraftServer SERVER; // Paper + public static final Logger LOGGER = LogManager.getLogger(); + public static final File USERID_CACHE_FILE = new File("usercache.json"); + public static final LevelSettings DEMO_SETTINGS = new LevelSettings("Demo World", GameType.SURVIVAL, false, Difficulty.NORMAL, false, new GameRules(), DataPackConfig.DEFAULT); +@@ -284,6 +285,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 29 Apr 2016 20:02:00 -0400 +Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes + +Maps used a modified version of rendering to support plugin controlled +imaging on maps. The Craft Map Renderer is much slower than Vanilla, +causing maps in item frames to cause a noticeable hit on server performance. + +This updates the map system to not use the Craft system if we detect that no +custom renderers are in use, defaulting to the much simpler Vanilla system. + +Additionally, numerous issues to player position tracking on maps has been fixed. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bb99d9fe5e274318d8480a6de2c45b0a57351f77..0a613f94d1c796267636e1a343aeee65a49ffed5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1164,6 +1164,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + { + if ( iter.next().player == entity ) + { ++ map.decorations.remove(entity.getName().getString()); // Paper + iter.remove(); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 3b451e75a7f49ea6b543aee9f0a51c0be3c4dfba..c11d5aa115d10e3c12863cf9d42c60194d63b690 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -85,6 +85,7 @@ import net.minecraft.world.item.ElytraItem; + import net.minecraft.world.item.ItemCooldowns; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; ++import net.minecraft.world.item.MapItem; + import net.minecraft.world.item.ProjectileWeaponItem; + import net.minecraft.world.item.SwordItem; + import net.minecraft.world.item.crafting.Recipe; +@@ -104,6 +105,7 @@ import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.entity.StructureBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.pattern.BlockInWorld; ++import net.minecraft.world.level.saveddata.maps.MapItemSavedData; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.PlayerTeam; +@@ -686,6 +688,12 @@ public abstract class Player extends LivingEntity { + return null; + } + // CraftBukkit end ++ // Paper start - remove player from map on drop ++ if (stack.getItem() == Items.FILLED_MAP) { ++ MapItemSavedData worldmap = MapItem.getOrCreateSavedData(stack, this.level); ++ worldmap.updateSeenPlayers(this, stack); ++ } ++ // Paper end + + return entityitem; + } +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index d44505b3ee2a35422568e9bce0d868191e348fc0..7582c7cd4235d212a0cf66a4c59ce0cedaa360ad 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -57,6 +57,7 @@ public class MapItemSavedData extends SavedData { + private final Map bannerMarkers = Maps.newHashMap(); + public final Map decorations = Maps.newLinkedHashMap(); + private final Map frameMarkers = Maps.newHashMap(); ++ private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper + + // CraftBukkit start + public final CraftMapView mapView; +@@ -69,6 +70,7 @@ public class MapItemSavedData extends SavedData { + // CraftBukkit start + mapView = new CraftMapView(this); + server = (CraftServer) org.bukkit.Bukkit.getServer(); ++ vanillaRender.buffer = colors; // Paper + // CraftBukkit end + } + +@@ -136,6 +138,7 @@ public class MapItemSavedData extends SavedData { + this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner); + this.addDecoration(mapiconbanner.getDecoration(), (LevelAccessor) null, mapiconbanner.getId(), (double) mapiconbanner.getPos().getX(), (double) mapiconbanner.getPos().getZ(), 180.0D, mapiconbanner.getName()); + } ++ this.vanillaRender.buffer = colors; // Paper + + ListTag nbttaglist1 = tag.getList("frames", 10); + +@@ -216,6 +219,7 @@ public class MapItemSavedData extends SavedData { + this.setDirty(); + } + ++ public void updateSeenPlayers(Player entityhuman, ItemStack itemstack) { this.tickCarriedBy(entityhuman, itemstack); } // Paper - OBFHELPER + public void tickCarriedBy(Player player, ItemStack stack) { + if (!this.carriedByPlayers.containsKey(player)) { + MapItemSavedData.HoldingPlayer worldmap_worldmaphumantracker = new MapItemSavedData.HoldingPlayer(player); +@@ -451,6 +455,21 @@ public class MapItemSavedData extends SavedData { + + public class HoldingPlayer { + ++ // Paper start ++ private void addSeenPlayers(java.util.Collection icons) { ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) player.getBukkitEntity(); ++ MapItemSavedData.this.decorations.forEach((name, mapIcon) -> { ++ // If this cursor is for a player check visibility with vanish system ++ org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot ++ if (other == null || player.canSee(other)) { ++ icons.add(mapIcon); ++ } ++ }); ++ } ++ private boolean shouldUseVanillaMap() { ++ return mapView.getRenderers().size() == 1 && mapView.getRenderers().get(0).getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class; ++ } ++ // Paper end + public final Player player; + private boolean dirtyData = true; + private int minDirtyX; +@@ -467,9 +486,12 @@ public class MapItemSavedData extends SavedData { + @Nullable + public Packet nextUpdatePacket(ItemStack stack) { + // CraftBukkit start +- org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit ++ if (!this.dirtyData && this.tick % 5 != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it! ++ boolean vanillaMaps = shouldUseVanillaMap(); // Paper ++ org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper + + java.util.Collection icons = new java.util.ArrayList(); ++ if (vanillaMaps) addSeenPlayers(icons); // Paper + + for ( org.bukkit.map.MapCursor cursor : render.cursors) { + +diff --git a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java +index 256a131781721c86dd6cdbc329335964570cbe8c..5768cd512ec166f1e8d1f4a28792015347297c3f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java ++++ b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java +@@ -5,7 +5,7 @@ import org.bukkit.map.MapCursor; + + public class RenderData { + +- public final byte[] buffer; ++ public byte[] buffer; // Paper + public final ArrayList cursors; + + public RenderData() { diff --git a/Remapped-Spigot-Server-Patches/0096-LootTable-API-Replenishable-Lootables-Feature.patch b/Remapped-Spigot-Server-Patches/0096-LootTable-API-Replenishable-Lootables-Feature.patch new file mode 100644 index 000000000..4e9c15788 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0096-LootTable-API-Replenishable-Lootables-Feature.patch @@ -0,0 +1,739 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 1 May 2016 21:19:14 -0400 +Subject: [PATCH] LootTable API & Replenishable Lootables Feature + +Provides an API to control the loot table for an object. +Also provides a feature that any Lootable Inventory (Chests in Structures) +can automatically replenish after a given time. + +This feature is good for long term worlds so that newer players +do not suffer with "Every chest has been looted" + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5baccb8d50c135ab20c38ffd0690f585514ce5af..eb04fdb172a50ec1f5b7fe78fa0e7655246abd60 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -269,4 +269,26 @@ public class PaperWorldConfig { + this.frostedIceDelayMax = this.getInt("frosted-ice.delay.max", this.frostedIceDelayMax); + log("Frosted Ice: " + (this.frostedIceEnabled ? "enabled" : "disabled") + " / delay: min=" + this.frostedIceDelayMin + ", max=" + this.frostedIceDelayMax); + } ++ ++ public boolean autoReplenishLootables; ++ public boolean restrictPlayerReloot; ++ public boolean changeLootTableSeedOnFill; ++ public int maxLootableRefills; ++ public int lootableRegenMin; ++ public int lootableRegenMax; ++ private void enhancedLootables() { ++ autoReplenishLootables = getBoolean("lootables.auto-replenish", false); ++ restrictPlayerReloot = getBoolean("lootables.restrict-player-reloot", true); ++ changeLootTableSeedOnFill = getBoolean("lootables.reset-seed-on-fill", true); ++ maxLootableRefills = getInt("lootables.max-refills", -1); ++ lootableRegenMin = PaperConfig.getSeconds(getString("lootables.refresh-min", "12h")); ++ lootableRegenMax = PaperConfig.getSeconds(getString("lootables.refresh-max", "2d")); ++ if (autoReplenishLootables) { ++ log("Lootables: Replenishing every " + ++ PaperConfig.timeSummary(lootableRegenMin) + " to " + ++ PaperConfig.timeSummary(lootableRegenMax) + ++ (restrictPlayerReloot ? " (restricting reloot)" : "") ++ ); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fda64b8860cb696e209eedcfb200e7193d216732 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java +@@ -0,0 +1,34 @@ ++package com.destroystokyo.paper.loottable; ++ ++import LootableInventory; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; ++import org.bukkit.Chunk; ++import org.bukkit.block.Block; ++ ++public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory { ++ ++ RandomizableContainerBlockEntity getTileEntity(); ++ ++ @Override ++ default LootableInventory getAPILootableInventory() { ++ return this; ++ } ++ ++ @Override ++ default Level getNMSWorld() { ++ return getTileEntity().getLevel(); ++ } ++ ++ default Block getBlock() { ++ final BlockPos position = getTileEntity().getBlockPos(); ++ final Chunk bukkitChunk = getTileEntity().getLevel().getChunkAt(position).bukkitChunk; ++ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ()); ++ } ++ ++ @Override ++ default PaperLootableInventoryData getLootableData() { ++ return getTileEntity().lootableData; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..292d5ef8a1c428893af729b298eecd32b4c4659a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java +@@ -0,0 +1,29 @@ ++package com.destroystokyo.paper.loottable; ++ ++import LootableInventory; ++import net.minecraft.world.level.Level; ++import org.bukkit.entity.Entity; ++ ++public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory { ++ ++ net.minecraft.world.entity.Entity getHandle(); ++ ++ @Override ++ default LootableInventory getAPILootableInventory() { ++ return this; ++ } ++ ++ default Entity getEntity() { ++ return getHandle().getBukkitEntity(); ++ } ++ ++ @Override ++ default Level getNMSWorld() { ++ return getHandle().getCommandSenderWorld(); ++ } ++ ++ @Override ++ default PaperLootableInventoryData getLootableData() { ++ return getHandle().lootableData; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b3def19a50081cfa758b6e25707b2fc6fed8d3ca +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java +@@ -0,0 +1,71 @@ ++package com.destroystokyo.paper.loottable; ++ ++import org.bukkit.loot.Lootable; ++import LootableInventory; ++import java.util.UUID; ++import net.minecraft.world.level.Level; ++ ++public interface PaperLootableInventory extends LootableInventory, Lootable { ++ ++ PaperLootableInventoryData getLootableData(); ++ LootableInventory getAPILootableInventory(); ++ ++ Level getNMSWorld(); ++ ++ default org.bukkit.World getBukkitWorld() { ++ return getNMSWorld().getWorld(); ++ } ++ ++ @Override ++ default boolean isRefillEnabled() { ++ return getNMSWorld().paperConfig.autoReplenishLootables; ++ } ++ ++ @Override ++ default boolean hasBeenFilled() { ++ return getLastFilled() != -1; ++ } ++ ++ @Override ++ default boolean hasPlayerLooted(UUID player) { ++ return getLootableData().hasPlayerLooted(player); ++ } ++ ++ @Override ++ default Long getLastLooted(UUID player) { ++ return getLootableData().getLastLooted(player); ++ } ++ ++ @Override ++ default boolean setHasPlayerLooted(UUID player, boolean looted) { ++ final boolean hasLooted = hasPlayerLooted(player); ++ if (hasLooted != looted) { ++ getLootableData().setPlayerLootedState(player, looted); ++ } ++ return hasLooted; ++ } ++ ++ @Override ++ default boolean hasPendingRefill() { ++ long nextRefill = getLootableData().getNextRefill(); ++ return nextRefill != -1 && nextRefill > getLootableData().getLastFill(); ++ } ++ ++ @Override ++ default long getLastFilled() { ++ return getLootableData().getLastFill(); ++ } ++ ++ @Override ++ default long getNextRefill() { ++ return getLootableData().getNextRefill(); ++ } ++ ++ @Override ++ default long setNextRefill(long refillAt) { ++ if (refillAt < -1) { ++ refillAt = -1; ++ } ++ return getLootableData().setNextRefill(refillAt); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88542462d34ba24e8590294bd896d7e73932ef9c +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +@@ -0,0 +1,180 @@ ++package com.destroystokyo.paper.loottable; ++ ++import com.destroystokyo.paper.PaperWorldConfig; ++import org.bukkit.entity.Player; ++import org.bukkit.loot.LootTable; ++ ++import javax.annotation.Nullable; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.Random; ++import java.util.UUID; ++ ++public class PaperLootableInventoryData { ++ ++ private static final Random RANDOM = new Random(); ++ ++ private long lastFill = -1; ++ private long nextRefill = -1; ++ private int numRefills = 0; ++ private Map lootedPlayers; ++ private final PaperLootableInventory lootable; ++ ++ public PaperLootableInventoryData(PaperLootableInventory lootable) { ++ this.lootable = lootable; ++ } ++ ++ long getLastFill() { ++ return this.lastFill; ++ } ++ ++ long getNextRefill() { ++ return this.nextRefill; ++ } ++ ++ long setNextRefill(long nextRefill) { ++ long prev = this.nextRefill; ++ this.nextRefill = nextRefill; ++ return prev; ++ } ++ ++ public boolean shouldReplenish(@Nullable net.minecraft.world.entity.player.Player player) { ++ LootTable table = this.lootable.getLootTable(); ++ ++ // No Loot Table associated ++ if (table == null) { ++ return false; ++ } ++ ++ // ALWAYS process the first fill or if the feature is disabled ++ if (this.lastFill == -1 || !this.lootable.getNMSWorld().paperConfig.autoReplenishLootables) { ++ return true; ++ } ++ ++ // Only process refills when a player is set ++ if (player == null) { ++ return false; ++ } ++ ++ // Chest is not scheduled for refill ++ if (this.nextRefill == -1) { ++ return false; ++ } ++ ++ final PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig; ++ ++ // Check if max refills has been hit ++ if (paperConfig.maxLootableRefills != -1 && this.numRefills >= paperConfig.maxLootableRefills) { ++ return false; ++ } ++ ++ // Refill has not been reached ++ if (this.nextRefill > System.currentTimeMillis()) { ++ return false; ++ } ++ ++ ++ final Player bukkitPlayer = (Player) player.getBukkitEntity(); ++ LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable.getAPILootableInventory()); ++ if (paperConfig.restrictPlayerReloot && hasPlayerLooted(player.getUUID())) { ++ event.setCancelled(true); ++ } ++ return event.callEvent(); ++ } ++ public void processRefill(@Nullable net.minecraft.world.entity.player.Player player) { ++ this.lastFill = System.currentTimeMillis(); ++ final PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig; ++ if (paperConfig.autoReplenishLootables) { ++ int min = paperConfig.lootableRegenMin; ++ int max = paperConfig.lootableRegenMax; ++ this.nextRefill = this.lastFill + (min + RANDOM.nextInt(max - min + 1)) * 1000L; ++ this.numRefills++; ++ if (paperConfig.changeLootTableSeedOnFill) { ++ this.lootable.setSeed(0); ++ } ++ if (player != null) { // This means that numRefills can be incremented without a player being in the lootedPlayers list - Seems to be EntityMinecartChest specific ++ this.setPlayerLootedState(player.getUUID(), true); ++ } ++ } else { ++ this.lootable.clearLootTable(); ++ } ++ } ++ ++ ++ public void loadNbt(CompoundTag base) { ++ if (!base.contains("Paper.LootableData", 10)) { // 10 = compound ++ return; ++ } ++ CompoundTag comp = base.getCompound("Paper.LootableData"); ++ if (comp.contains("lastFill")) { ++ this.lastFill = comp.getLong("lastFill"); ++ } ++ if (comp.contains("nextRefill")) { ++ this.nextRefill = comp.getLong("nextRefill"); ++ } ++ ++ if (comp.contains("numRefills")) { ++ this.numRefills = comp.getInt("numRefills"); ++ } ++ if (comp.contains("lootedPlayers", 9)) { // 9 = list ++ ListTag list = comp.getList("lootedPlayers", 10); // 10 = compound ++ final int size = list.size(); ++ if (size > 0) { ++ this.lootedPlayers = new HashMap<>(list.size()); ++ } ++ for (int i = 0; i < size; i++) { ++ final CompoundTag cmp = list.getCompound(i); ++ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time")); ++ } ++ } ++ } ++ public void saveNbt(CompoundTag base) { ++ CompoundTag comp = new CompoundTag(); ++ if (this.nextRefill != -1) { ++ comp.putLong("nextRefill", this.nextRefill); ++ } ++ if (this.lastFill != -1) { ++ comp.putLong("lastFill", this.lastFill); ++ } ++ if (this.numRefills != 0) { ++ comp.putInt("numRefills", this.numRefills); ++ } ++ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) { ++ ListTag list = new ListTag(); ++ for (Map.Entry entry : this.lootedPlayers.entrySet()) { ++ CompoundTag cmp = new CompoundTag(); ++ cmp.setUUID("UUID", entry.getKey()); ++ cmp.putLong("Time", entry.getValue()); ++ list.add(cmp); ++ } ++ comp.put("lootedPlayers", list); ++ } ++ ++ if (!comp.isEmpty()) { ++ base.put("Paper.LootableData", comp); ++ } ++ } ++ ++ void setPlayerLootedState(UUID player, boolean looted) { ++ if (looted && this.lootedPlayers == null) { ++ this.lootedPlayers = new HashMap<>(); ++ } ++ if (looted) { ++ if (!this.lootedPlayers.containsKey(player)) { ++ this.lootedPlayers.put(player, System.currentTimeMillis()); ++ } ++ } else if (this.lootedPlayers != null) { ++ this.lootedPlayers.remove(player); ++ } ++ } ++ ++ boolean hasPlayerLooted(UUID player) { ++ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player); ++ } ++ ++ Long getLastLooted(UUID player) { ++ return lootedPlayers != null ? lootedPlayers.get(player) : null; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperMinecartLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperMinecartLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d9b31c8a21fdffb33d1f75b1a16606f218145b39 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperMinecartLootableInventory.java +@@ -0,0 +1,63 @@ ++package com.destroystokyo.paper.loottable; ++ ++import LootableInventory; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.vehicle.AbstractMinecartContainer; ++import net.minecraft.world.level.Level; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperMinecartLootableInventory implements PaperLootableEntityInventory { ++ ++ private AbstractMinecartContainer entity; ++ ++ public PaperMinecartLootableInventory(AbstractMinecartContainer entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public org.bukkit.loot.LootTable getLootTable() { ++ return entity.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.lootTable)) : null; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { ++ setLootTable(table); ++ setSeed(seed); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ entity.lootTableSeed = seed; ++ } ++ ++ @Override ++ public long getSeed() { ++ return entity.lootTableSeed; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table) { ++ entity.lootTable = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); ++ } ++ ++ @Override ++ public PaperLootableInventoryData getLootableData() { ++ return entity.lootableData; ++ } ++ ++ @Override ++ public Entity getHandle() { ++ return entity; ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ return (LootableInventory) entity.getBukkitEntity(); ++ } ++ ++ @Override ++ public Level getNMSWorld() { ++ return entity.level; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6bc899ec4dc03b09cc978bc7a763a9755a3d2dc4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +@@ -0,0 +1,66 @@ ++package com.destroystokyo.paper.loottable; ++ ++import LootableInventory; ++import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperTileEntityLootableInventory implements PaperLootableBlockInventory { ++ private RandomizableContainerBlockEntity tileEntityLootable; ++ ++ public PaperTileEntityLootableInventory(RandomizableContainerBlockEntity tileEntityLootable) { ++ this.tileEntityLootable = tileEntityLootable; ++ } ++ ++ @Override ++ public org.bukkit.loot.LootTable getLootTable() { ++ return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { ++ setLootTable(table); ++ setSeed(seed); ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table) { ++ tileEntityLootable.lootTable = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ tileEntityLootable.lootTableSeed = seed; ++ } ++ ++ @Override ++ public long getSeed() { ++ return tileEntityLootable.lootTableSeed; ++ } ++ ++ @Override ++ public PaperLootableInventoryData getLootableData() { ++ return tileEntityLootable.lootableData; ++ } ++ ++ @Override ++ public RandomizableContainerBlockEntity getTileEntity() { ++ return tileEntityLootable; ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ Level world = tileEntityLootable.getLevel(); ++ if (world == null) { ++ return null; ++ } ++ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getBlockPos())).getState(); ++ } ++ ++ @Override ++ public Level getNMSWorld() { ++ return tileEntityLootable.getLevel(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 61048140cf0adca03bfb57193ada0adaee73b1bb..171697e88f5a4d8c0be2a47b67b865bbdc4dfe8c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -157,6 +157,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + }; + // Paper end + ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +index f4758251e58fbb36526cea5c4825561d62c9665a..5b96b1e7428a43c8c5f4a96ea37d5189f0d84f56 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +@@ -45,6 +45,7 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + public long lootTableSeed; + + // CraftBukkit start ++ { this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperMinecartLootableInventory(this)); } // Paper + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; + +@@ -202,12 +203,13 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); ++ this.lootableData.saveNbt(tag); // Paper + if (this.lootTable != null) { + tag.putString("LootTable", this.lootTable.toString()); + if (this.lootTableSeed != 0L) { + tag.putLong("LootTableSeed", this.lootTableSeed); + } +- } else { ++ } if (true) { // Paper - Always save the items, Table may stick around + ContainerHelper.saveAllItems(tag, this.itemStacks); + } + +@@ -216,11 +218,12 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + super.readAdditionalSaveData(tag); ++ this.lootableData.loadNbt(tag); // Paper + this.itemStacks = NonNullList.a(this.getContainerSize(), ItemStack.EMPTY); + if (tag.contains("LootTable", 8)) { + this.lootTable = new ResourceLocation(tag.getString("LootTable")); + this.lootTableSeed = tag.getLong("LootTableSeed"); +- } else { ++ } if (true) { // Paper - always load the items, table may still remain + ContainerHelper.loadAllItems(tag, this.itemStacks); + } + +@@ -251,14 +254,15 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + } + + public void unpackLootTable(@Nullable Player player) { +- if (this.lootTable != null && this.level.getServer() != null) { ++ if (this.lootableData.shouldReplenish(player) && this.level.getServer() != null) { // Paper + LootTable loottable = this.level.getServer().getLootTables().get(this.lootTable); + + if (player instanceof ServerPlayer) { + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer) player, this.lootTable); + } + +- this.lootTable = null; ++ //this.lootTable = null; // Paper ++ this.lootableData.processRefill(player); // Paper + LootContext.Builder loottableinfo_builder = (new LootContext.Builder((ServerLevel) this.level)).withParameter(LootContextParams.ORIGIN, this.position()).withOptionalRandomSeed(this.lootTableSeed); + + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index 9d33bc31c8088bfba66be1aecbf20e7ee86e4f83..5ad419941ff1113ef29b9a4593f44d8f35ba8424 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -27,6 +27,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + @Nullable + public ResourceLocation lootTable; + public long lootTableSeed; ++ public final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperTileEntityLootableInventory(this)); // Paper + + protected RandomizableContainerBlockEntity(BlockEntityType type) { + super(type); +@@ -42,16 +43,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + } + + protected boolean tryLoadLootTable(CompoundTag nbttagcompound) { ++ this.lootableData.loadNbt(nbttagcompound); // Paper + if (nbttagcompound.contains("LootTable", 8)) { + this.lootTable = new ResourceLocation(nbttagcompound.getString("LootTable")); ++ try { org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.lootTable); } catch (IllegalArgumentException ex) { this.lootTable = null; } // Paper - validate + this.lootTableSeed = nbttagcompound.getLong("LootTableSeed"); +- return true; ++ return false; // Paper - always load the items, table may still remain + } else { + return false; + } + } + + protected boolean trySaveLootTable(CompoundTag nbttagcompound) { ++ this.lootableData.saveNbt(nbttagcompound); // Paper + if (this.lootTable == null) { + return false; + } else { +@@ -60,19 +64,20 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + nbttagcompound.putLong("LootTableSeed", this.lootTableSeed); + } + +- return true; ++ return false; // Paper - always save the items, table may still remain + } + } + + public void unpackLootTable(@Nullable Player player) { +- if (this.lootTable != null && this.level.getServer() != null) { ++ if (this.lootableData.shouldReplenish(player) && this.level.getServer() != null) { // Paper + LootTable loottable = this.level.getServer().getLootTables().get(this.lootTable); + + if (player instanceof ServerPlayer) { + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer) player, this.lootTable); + } + +- this.lootTable = null; ++ //this.lootTable = null; // Paper ++ this.lootableData.processRefill(player); // Paper + LootContext.Builder loottableinfo_builder = (new LootContext.Builder((ServerLevel) this.level)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf((Vec3i) this.worldPosition)).withOptionalRandomSeed(this.lootTableSeed); + + if (player != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 1e2e94b0cd2ede8fb7ae5902dcd0b639bd8dcf52..e89a93082fe07fdb14df8ffef5beca5bd52d7866 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -64,7 +64,7 @@ public class CraftBlockEntityState extends CraftBlockStat + } + + // gets the wrapped TileEntity +- protected T getTileEntity() { ++ public T getTileEntity() { // Paper - protected -> public + return tileEntity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +index 20d9a192ff102e04687a8aa3eff1ba36a69b6c03..a821df3e13e2ddc479dc5f55540671f43563cdac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +@@ -12,8 +12,9 @@ import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.inventory.CraftInventory; + import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest; + import org.bukkit.inventory.Inventory; ++import com.destroystokyo.paper.loottable.PaperLootableBlockInventory; // Paper + +-public class CraftChest extends CraftLootable implements Chest { ++public class CraftChest extends CraftLootable implements Chest, PaperLootableBlockInventory { // Paper + + public CraftChest(final Block block) { + super(block, ChestBlockEntity.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +index 309650aad43d8b6ce4bb13f8c172028f3feab299..5babbcfcacb89e62f00f8184af2ceea227f9ff69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +@@ -10,7 +10,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.loot.LootTable; + import org.bukkit.loot.Lootable; + +-public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable { ++public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable, com.destroystokyo.paper.loottable.PaperLootableBlockInventory { // Paper + + public CraftLootable(Block block, Class tileEntityClass) { + super(block, tileEntityClass); +@@ -54,7 +54,7 @@ public abstract class CraftLootable + setLootTable(getLootTable(), seed); + } + +- private void setLootTable(LootTable table, long seed) { ++ public void setLootTable(LootTable table, long seed) { // Paper - public + ResourceLocation key = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); + getSnapshot().setLootTable(key, seed); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +index f0a30acb0199e396d6863a473db433cbe112d8a5..293b222565d8e0592f9f355a2ee8cdfbc868a08e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +@@ -8,7 +8,7 @@ import org.bukkit.entity.minecart.StorageMinecart; + import org.bukkit.inventory.Inventory; + + @SuppressWarnings("deprecation") +-public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart { ++public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper + private final CraftInventory inventory; + + public CraftMinecartChest(CraftServer server, MinecartChest entity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +index 12044062cb746bd5c77abacf8acddc67e08e78ce..ce14bc4791bd282d16af0ee91fc431acefa3b909 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +@@ -47,7 +47,7 @@ public abstract class CraftMinecartContainer extends CraftMinecart implements Lo + return getHandle().lootTableSeed; + } + +- private void setLootTable(LootTable table, long seed) { ++ public void setLootTable(LootTable table, long seed) { // Paper + ResourceLocation newKey = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); + getHandle().setLootTable(newKey, seed); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index c1af739369715d8c628c466b269fdde99a2f6286..c8c5f60b6b32248696363d9b63bbbe43810743d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -7,7 +7,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.minecart.HopperMinecart; + import org.bukkit.inventory.Inventory; + +-public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart { ++public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper + private final CraftInventory inventory; + + public CraftMinecartHopper(CraftServer server, MinecartHopper entity) { diff --git a/Remapped-Spigot-Server-Patches/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch b/Remapped-Spigot-Server-Patches/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch new file mode 100644 index 000000000..f9fbbc4e7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 May 2016 23:33:08 -0400 +Subject: [PATCH] Don't save empty scoreboard teams to scoreboard.dat + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 504efea7b6f50a0d17f4f353781953dfb18bdeca..1b8e5671c9dc8c15ce33d351c1bb20f28919b9a2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -237,4 +237,9 @@ public class PaperConfig { + private static void enablePlayerCollisions() { + enablePlayerCollisions = getBoolean("settings.enable-player-collisions", true); + } ++ ++ public static boolean saveEmptyScoreboardTeams = false; ++ private static void saveEmptyScoreboardTeams() { ++ saveEmptyScoreboardTeams = getBoolean("settings.save-empty-scoreboard-teams", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/scores/ScoreboardSaveData.java b/src/main/java/net/minecraft/world/scores/ScoreboardSaveData.java +index 36a922029687b9fa3ca3a986ae42a373ced87a0e..b9e14d1c54b690f0b975bda5733c4cb4f6449f77 100644 +--- a/src/main/java/net/minecraft/world/scores/ScoreboardSaveData.java ++++ b/src/main/java/net/minecraft/world/scores/ScoreboardSaveData.java +@@ -182,6 +182,7 @@ public class ScoreboardSaveData extends SavedData { + + while (iterator.hasNext()) { + PlayerTeam scoreboardteam = (PlayerTeam) iterator.next(); ++ if (!com.destroystokyo.paper.PaperConfig.saveEmptyScoreboardTeams && scoreboardteam.getPlayers().isEmpty()) continue; // Paper + CompoundTag nbttagcompound = new CompoundTag(); + + nbttagcompound.putString("Name", scoreboardteam.getName()); diff --git a/Remapped-Spigot-Server-Patches/0098-System-property-for-disabling-watchdoge.patch b/Remapped-Spigot-Server-Patches/0098-System-property-for-disabling-watchdoge.patch new file mode 100644 index 000000000..d8a22a7f5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0098-System-property-for-disabling-watchdoge.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 12 May 2016 23:02:58 -0500 +Subject: [PATCH] System property for disabling watchdoge + + +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 0ed95268364ea7f6a92a39b726a1e03bc815be07..ee0cca25ef458f2f0f7e450a2edea2b2adb7e846 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -61,7 +61,7 @@ public class WatchdogThread extends Thread + while ( !stopping ) + { + // +- if ( lastTick != 0 && timeoutTime > 0 && monotonicMillis() > lastTick + timeoutTime ) ++ if ( lastTick != 0 && timeoutTime > 0 && monotonicMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); diff --git a/Remapped-Spigot-Server-Patches/0099-Optimize-UserCache-Thread-Safe.patch b/Remapped-Spigot-Server-Patches/0099-Optimize-UserCache-Thread-Safe.patch new file mode 100644 index 000000000..4b08a2274 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0099-Optimize-UserCache-Thread-Safe.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 May 2016 20:47:41 -0400 +Subject: [PATCH] Optimize UserCache / Thread Safe + +Because Techable keeps complaining about how this isn't thread safe, +easier to do this than replace the entire thing. + +Additionally, move Saving of the User cache to be done async, incase +the user never changed the default setting for Spigot's save on stop only. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4e468cb7ccf683b8fc9e04a48cfc25779775e25f..211251fe7cd08074c040df2f4642f37d5f90d856 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -905,7 +905,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Paper + + try { + BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); +@@ -268,6 +270,14 @@ public class GameProfileCache { + } catch (IOException ioexception) { + ; + } ++ // Paper start ++ }; ++ if (asyncSave) { ++ MCUtil.scheduleAsyncTask(save); ++ } else { ++ save.run(); ++ } ++ // Paper end + + } + diff --git a/Remapped-Spigot-Server-Patches/0100-Avoid-blocking-on-Network-Manager-creation.patch b/Remapped-Spigot-Server-Patches/0100-Avoid-blocking-on-Network-Manager-creation.patch new file mode 100644 index 000000000..ba69589d2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0100-Avoid-blocking-on-Network-Manager-creation.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 May 2016 23:19:16 -0400 +Subject: [PATCH] Avoid blocking on Network Manager creation + +Per Paper issue 294 + +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 9680b0b3879c72776d6225a6a5a89fdfa3520598..6cb51a4fe3c11f53fbb556ce6b0d64b735254d51 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -52,6 +52,15 @@ public class ServerConnectionListener { + public volatile boolean running; + private final List channels = Collections.synchronizedList(Lists.newArrayList()); + private final List connections = Collections.synchronizedList(Lists.newArrayList()); ++ // Paper start - prevent blocking on adding a new network manager while the server is ticking ++ private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private void addPending() { ++ Connection manager = null; ++ while ((manager = pending.poll()) != null) { ++ connections.add(manager); ++ } ++ } ++ // Paper end + + public ServerConnectionListener(MinecraftServer server) { + this.server = server; +@@ -87,7 +96,8 @@ public class ServerConnectionListener { + int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); + Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); + +- ServerConnectionListener.this.connections.add((Connection) object); // CraftBukkit - decompile error ++ //ServerConnection.this.connectedChannels.add((NetworkManager) object); // CraftBukkit - decompile error ++ pending.add((Connection) object); // Paper + channel.pipeline().addLast("packet_handler", (ChannelHandler) object); + ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + } +@@ -126,6 +136,7 @@ public class ServerConnectionListener { + + synchronized (this.connections) { + // Spigot Start ++ this.addPending(); // Paper + // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order + if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) + { diff --git a/Remapped-Spigot-Server-Patches/0101-Optional-TNT-doesn-t-move-in-water.patch b/Remapped-Spigot-Server-Patches/0101-Optional-TNT-doesn-t-move-in-water.patch new file mode 100644 index 000000000..cd8e56460 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0101-Optional-TNT-doesn-t-move-in-water.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 22 May 2016 20:20:55 -0500 +Subject: [PATCH] Optional TNT doesn't move in water + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index eb04fdb172a50ec1f5b7fe78fa0e7655246abd60..6eca3f300020006f02dd36253b522db442e3cc33 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -2,7 +2,6 @@ package com.destroystokyo.paper; + + import java.util.List; + +-import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -291,4 +290,14 @@ public class PaperWorldConfig { + ); + } + } ++ ++ public boolean preventTntFromMovingInWater; ++ private void preventTntFromMovingInWater() { ++ if (PaperConfig.version < 13) { ++ boolean oldVal = getBoolean("enable-old-tnt-cannon-behaviors", false); ++ set("prevent-tnt-from-moving-in-water", oldVal); ++ } ++ preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false); ++ log("Prevent TNT from moving in water: " + preventTntFromMovingInWater); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index fa6893055fa5617742bfb4b7eff60c8139395cb6..49c71b21b6b88bc41ca6ddf4c76186ce522ee456 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -66,7 +66,7 @@ public class ServerEntity { + private boolean wasRiding; + private boolean wasOnGround; + // CraftBukkit start +- private final Set trackedPlayers; ++ final Set trackedPlayers; // Paper - private -> package + // Paper start + private java.util.Map trackedPlayerMap = null; + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 171697e88f5a4d8c0be2a47b67b865bbdc4dfe8c..c3aece8e5001828edea304b2a8377e9a28b34cfe 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2770,6 +2770,11 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean isPushedByFluid() { ++ // Paper start ++ return this.pushedByWater(); ++ } ++ public boolean pushedByWater() { ++ // Paper end + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +index 661848084fd986321ef782317934dac19ed4dce3..347ac17643de8bcb0c8496c2ea5eb18c2e4d856b 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -5,9 +5,13 @@ import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; ++import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityDimensions; + import net.minecraft.world.entity.EntityType; +@@ -95,7 +99,27 @@ public class PrimedTnt extends Entity { + this.level.addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D); + } + } +- ++ // Paper start - Optional prevent TNT from moving in water ++ if (!this.removed && this.wasTouchingWater && this.level.paperConfig.preventTntFromMovingInWater) { ++ /* ++ * Author: Jedediah Smith ++ */ ++ // Send position and velocity updates to nearby players on every tick while the TNT is in water. ++ // This does pretty well at keeping their clients in sync with the server. ++ ChunkMap.TrackedEntity ete = ((ServerLevel)this.level).getChunkSource().chunkMap.entityMap.get(this.getId()); ++ if (ete != null) { ++ ClientboundSetEntityMotionPacket velocityPacket = new ClientboundSetEntityMotionPacket(this); ++ ClientboundTeleportEntityPacket positionPacket = new ClientboundTeleportEntityPacket(this); ++ ++ ete.seenBy.stream() ++ .filter(viewer -> (viewer.getX() - this.getX()) * (viewer.getY() - this.getY()) * (viewer.getZ() - this.getZ()) < 16 * 16) ++ .forEach(viewer -> { ++ viewer.connection.send(velocityPacket); ++ viewer.connection.send(positionPacket); ++ }); ++ } ++ } ++ // Paper end + } + + private void explode() { +@@ -164,4 +188,11 @@ public class PrimedTnt extends Entity { + public Packet getAddEntityPacket() { + return new ClientboundAddEntityPacket(this); + } ++ ++ // Paper start - Optional prevent TNT from moving in water ++ @Override ++ public boolean pushedByWater() { ++ return !level.paperConfig.preventTntFromMovingInWater && super.pushedByWater(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0102-Faster-redstone-torch-rapid-clock-removal.patch b/Remapped-Spigot-Server-Patches/0102-Faster-redstone-torch-rapid-clock-removal.patch new file mode 100644 index 000000000..cc437e735 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0102-Faster-redstone-torch-rapid-clock-removal.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Panzer +Date: Mon, 23 May 2016 12:12:37 +0200 +Subject: [PATCH] Faster redstone torch rapid clock removal + +Only resize the the redstone torch list once, since resizing arrays / lists is costly + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 8f0fec38b482465285057d3fd27d456cf036f2fd..5f3d17cb247156fc8aaa7a763e402c2bbb42a7ec 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -48,6 +48,7 @@ import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.BaseFireBlock; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.RedstoneTorchBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.BlockEntityType; + import net.minecraft.world.level.block.entity.TickableBlockEntity; +@@ -142,6 +143,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + private org.spigotmc.TickLimiter tileLimiter; + private int tileTickPosition; + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions ++ public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here + + public CraftWorld getWorld() { + return this.world; +diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +index 7d71c99c2268174fbea4297f028b75a3a0f43c11..dd4391086aff05bdea81e62b950b88cfab5ac6b8 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -21,7 +21,7 @@ import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + public class RedstoneTorchBlock extends TorchBlock { + + public static final BooleanProperty LIT = BlockStateProperties.LIT; +- private static final Map> RECENT_TOGGLES = new WeakHashMap(); ++ // Paper - Move the mapped list to World + + protected RedstoneTorchBlock(BlockBehaviour.Properties settings) { + super(settings, DustParticleOptions.REDSTONE); +@@ -68,11 +68,15 @@ public class RedstoneTorchBlock extends TorchBlock { + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { + boolean flag = this.hasNeighborSignal((Level) world, pos, state); +- List list = (List) RedstoneTorchBlock.RECENT_TOGGLES.get(world); +- +- while (list != null && !list.isEmpty() && world.getGameTime() - ((RedstoneTorchBlock.Toggle) list.get(0)).when > 60L) { +- list.remove(0); ++ // Paper start ++ java.util.ArrayDeque redstoneUpdateInfos = world.redstoneUpdateInfos; ++ if (redstoneUpdateInfos != null) { ++ RedstoneTorchBlock.Toggle curr; ++ while ((curr = redstoneUpdateInfos.peek()) != null && world.getGameTime() - curr.getTime() > 60L) { ++ redstoneUpdateInfos.poll(); ++ } + } ++ // Paper end + + // CraftBukkit start + org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager(); +@@ -137,9 +141,12 @@ public class RedstoneTorchBlock extends TorchBlock { + } + + private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) { +- List list = (List) RedstoneTorchBlock.RECENT_TOGGLES.computeIfAbsent(world, (iblockaccess) -> { +- return Lists.newArrayList(); +- }); ++ // Paper start ++ java.util.ArrayDeque list = world.redstoneUpdateInfos; ++ if (list == null) { ++ list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>(); ++ } ++ + + if (addNew) { + list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), world.getGameTime())); +@@ -147,9 +154,9 @@ public class RedstoneTorchBlock extends TorchBlock { + + int i = 0; + +- for (int j = 0; j < list.size(); ++j) { +- RedstoneTorchBlock.Toggle blockredstonetorch_redstoneupdateinfo = (RedstoneTorchBlock.Toggle) list.get(j); +- ++ for (java.util.Iterator iterator = list.iterator(); iterator.hasNext();) { ++ RedstoneTorchBlock.Toggle blockredstonetorch_redstoneupdateinfo = iterator.next(); ++ // Paper end + if (blockredstonetorch_redstoneupdateinfo.pos.equals(pos)) { + ++i; + if (i >= 8) { +@@ -164,7 +171,7 @@ public class RedstoneTorchBlock extends TorchBlock { + public static class Toggle { + + private final BlockPos pos; +- private final long when; ++ private final long when; final long getTime() { return this.when; } // Paper - OBFHELPER + + public Toggle(BlockPos pos, long time) { + this.pos = pos; diff --git a/Remapped-Spigot-Server-Patches/0103-Add-server-name-parameter.patch b/Remapped-Spigot-Server-Patches/0103-Add-server-name-parameter.patch new file mode 100644 index 000000000..50ec62154 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0103-Add-server-name-parameter.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Panzer +Date: Sat, 28 May 2016 16:54:03 +0200 +Subject: [PATCH] Add server-name parameter + + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index e8c225fcd1a3fa5a7e1971683b1876dd6462a1e2..b849b2afd009da433fe6cea5837b3ee9bb5c52b4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -143,6 +143,14 @@ public class Main { + .defaultsTo(new File("paper.yml")) + .describedAs("Yml file"); + // Paper end ++ ++ // Paper start ++ acceptsAll(asList("server-name"), "Name of the server") ++ .withRequiredArg() ++ .ofType(String.class) ++ .defaultsTo("Unknown Server") ++ .describedAs("Name"); ++ // Paper end + } + }; + diff --git a/Remapped-Spigot-Server-Patches/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch b/Remapped-Spigot-Server-Patches/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch new file mode 100644 index 000000000..e84fc6a07 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 May 2016 22:53:50 -0400 +Subject: [PATCH] Only send Dragon/Wither Death sounds to same world + +Also fix view distance lookup + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 8d1b3cbb21fe798c27ad2d39bccffa9cc983cf96..39298b69918da890c3faa516f80d1a69adb88fe2 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -619,8 +619,9 @@ public class EnderDragon extends Mob implements Enemy { + if (this.dragonDeathTime == 1 && !this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1028, this.getChunkCoordinates(), 0); +- int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; +- for (net.minecraft.server.level.ServerPlayer player : this.level.getServer().getPlayerList().players) { ++ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (net.minecraft.server.level.ServerPlayer player : (List) ((ServerLevel)level).players()) { ++ final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 61c982ead18334a29438ef8e024d97ead178a2c8..3a80869dc3c16cb81ac87100f28d63eee722067f 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -46,7 +46,6 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerBossEvent; +-import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; +@@ -256,8 +255,9 @@ public class WitherBoss extends Monster implements RangedAttackMob { + if (!this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1023, new BlockPosition(this), 0); +- int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; +- for (ServerPlayer player : (List) MinecraftServer.getServer().getPlayerList().players) { ++ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (ServerPlayer player : (List)this.level.players()) { ++ final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; diff --git a/Remapped-Spigot-Server-Patches/0105-Fix-Double-World-Add-issues.patch b/Remapped-Spigot-Server-Patches/0105-Fix-Double-World-Add-issues.patch new file mode 100644 index 000000000..b412753dd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0105-Fix-Double-World-Add-issues.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 21 Jun 2016 22:54:34 -0400 +Subject: [PATCH] Fix Double World Add issues + +Vanilla will double add Spider Jockeys to the world, so ignore already added. + +Also add debug if something else tries to, and abort before world gets bad state + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0a613f94d1c796267636e1a343aeee65a49ffed5..335928d60dbfc07644ffeab366900c5e77e99d56 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1032,6 +1032,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper + if (entity.removed) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit + return false; diff --git a/Remapped-Spigot-Server-Patches/0106-Fix-Old-Sign-Conversion.patch b/Remapped-Spigot-Server-Patches/0106-Fix-Old-Sign-Conversion.patch new file mode 100644 index 000000000..d08b9cb8c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0106-Fix-Old-Sign-Conversion.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 17 Jun 2016 20:50:11 -0400 +Subject: [PATCH] Fix Old Sign Conversion + +1) Sign loading code was trying to parse the JSON before the check for oldSign. + That code could then skip the old sign converting code if it triggers a JSON parse exception. +2) New Mojang Schematic system has Tile Entities in the new converted format, but missing the Bukkit.isConverted flag + This causes Igloos and such to render broken signs. We fix this by ignoring sign conversion for Defined Structures + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 13115d1b28dfa2d87b45a50bd0feaa7f57769122..d08ed44884726ca2ba4578226b8aa6244778f4c7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -34,6 +34,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + public CraftPersistentDataContainer persistentDataContainer; + // CraftBukkit end + private static final Logger LOGGER = LogManager.getLogger(); ++ public boolean isLoadingStructure = false; // Paper + private final BlockEntityType type; public BlockEntityType getTileEntityType() { return type; } // Paper - OBFHELPER + @Nullable + protected Level level; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index e747f729326fb3bacfb3f983ac7701c0fb0f0e6a..e4eab82855649fec654c60b2e94ba7b71c2ac5a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -78,13 +78,14 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + } + + try { +- MutableComponent ichatmutablecomponent = Component.Serializer.fromJson(s.isEmpty() ? "\"\"" : s); ++ //IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(s.isEmpty() ? "\"\"" : s); // Paper - move down - the old format might throw a json error + +- if (oldSign) { ++ if (oldSign && !isLoadingStructure) { // Paper - saved structures will be in the new format, but will not have isConverted + messages[i] = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s)[0]; + continue; + } + // CraftBukkit end ++ MutableComponent ichatmutablecomponent = Component.Serializer.fromJson(s.isEmpty() ? "\"\"" : s); // Paper - after old sign + + if (this.level instanceof ServerLevel) { + try { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index 8da00ee410e3f8f09b8ac273095a3d22d6c4d92b..d4cf5d3bdbe629081f6ec9d4ea94004560c93ebc 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -278,9 +278,11 @@ public class StructureTemplate { + definedstructure_blockinfo.nbt.putLong("LootTableSeed", random.nextLong()); + } + ++ tileentity.isLoadingStructure = true; // Paper + tileentity.load(definedstructure_blockinfo.state, definedstructure_blockinfo.nbt); + tileentity.mirror(placementData.getMirror()); + tileentity.rotate(placementData.getRotation()); ++ tileentity.isLoadingStructure = false; // Paper + } + } + diff --git a/Remapped-Spigot-Server-Patches/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch b/Remapped-Spigot-Server-Patches/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch new file mode 100644 index 000000000..51ddf3745 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 16 Jul 2016 19:11:17 -0500 +Subject: [PATCH] Don't lookup game profiles that have no UUID and no name + + +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index 9342fa6b28e805743b8e3a13007605934244d6cd..f3e05fac1b5248ca4ee2cac03263e96c166ed343 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -92,7 +92,7 @@ public class GameProfileCache { + repository.findProfilesByNames(new String[]{name}, Agent.MINECRAFT, profilelookupcallback); + GameProfile gameprofile = (GameProfile) atomicreference.get(); + +- if (!usesAuthentication() && gameprofile == null) { ++ if (!usesAuthentication() && gameprofile == null && !org.apache.commons.lang3.StringUtils.isBlank(name)) { // Paper - Don't lookup a profile with a blank name + UUID uuid = Player.createPlayerUUID(new GameProfile((UUID) null, name)); + + gameprofile = new GameProfile(uuid, name); diff --git a/Remapped-Spigot-Server-Patches/0108-Add-setting-for-proxy-online-mode-status.patch b/Remapped-Spigot-Server-Patches/0108-Add-setting-for-proxy-online-mode-status.patch new file mode 100644 index 000000000..067e04850 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0108-Add-setting-for-proxy-online-mode-status.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabriele C +Date: Fri, 5 Aug 2016 01:03:08 +0200 +Subject: [PATCH] Add setting for proxy online mode status + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 1b8e5671c9dc8c15ce33d351c1bb20f28919b9a2..c52dc0346f93527965ef29a0ccdc4bf3debe302e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -23,6 +23,7 @@ import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; + import co.aikar.timings.Timings; + import co.aikar.timings.TimingsManager; ++import org.spigotmc.SpigotConfig; + + public class PaperConfig { + +@@ -242,4 +243,13 @@ public class PaperConfig { + private static void saveEmptyScoreboardTeams() { + saveEmptyScoreboardTeams = getBoolean("settings.save-empty-scoreboard-teams", false); + } ++ ++ public static boolean bungeeOnlineMode = true; ++ private static void bungeeOnlineMode() { ++ bungeeOnlineMode = getBoolean("settings.bungee-online-mode", true); ++ } ++ ++ public static boolean isProxyOnlineMode() { ++ return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); ++ } + } +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index f3e05fac1b5248ca4ee2cac03263e96c166ed343..e8af352f813a5015d216fc590190ae8fdb03f77d 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -89,6 +89,7 @@ public class GameProfileCache { + } + }; + ++ if (com.destroystokyo.paper.PaperConfig.isProxyOnlineMode()) // Paper - only run in online mode - 100 COL + repository.findProfilesByNames(new String[]{name}, Agent.MINECRAFT, profilelookupcallback); + GameProfile gameprofile = (GameProfile) atomicreference.get(); + +@@ -106,7 +107,7 @@ public class GameProfileCache { + } + + private static boolean usesAuthentication() { +- return GameProfileCache.usesAuthentication; ++ return com.destroystokyo.paper.PaperConfig.isProxyOnlineMode(); // Paper + } + + public synchronized void add(GameProfile gameprofile) { // Paper - synchronize +diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +index 09c5fa2dbcbed05da51ef2d63e6d6112d22d7877..e6a26c274616947329a6164e4648486452819b0c 100644 +--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java ++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +@@ -63,7 +63,8 @@ public class OldUsersConverter { + return new String[i]; + }); + +- if (server.usesAuthentication() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. ++ if (server.usesAuthentication() ++ || (com.destroystokyo.paper.PaperConfig.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Handle via setting + server.getProfileRepository().findProfilesByNames(astring, Agent.MINECRAFT, callback); + } else { + String[] astring1 = astring; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2828936fe294d9d6750a8838da49ec8398835214..bbe0978f56d23b7defce765d381d4a7c20acd75c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1515,7 +1515,8 @@ public final class CraftServer implements Server { + // Spigot Start + GameProfile profile = null; + // Only fetch an online UUID in online mode +- if ( getOnlineMode() || org.spigotmc.SpigotConfig.bungee ) ++ if ( getOnlineMode() ++ || com.destroystokyo.paper.PaperConfig.isProxyOnlineMode() ) // Paper - Handle via setting + { + profile = console.getProfileCache().get( name ); + } diff --git a/Remapped-Spigot-Server-Patches/0109-Optimise-BlockState-s-hashCode-equals.patch b/Remapped-Spigot-Server-Patches/0109-Optimise-BlockState-s-hashCode-equals.patch new file mode 100644 index 000000000..6e9e140dc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0109-Optimise-BlockState-s-hashCode-equals.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Fri, 19 Aug 2016 01:52:56 +0100 +Subject: [PATCH] Optimise BlockState's hashCode/equals + +These are singleton "single instance" objects. We can rely on +object identity checks safely. + +Use a simpler optimized hashcode + +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +index c5af36a0279d1e1c951e6f9b34857b0aa934f940..62f11a02e3f5c07e838f425cffb0a28b6d2bc138 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +@@ -30,8 +30,7 @@ public class BooleanProperty extends Property { + return value.toString(); + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof BooleanProperty && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +index b5817645727f2af2785e0987ba824f431d4e9e32..2fdfd7d2470ee9f1a96eda7418b104c960df8460 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +@@ -50,8 +50,7 @@ public class EnumProperty & StringRepresentable> extends Prope + return ((StringRepresentable) value).getSerializedName(); + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof EnumProperty && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +index c3ec7f91794d802e5b9ddac3fffccce378dace68..72f508321ebffcca31240fbdd068b4d185454cbc 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +@@ -38,8 +38,7 @@ public class IntegerProperty extends Property { + return this.values; + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof IntegerProperty && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +index 8cc07c70fde81e44679f3ea7d9a4c6b2447885d4..80f8966ac56e8af4a6c7aa86b2d8dd0f319c7b5d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +@@ -60,23 +60,17 @@ public abstract class Property> { + } + + public boolean equals(Object object) { +- if (this == object) { +- return true; +- } else if (!(object instanceof Property)) { +- return false; +- } else { +- Property iblockstate = (Property) object; +- +- return this.clazz.equals(iblockstate.clazz) && this.name.equals(iblockstate.name); +- } ++ return this == object; // Paper - only one instance per configuration + } + ++ private static final java.util.concurrent.atomic.AtomicInteger hashId = new java.util.concurrent.atomic.AtomicInteger(1); // Paper - only one instance per configuration ++ private final int hashCode = 92821 * hashId.getAndIncrement(); // Paper - only one instance per configuration + public final int hashCode() { + if (this.hashCode == null) { + this.hashCode = this.generateHashCode(); + } + +- return this.hashCode; ++ return this.hashCode; // Paper - only one instance per configuration + } + + public int generateHashCode() { diff --git a/Remapped-Spigot-Server-Patches/0110-Configurable-packet-in-spam-threshold.patch b/Remapped-Spigot-Server-Patches/0110-Configurable-packet-in-spam-threshold.patch new file mode 100644 index 000000000..bce5b61b0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0110-Configurable-packet-in-spam-threshold.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 11 Sep 2016 14:30:57 -0500 +Subject: [PATCH] Configurable packet in spam threshold + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index c52dc0346f93527965ef29a0ccdc4bf3debe302e..64d7c9058ee757a6d3cf3b648596092a810e105c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -252,4 +252,13 @@ public class PaperConfig { + public static boolean isProxyOnlineMode() { + return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); + } ++ ++ public static int packetInSpamThreshold = 300; ++ private static void packetInSpamThreshold() { ++ if (version < 11) { ++ int oldValue = getInt("settings.play-in-use-item-spam-threshold", 300); ++ set("settings.incoming-packet-spam-threshold", oldValue); ++ } ++ packetInSpamThreshold = getInt("settings.incoming-packet-spam-threshold", 300); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e2bfe8e916c9e59af81627ea0ee449970527034d..d6f4ccf06c919410e13409433bdfc3aa88a21c30 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1463,13 +1463,14 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // Spigot start - limit place/interactions + private int limitedPackets; + private long lastLimitedPacket = -1; ++ private static final int THRESHOLD = com.destroystokyo.paper.PaperConfig.packetInSpamThreshold; // Paper - Configurable threshold + + private boolean checkLimit(long timestamp) { +- if (lastLimitedPacket != -1 && timestamp - lastLimitedPacket < 30 && limitedPackets++ >= 4) { ++ if (lastLimitedPacket != -1 && timestamp - lastLimitedPacket < THRESHOLD && limitedPackets++ >= 8) { // Paper - Use threshold, raise packet limit to 8 + return false; + } + +- if (lastLimitedPacket == -1 || timestamp - lastLimitedPacket >= 30) { ++ if (lastLimitedPacket == -1 || timestamp - lastLimitedPacket >= THRESHOLD) { // Paper + lastLimitedPacket = timestamp; + limitedPackets = 0; + return true; diff --git a/Remapped-Spigot-Server-Patches/0111-Configurable-flying-kick-messages.patch b/Remapped-Spigot-Server-Patches/0111-Configurable-flying-kick-messages.patch new file mode 100644 index 000000000..fc05cbc99 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0111-Configurable-flying-kick-messages.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Tue, 20 Sep 2016 00:58:01 +0000 +Subject: [PATCH] Configurable flying kick messages + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 64d7c9058ee757a6d3cf3b648596092a810e105c..4e2f243faa209925dcb7c3ef89df3ed875c5ff78 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -261,4 +261,11 @@ public class PaperConfig { + } + packetInSpamThreshold = getInt("settings.incoming-packet-spam-threshold", 300); + } ++ ++ public static String flyingKickPlayerMessage = "Flying is not enabled on this server"; ++ public static String flyingKickVehicleMessage = "Flying is not enabled on this server"; ++ private static void flyingKickMessages() { ++ flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage); ++ flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index d6f4ccf06c919410e13409433bdfc3aa88a21c30..1b92c669bbe69bcc07a554b7b43ee99bfebc1af4 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -305,7 +305,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + if (this.clientIsFloating && !this.player.isSleeping()) { + if (++this.aboveGroundTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.flying")); ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage); // Paper - use configurable kick message + return; + } + } else { +@@ -324,7 +324,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.flying")); ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage); // Paper - use configurable kick message + return; + } + } else { diff --git a/Remapped-Spigot-Server-Patches/0112-Chunk-registration-fixes.patch b/Remapped-Spigot-Server-Patches/0112-Chunk-registration-fixes.patch new file mode 100644 index 000000000..4d2da1cd8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0112-Chunk-registration-fixes.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Sep 2016 22:54:28 -0400 +Subject: [PATCH] Chunk registration fixes + +World checks and the Chunk Add logic are inconsistent on how Y > 256, < 0, is treated + +Keep them consistent + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 335928d60dbfc07644ffeab366900c5e77e99d56..20650bfd10abfa010e71cfeede06c461d50d19a3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -841,7 +841,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (entity.checkAndResetUpdateChunkPos()) { + this.getProfiler().push("chunkCheck"); + int i = Mth.floor(entity.getX() / 16.0D); +- int j = Mth.floor(entity.getY() / 16.0D); ++ int j = Math.min(15, Math.max(0, Mth.floor(entity.getY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior + int k = Mth.floor(entity.getZ() / 16.0D); + + if (!entity.inChunk || entity.xChunk != i || entity.yChunk != j || entity.zChunk != k) { diff --git a/Remapped-Spigot-Server-Patches/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch b/Remapped-Spigot-Server-Patches/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch new file mode 100644 index 000000000..0d1bb6c9b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 16 Jun 2016 00:17:23 -0400 +Subject: [PATCH] Remove FishingHook reference on Craft Entity removal + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index 5668facc5bf5c56581c3ebd268f832d77ce5c05b..50322cfc07a7d93c32461faeb5e22e35ceead323 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -119,4 +119,14 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + public HookState getState() { + return HookState.values()[getHandle().currentState.ordinal()]; + } ++ ++ // Paper start ++ @Override ++ public void remove() { ++ super.remove(); ++ if (getHandle().getPlayerOwner() != null) { ++ getHandle().getPlayerOwner().fishing = null; ++ } ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0114-Auto-fix-bad-Y-levels-on-player-login.patch b/Remapped-Spigot-Server-Patches/0114-Auto-fix-bad-Y-levels-on-player-login.patch new file mode 100644 index 000000000..a6a274fc0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0114-Auto-fix-bad-Y-levels-on-player-login.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Sep 2016 23:48:39 -0400 +Subject: [PATCH] Auto fix bad Y levels on player login + +Bring down to a saner Y level if super high, as this can cause the server to crash + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index bd3d9182dfb2c0ae1d8c3b9aa360f94c33252592..3a2356b3e00098d100a179a05316f402390d4e9b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -337,6 +337,7 @@ public class ServerPlayer extends Player implements ContainerListener { + @Override + public void readAdditionalSaveData(CompoundTag tag) { + super.readAdditionalSaveData(tag); ++ if (this.getY() > 300) this.setPosRaw(getX(), 257, getZ()); // Paper - bring down to a saner Y level if out of world + if (tag.contains("playerGameType", 99)) { + if (this.getServer().getForceGameType()) { + this.gameMode.setGameModeForPlayer(this.getServer().getDefaultGameType(), GameType.NOT_SET); diff --git a/Remapped-Spigot-Server-Patches/0115-Option-to-remove-corrupt-tile-entities.patch b/Remapped-Spigot-Server-Patches/0115-Option-to-remove-corrupt-tile-entities.patch new file mode 100644 index 000000000..d6b80297c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0115-Option-to-remove-corrupt-tile-entities.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 5 Oct 2016 16:27:36 -0500 +Subject: [PATCH] Option to remove corrupt tile entities + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6eca3f300020006f02dd36253b522db442e3cc33..622affa0dc3cc1eadaed400511f2ca2cde3fca2a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -300,4 +300,9 @@ public class PaperWorldConfig { + preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false); + log("Prevent TNT from moving in water: " + preventTntFromMovingInWater); + } ++ ++ public boolean removeCorruptTEs = false; ++ private void removeCorruptTEs() { ++ removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 70f5b025c2b803df3de8a51cbcfafbe915866f42..d69ccb1f31f31ebeee477df20ce1410f9e485eb7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -678,6 +678,12 @@ public class LevelChunk implements ChunkAccess { + "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16)); + e.printStackTrace(); + ServerInternalException.reportInternalException(e); ++ ++ if (this.world.paperConfig.removeCorruptTEs) { ++ this.removeBlockEntity(blockEntity.getBlockPos()); ++ this.markUnsaved(); ++ org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity"); ++ } + // Paper end + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0116-Add-EntityZapEvent.patch b/Remapped-Spigot-Server-Patches/0116-Add-EntityZapEvent.patch new file mode 100644 index 000000000..20a798bf8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0116-Add-EntityZapEvent.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlphaBlend +Date: Sun, 16 Oct 2016 23:19:30 -0700 +Subject: [PATCH] Add EntityZapEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Pig.java b/src/main/java/net/minecraft/world/entity/animal/Pig.java +index 6ecf7afe5fd7c4c95a17eaed1445d034aa2d5f18..e512a38ccbba93266f0234e3b2fcf7f62693039b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Pig.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Pig.java +@@ -254,6 +254,11 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { + } + + entitypigzombie.setPersistenceRequired(); ++ // Paper start ++ if (CraftEventFactory.callEntityZapEvent(this, lightning, entitypigzombie).isCancelled()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start + if (CraftEventFactory.callPigZapEvent(this, lightning, entitypigzombie).isCancelled()) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 2f99bdaabe1c1a6a4e1a7e2bd533a63b12818be1..5648a4a4d8511ac8c46c61245a7ff83753a3e51f 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -786,6 +786,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); + Witch entitywitch = (Witch) EntityType.WITCH.create((Level) world); + ++ // Paper start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch).isCancelled()) { ++ return; ++ } ++ // Paper end ++ + entitywitch.moveTo(this.getX(), this.getY(), this.getZ(), this.yRot, this.xRot); + entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null, (CompoundTag) null); + entitywitch.setNoAi(this.isNoAi()); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7fde1bb7587e567270e3f936381c6d361870211f..81af3e2e0964b6e179f92a342efdae943be18b75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1086,6 +1086,14 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start ++ public static com.destroystokyo.paper.event.entity.EntityZapEvent callEntityZapEvent (Entity entity, Entity lightning, Entity changedEntity) { ++ com.destroystokyo.paper.event.entity.EntityZapEvent event = new com.destroystokyo.paper.event.entity.EntityZapEvent(entity.getBukkitEntity(), (LightningStrike) lightning.getBukkitEntity(), changedEntity.getBukkitEntity()); ++ entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static HorseJumpEvent callHorseJumpEvent(Entity horse, float power) { + HorseJumpEvent event = new HorseJumpEvent((AbstractHorse) horse.getBukkitEntity(), power); + horse.getBukkitEntity().getServer().getPluginManager().callEvent(event); diff --git a/Remapped-Spigot-Server-Patches/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch b/Remapped-Spigot-Server-Patches/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch new file mode 100644 index 000000000..782f93006 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 12 Nov 2016 23:25:22 -0600 +Subject: [PATCH] Filter bad data from ArmorStand and SpawnEgg items + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 622affa0dc3cc1eadaed400511f2ca2cde3fca2a..e83216be5a00d5b927d8c2fc364551bd3077c974 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -2,6 +2,7 @@ package com.destroystokyo.paper; + + import java.util.List; + ++import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -305,4 +306,12 @@ public class PaperWorldConfig { + private void removeCorruptTEs() { + removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false); + } ++ ++ public boolean filterNBTFromSpawnEgg = true; ++ private void fitlerNBTFromSpawnEgg() { ++ filterNBTFromSpawnEgg = getBoolean("filter-nbt-data-from-spawn-eggs-and-related", true); ++ if (!filterNBTFromSpawnEgg) { ++ Bukkit.getLogger().warning("Spawn Egg and Armor Stand NBT filtering disabled, this is a potential security risk"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 1d87717cc9002ea202ee2ca614aaa8a4c7ea3cb2..ff8f7e4569a889ead1512b7c9908f9c5cad9eed5 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -270,6 +270,13 @@ public class FallingBlockEntity extends Entity { + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + this.blockState = NbtUtils.readBlockState(tag.getCompound("BlockState")); ++ // Paper start - Block FallingBlocks with Command Blocks ++ // Check mappings on update - dc = "repeating_command_block" - dd = "chain_command_block" ++ final Block b = this.blockState.getBlock(); ++ if (this.level.paperConfig.filterNBTFromSpawnEgg && (b == Blocks.COMMAND_BLOCK || b == Blocks.REPEATING_COMMAND_BLOCK || b == Blocks.CHAIN_COMMAND_BLOCK)) { ++ this.blockState = Blocks.STONE.defaultBlockState(); ++ } ++ // Paper end + this.time = tag.getInt("Time"); + if (tag.contains("HurtEntities", 99)) { + this.hurtEntities = tag.getBoolean("HurtEntities"); diff --git a/Remapped-Spigot-Server-Patches/0118-Cache-user-authenticator-threads.patch b/Remapped-Spigot-Server-Patches/0118-Cache-user-authenticator-threads.patch new file mode 100644 index 000000000..be4de8cb2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0118-Cache-user-authenticator-threads.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: vemacs +Date: Wed, 23 Nov 2016 08:31:45 -0500 +Subject: [PATCH] Cache user authenticator threads + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0597c0c3e881dd43cf91bd3088ed30dfecfe8098..175bf535066afc42de8a3f0d11c46af66f3e3e52 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1388,7 +1388,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + +- player.entitiesToRemove.remove(Integer.valueOf(this.entity.getId())); ++ player.removeQueue.remove(Integer.valueOf(this.entity.getId())); + // CraftBukkit end + + if (flag1 && this.trackedPlayerMap.putIfAbsent(player, true) == null) { // Paper +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3a2356b3e00098d100a179a05316f402390d4e9b..3cde25c2479adcc4ce3014e5ac2ec0710bffeea9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -4,7 +4,9 @@ import com.google.common.collect.Lists; + import com.mojang.authlib.GameProfile; + import com.mojang.datafixers.util.Either; + import com.mojang.serialization.DataResult; ++import java.util.ArrayDeque; // Paper + import java.util.Collection; ++import java.util.Deque; // Paper + import java.util.Iterator; + import java.util.List; + import java.util.Optional; +@@ -169,7 +171,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public ServerGamePacketListenerImpl connection; + public final MinecraftServer server; + public final ServerPlayerGameMode gameMode; +- public final List entitiesToRemove = Lists.newLinkedList(); ++ public final Deque removeQueue = new ArrayDeque<>(); // Paper + private final PlayerAdvancements advancements; + private final ServerStatsCounter stats; + private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; +@@ -544,16 +546,23 @@ public class ServerPlayer extends Player implements ContainerListener { + this.containerMenu = this.inventoryMenu; + } + +- while (!this.entitiesToRemove.isEmpty()) { +- int i = Math.min(this.entitiesToRemove.size(), Integer.MAX_VALUE); ++ while (!this.removeQueue.isEmpty()) { ++ int i = Math.min(this.removeQueue.size(), Integer.MAX_VALUE); + int[] aint = new int[i]; +- Iterator iterator = this.entitiesToRemove.iterator(); ++ //Iterator iterator = this.removeQueue.iterator(); // Paper + int j = 0; + +- while (iterator.hasNext() && j < i) { ++ // Paper start ++ /* while (iterator.hasNext() && j < i) { + aint[j++] = (Integer) iterator.next(); + iterator.remove(); ++ } */ ++ ++ Integer integer; ++ while (j < i && (integer = this.removeQueue.poll()) != null) { ++ aint[j++] = integer.intValue(); + } ++ // Paper end + + this.connection.send(new ClientboundRemoveEntitiesPacket(aint)); + } +@@ -1558,7 +1567,14 @@ public class ServerPlayer extends Player implements ContainerListener { + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; + // this.recipeBook.a((RecipeBook) entityplayer.recipeBook); // CraftBukkit +- this.entitiesToRemove.addAll(oldPlayer.entitiesToRemove); ++ // Paper start - Optimize remove queue - vanilla copies player objects, but CB doesn't. This method currently only ++ // Applies to the same player, so we need to not duplicate our removal queue. The rest of this method does "resetting" ++ // type logic so it does need to be called, maybe? This is silly. ++ // this.removeQueue.addAll(entityplayer.removeQueue); ++ if (this.removeQueue != oldPlayer.removeQueue) { ++ this.removeQueue.addAll(oldPlayer.removeQueue); ++ } ++ // Paper end + this.seenCredits = oldPlayer.seenCredits; + this.enteredNetherPosition = oldPlayer.enteredNetherPosition; + this.setShoulderEntityLeft(oldPlayer.getShoulderEntityLeft()); +@@ -1748,13 +1764,13 @@ public class ServerPlayer extends Player implements ContainerListener { + if (entity instanceof Player) { + this.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{entity.getId()})); + } else { +- this.entitiesToRemove.add((Integer) entity.getId()); // CraftBukkit - decompile error ++ this.removeQueue.add((Integer) entity.getId()); // CraftBukkit - decompile error + } + + } + + public void cancelRemoveEntity(Entity entity) { +- this.entitiesToRemove.remove((Integer) entity.getId()); // CraftBukkit - decompile error ++ this.removeQueue.remove((Integer) entity.getId()); // CraftBukkit - decompile error + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0119-Optimise-removeQueue.patch b/Remapped-Spigot-Server-Patches/0119-Optimise-removeQueue.patch new file mode 100644 index 000000000..f3b806851 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0119-Optimise-removeQueue.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Fri, 25 Nov 2016 13:22:40 +0000 +Subject: [PATCH] Optimise removeQueue + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 9dbb3bb79ae2de6635f71e5ee5bebeb5e57b624e..e3c1460c580ea348ee6d436018244441a5a1206e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -114,6 +114,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + + } + ++ // Paper start - Cache authenticator threads ++ private static final AtomicInteger threadId = new AtomicInteger(0); ++ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool( ++ r -> new Thread(r, "User Authenticator #" + threadId.incrementAndGet()) ++ ); ++ // Paper end + // Spigot start + public void initUUID() + { +@@ -193,8 +199,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.nonce)); + } else { + // Spigot start +- new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) { +- ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { + @Override + public void run() { + try { +@@ -205,7 +211,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + gameProfile.getName(), ex); + } + } +- }.start(); ++ }); ++ // Paper end + // Spigot end + } + +@@ -234,7 +241,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + throw new IllegalStateException("Protocol error", cryptographyexception); + } + +- Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) { ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { + public void run() { + GameProfile gameprofile = ServerLoginPacketListenerImpl.this.gameProfile; + +@@ -279,10 +287,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + + return ServerLoginPacketListenerImpl.this.server.getPreventProxyConnections() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null; + } +- }; +- +- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(ServerLoginPacketListenerImpl.LOGGER)); +- thread.start(); ++ }); ++ // Paper end + } + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0120-Allow-Reloading-of-Command-Aliases.patch b/Remapped-Spigot-Server-Patches/0120-Allow-Reloading-of-Command-Aliases.patch new file mode 100644 index 000000000..360a4a34c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0120-Allow-Reloading-of-Command-Aliases.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Mon, 28 Nov 2016 10:21:52 -0500 +Subject: [PATCH] Allow Reloading of Command Aliases + +Reload the aliases stored in commands.yml + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index bbe0978f56d23b7defce765d381d4a7c20acd75c..9365fd2bcf74755c90c4ae9b550969e97a22c639 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2281,5 +2281,24 @@ public final class CraftServer implements Server { + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); + } ++ ++ @Override ++ public boolean reloadCommandAliases() { ++ Set removals = getCommandAliases().keySet().stream() ++ .map(key -> key.toLowerCase(java.util.Locale.ENGLISH)) ++ .collect(java.util.stream.Collectors.toSet()); ++ getCommandMap().getKnownCommands().keySet().removeIf(removals::contains); ++ File file = getCommandsConfigFile(); ++ try { ++ commandsConfiguration.load(file); ++ } catch (FileNotFoundException ex) { ++ return false; ++ } catch (IOException | org.bukkit.configuration.InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); ++ return false; ++ } ++ commandMap.registerServerAliases(); ++ return true; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0121-Add-source-to-PlayerExpChangeEvent.patch b/Remapped-Spigot-Server-Patches/0121-Add-source-to-PlayerExpChangeEvent.patch new file mode 100644 index 000000000..0ab2cb875 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0121-Add-source-to-PlayerExpChangeEvent.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlphaBlend +Date: Thu, 8 Sep 2016 08:48:33 -0700 +Subject: [PATCH] Add source to PlayerExpChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index a4a1e836767d0b2b71c3277a28eb72418fa76210..f932fc4f8240b48f8e518af05d1521bc8ff9cbee 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -202,7 +202,7 @@ public class ExperienceOrb extends Entity { + } + + if (this.value > 0) { +- player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this.value).getAmount()); // CraftBukkit - this.value -> event.getAmount() ++ player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object + } + + this.remove(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 81af3e2e0964b6e179f92a342efdae943be18b75..c85fcad33e17c8ae2e4ee5cb873dbd4166fcc7f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -109,6 +109,7 @@ import org.bukkit.entity.ThrownPotion; + import org.bukkit.entity.Vehicle; + import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; ++import org.bukkit.entity.ExperienceOrb; // Paper + import org.bukkit.event.Cancellable; + import org.bukkit.event.Event; + import org.bukkit.event.Event.Result; +@@ -1045,6 +1046,17 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - Add orb ++ public static PlayerExpChangeEvent callPlayerExpChangeEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb entityOrb) { ++ Player player = (Player) entity.getBukkitEntity(); ++ ExperienceOrb source = (ExperienceOrb) entityOrb.getBukkitEntity(); ++ int expAmount = source.getExperience(); ++ PlayerExpChangeEvent event = new PlayerExpChangeEvent(player, source, expAmount); ++ Bukkit.getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static boolean handleBlockGrowEvent(Level world, BlockPos pos, net.minecraft.world.level.block.state.BlockState block) { + return handleBlockGrowEvent(world, pos, block, 3); + } diff --git a/Remapped-Spigot-Server-Patches/0122-Don-t-let-fishinghooks-use-portals.patch b/Remapped-Spigot-Server-Patches/0122-Don-t-let-fishinghooks-use-portals.patch new file mode 100644 index 000000000..684578492 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0122-Don-t-let-fishinghooks-use-portals.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 16 Dec 2016 16:03:19 -0600 +Subject: [PATCH] Don't let fishinghooks use portals + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 2f67c2065ef29f17f12190b25bd1ea53e1fb55b4..fa078167dd9e0cae80516549eef0e554c13938a3 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -237,6 +237,11 @@ public class FishingHook extends Projectile { + + this.setDeltaMovement(this.getDeltaMovement().scale(0.92D)); + this.reapplyPosition(); ++ // Paper start - These shouldn't be going through portals ++ if (this.isInsidePortal) { ++ this.remove(); ++ } ++ // Paper end + } + } + diff --git a/Remapped-Spigot-Server-Patches/0123-Add-ProjectileCollideEvent.patch b/Remapped-Spigot-Server-Patches/0123-Add-ProjectileCollideEvent.patch new file mode 100644 index 000000000..0f4f99f50 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0123-Add-ProjectileCollideEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Fri, 16 Dec 2016 21:25:39 -0600 +Subject: [PATCH] Add ProjectileCollideEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 0dc5792d542658107c9c22c1f920986decd13920..3ce431c1fdf1f5bd62b49f26cca188e939e98efa 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -196,6 +196,17 @@ public abstract class AbstractArrow extends Projectile { + } + } + ++ // Paper start - Call ProjectileCollideEvent ++ // TODO: flag - noclip - call cancelled? ++ if (object instanceof EntityHitResult) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileCollideEvent(this, (EntityHitResult)object); ++ if (event.isCancelled()) { ++ object = null; ++ movingobjectpositionentity = null; ++ } ++ } ++ // Paper end ++ + if (object != null && !flag) { + this.preOnHit((HitResult) object); // CraftBukkit - projectile hit event + this.hasImpulse = true; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +index bdff1c57f64d1bf29f2050f06c8b585d395b2c5c..872ff430547276e2a41a48aa07ae63b87ab39e5d 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +@@ -12,6 +12,7 @@ import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit +@@ -71,7 +72,16 @@ public abstract class AbstractHurtingProjectile extends Projectile { + + HitResult movingobjectposition = ProjectileUtil.getHitResult((Entity) this, this::canHitEntity); + +- if (movingobjectposition.getType() != HitResult.Type.MISS) { ++ // Paper start - Call ProjectileCollideEvent ++ if (movingobjectposition instanceof EntityHitResult) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = CraftEventFactory.callProjectileCollideEvent(this, (EntityHitResult)movingobjectposition); ++ if (event.isCancelled()) { ++ movingobjectposition = null; ++ } ++ } ++ // Paper end ++ ++ if (movingobjectposition != null && movingobjectposition.getType() != HitResult.Type.MISS) { // Paper - add null check in case cancelled + this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event + + // CraftBukkit start - Fire ProjectileHitEvent +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java +index 23f255bfa63cd16e2930fc932a2f4df8e522f2dc..becb07cda7388bcf2e987f06557894ae50d70dbf 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java +@@ -13,6 +13,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + +@@ -57,7 +58,17 @@ public abstract class ThrowableProjectile extends Projectile { + } + + if (movingobjectposition.getType() != HitResult.Type.MISS && !flag) { ++ // Paper start - Call ProjectileCollideEvent ++ if (movingobjectposition instanceof EntityHitResult) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileCollideEvent(this, (EntityHitResult)movingobjectposition); ++ if (event.isCancelled()) { ++ movingobjectposition = null; ++ } ++ } ++ if (movingobjectposition != null) { ++ // Paper end + this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event ++ } // Paper + } + + this.checkInsideBlocks(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c85fcad33e17c8ae2e4ee5cb873dbd4166fcc7f5..3f082b7fd50752728917a7da28cba4cb396a9fdf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1190,6 +1190,16 @@ public class CraftEventFactory { + return CraftItemStack.asNMSCopy(bitem); + } + ++ // Paper start ++ public static com.destroystokyo.paper.event.entity.ProjectileCollideEvent callProjectileCollideEvent(Entity entity, EntityHitResult position) { ++ Projectile projectile = (Projectile) entity.getBukkitEntity(); ++ org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); ++ Bukkit.getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { + Projectile bukkitEntity = (Projectile) entity.getBukkitEntity(); + ProjectileLaunchEvent event = new ProjectileLaunchEvent(bukkitEntity); diff --git a/Remapped-Spigot-Server-Patches/0124-Prevent-Pathfinding-out-of-World-Border.patch b/Remapped-Spigot-Server-Patches/0124-Prevent-Pathfinding-out-of-World-Border.patch new file mode 100644 index 000000000..f8a0e7b1e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0124-Prevent-Pathfinding-out-of-World-Border.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 19 Dec 2016 23:07:42 -0500 +Subject: [PATCH] Prevent Pathfinding out of World Border + +This prevents Entities from trying to run outside of the World Border + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index 25bc3adfad956157cef0953e6e632b7b7e352f3a..c3082f5dd64413a47421cb01538bec846bf21d2c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -148,7 +148,7 @@ public abstract class PathNavigation { + // Paper start - Pathfind event + boolean copiedSet = false; + for (BlockPos possibleTarget : set) { +- if (!new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), ++ if (!getEntity().getCommandSenderWorld().getWorldBorder().isInBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), // Paper - don't path out of world border + MCUtil.toLocation(getEntity().level, possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) { + if (!copiedSet) { + copiedSet = true; diff --git a/Remapped-Spigot-Server-Patches/0125-Optimize-World.isLoaded-BlockPosition-Z.patch b/Remapped-Spigot-Server-Patches/0125-Optimize-World.isLoaded-BlockPosition-Z.patch new file mode 100644 index 000000000..6a3d81e0f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0125-Optimize-World.isLoaded-BlockPosition-Z.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 2 Dec 2016 00:11:43 -0500 +Subject: [PATCH] Optimize World.isLoaded(BlockPosition)Z + +Reduce method invocations for World.isLoaded(BlockPosition)Z + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5f3d17cb247156fc8aaa7a763e402c2bbb42a7ec..ecb6378a285dff4b34170410387ebb7a8510a6dc 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -305,6 +305,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return chunk == null ? null : chunk.getFluidState(blockposition); + } + ++ public final boolean hasChunkAt(BlockPos pos) { ++ return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper ++ } ++ + public final boolean isLoadedAndInBounds(BlockPos blockposition) { // Paper - final for inline + return getWorldBorder().isInBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; + } diff --git a/Remapped-Spigot-Server-Patches/0126-Bound-Treasure-Maps-to-World-Border.patch b/Remapped-Spigot-Server-Patches/0126-Bound-Treasure-Maps-to-World-Border.patch new file mode 100644 index 000000000..b83911028 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0126-Bound-Treasure-Maps-to-World-Border.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 20 Dec 2016 15:15:11 -0500 +Subject: [PATCH] Bound Treasure Maps to World Border + +Make it so a Treasure Map does not target a structure outside of the +World Border, where players are not even able to reach. + +This also would help the case where a players close to the border, and one +that is outside happens to be closer, but unreachable, yet another reachable +one is in border that would of been missed. + +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 0846f649dca3422dbab3bb0a4826e27430cc8186..7a728ca96ee2eaf776c391ba8351196a526e18ec 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -36,6 +36,18 @@ public class WorldBorder { + return (double) (pos.getX() + 1) > this.getMinX() && (double) pos.getX() < this.getMaxX() && (double) (pos.getZ() + 1) > this.getMinZ() && (double) pos.getZ() < this.getMaxZ(); + } + ++ // Paper start ++ private final BlockPos.MutableBlockPos mutPos = new BlockPos.MutableBlockPos(); ++ public boolean isBlockInBounds(int chunkX, int chunkZ) { ++ this.mutPos.setValues(chunkX, 64, chunkZ); ++ return this.isInBounds(this.mutPos); ++ } ++ public boolean isChunkInBounds(int chunkX, int chunkZ) { ++ this.mutPos.setValues(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15); ++ return this.isInBounds(this.mutPos); ++ } ++ // Paper end ++ + public boolean isWithinBounds(ChunkPos pos) { + return (double) pos.getMaxBlockX() > this.getMinX() && (double) pos.getMinBlockX() < this.getMaxX() && (double) pos.getMaxBlockZ() > this.getMinZ() && (double) pos.getMinBlockZ() < this.getMaxZ(); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +index 9f60abfe0a37e30c5528a1ca0546295b00598798..0624b8270bc28c83c5479cd51fa4633ed5c36f44 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +@@ -175,6 +175,7 @@ public abstract class StructureFeature { + int i2 = l + k * k1; + int j2 = i1 + k * l1; + ChunkPos chunkcoordintpair = this.getPotentialFeatureChunk(config, worldSeed, seededrandom, i2, j2); ++ if (!world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper + ChunkAccess ichunkaccess = world.getChunk(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); + StructureStart structurestart = structureAccessor.getStartForFeature(SectionPos.of(ichunkaccess.getPos(), 0), this, ichunkaccess); + diff --git a/Remapped-Spigot-Server-Patches/0127-Configurable-Cartographer-Treasure-Maps.patch b/Remapped-Spigot-Server-Patches/0127-Configurable-Cartographer-Treasure-Maps.patch new file mode 100644 index 000000000..2eabafdf5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0127-Configurable-Cartographer-Treasure-Maps.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 20 Dec 2016 15:26:27 -0500 +Subject: [PATCH] Configurable Cartographer Treasure Maps + +Allow configuring for cartographers to return the same map location + +Also allow turning off treasure maps all together as they can eat up Map ID's +which are limited in quantity. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e83216be5a00d5b927d8c2fc364551bd3077c974..2dc58b9f769ea43b737804456aafab47ecc143b8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -314,4 +314,14 @@ public class PaperWorldConfig { + Bukkit.getLogger().warning("Spawn Egg and Armor Stand NBT filtering disabled, this is a potential security risk"); + } + } ++ ++ public boolean enableTreasureMaps = true; ++ public boolean treasureMapsAlreadyDiscovered = false; ++ private void treasureMapsAlreadyDiscovered() { ++ enableTreasureMaps = getBoolean("enable-treasure-maps", true); ++ treasureMapsAlreadyDiscovered = getBoolean("treasure-maps-return-already-discovered", false); ++ if (treasureMapsAlreadyDiscovered) { ++ log("Treasure Maps will return already discovered locations"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +index fd1b84baae5f333c58dbbdcbfaa9198328f0961d..7d490e1be772be22c3ab75c7e356465183a5c6b1 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java ++++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -124,7 +124,8 @@ public class VillagerTrades { + return null; + } else { + ServerLevel worldserver = (ServerLevel) entity.level; +- BlockPos blockposition = worldserver.findNearestMapFeature(this.destination, entity.blockPosition(), 100, true); ++ if (!worldserver.paperConfig.enableTreasureMaps) return null; // Paper ++ BlockPos blockposition = worldserver.findNearestMapFeature(this.destination, entity.blockPosition(), 100, !worldserver.paperConfig.treasureMapsAlreadyDiscovered); // Paper + + if (blockposition != null) { + ItemStack itemstack = MapItem.create(worldserver, blockposition.getX(), blockposition.getZ(), (byte) 2, true, true); +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +index a3ce120b0da62f9be938c58c3414ce997f5d30ea..81a8331bfdf30da6ea69952ae42d3c77a2103bfd 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +@@ -64,7 +64,16 @@ public class ExplorationMapFunction extends LootItemConditionalFunction { + + if (vec3d != null) { + ServerLevel worldserver = context.getLevel(); +- BlockPos blockposition = worldserver.findNearestMapFeature(this.destination, new BlockPos(vec3d), this.searchRadius, this.skipKnownStructures); ++ // Paper start ++ if (!worldserver.paperConfig.enableTreasureMaps) { ++ /* ++ * NOTE: I fear users will just get a plain map as their "treasure" ++ * This is preferable to disrespecting the config. ++ */ ++ return stack; ++ } ++ // Paper end ++ BlockPos blockposition = worldserver.findNearestMapFeature(this.destination, new BlockPos(vec3d), this.searchRadius, !worldserver.paperConfig.treasureMapsAlreadyDiscovered && this.skipKnownStructures); // Paper + + if (blockposition != null) { + ItemStack itemstack1 = MapItem.create(worldserver, blockposition.getX(), blockposition.getZ(), this.zoom, true, true); diff --git a/Remapped-Spigot-Server-Patches/0128-Optimize-ItemStack.isEmpty.patch b/Remapped-Spigot-Server-Patches/0128-Optimize-ItemStack.isEmpty.patch new file mode 100644 index 000000000..329afa740 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0128-Optimize-ItemStack.isEmpty.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Dec 2016 03:48:29 -0500 +Subject: [PATCH] Optimize ItemStack.isEmpty() + +Remove hashMap lookup every check, simplify code to remove ternary + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 123025c6dc9a2eea56c7db5cb508cdfd7c6cc97b..a0815c0d7f68f345dc48c73b8253de637c7a3e0f 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -206,7 +206,7 @@ public final class ItemStack { + } + + public boolean isEmpty() { +- return this == ItemStack.EMPTY ? true : (this.getItem() != null && this.getItem() != Items.AIR ? this.count <= 0 : true); ++ return this == ItemStack.NULL_ITEM || this.item == null || this.item == Items.AIR || this.count <= 0; // Paper + } + + public ItemStack split(int amount) { diff --git a/Remapped-Spigot-Server-Patches/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch b/Remapped-Spigot-Server-Patches/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch new file mode 100644 index 000000000..970e858ba --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch @@ -0,0 +1,106 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 21 Dec 2016 11:47:25 -0600 +Subject: [PATCH] Add API methods to control if armour stands can move + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 09d39b73e8a3987e58a502bd914a6451b807421b..46f0ebfc99352ec8b64bdff2c6bb8d17ecfeb619 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1,6 +1,5 @@ + package net.minecraft.world.entity; + +-import PathfinderGoalFloat; + import com.google.common.collect.Maps; + import java.util.Arrays; + import java.util.Iterator; +@@ -39,6 +38,7 @@ import net.minecraft.world.entity.ai.control.BodyRotationControl; + import net.minecraft.world.entity.ai.control.JumpControl; + import net.minecraft.world.entity.ai.control.LookControl; + import net.minecraft.world.entity.ai.control.MoveControl; ++import net.minecraft.world.entity.ai.goal.FloatGoal; + import net.minecraft.world.entity.ai.goal.Goal; + import net.minecraft.world.entity.ai.goal.GoalSelector; + import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; +@@ -47,6 +47,8 @@ import net.minecraft.world.entity.ai.sensing.Sensing; + import net.minecraft.world.entity.decoration.HangingEntity; + import net.minecraft.world.entity.decoration.LeashFenceKnotEntity; + import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.entity.monster.Blaze; ++import net.minecraft.world.entity.monster.EnderMan; + import net.minecraft.world.entity.monster.Enemy; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.vehicle.Boat; +@@ -97,7 +99,7 @@ public abstract class Mob extends LivingEntity { + private final BodyRotationControl bodyRotationControl; + protected PathNavigation navigation; + public GoalSelector goalSelector; +- @Nullable public PathfinderGoalFloat goalFloat; // Paper ++ @Nullable public FloatGoal goalFloat; // Paper + public GoalSelector targetSelector; + private LivingEntity target; + private final Sensing sensing; +@@ -789,7 +791,7 @@ public abstract class Mob extends LivingEntity { + if (goalFloat.validConditions()) goalFloat.update(); + this.getJumpControl().jumpIfSet(); + } +- if ((this instanceof EntityBlaze || this instanceof EntityEnderman) && isInWaterOrRainOrBubble()) { ++ if ((this instanceof Blaze || this instanceof EnderMan) && isInWaterOrRainOrBubble()) { + hurt(DamageSource.DROWN, 1.0F); + } + return; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 438dd75e3adffc395960b34b8bb26cbc07a4291e..8b6ec9ddf0d47bf4369b247e764f75893ab15781 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -26,6 +26,7 @@ import net.minecraft.world.entity.HumanoidArm; + import net.minecraft.world.entity.LightningBolt; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; ++import net.minecraft.world.entity.MoverType; + import net.minecraft.world.entity.Pose; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.vehicle.AbstractMinecart; +@@ -75,6 +76,7 @@ public class ArmorStand extends LivingEntity { + public Rotations rightArmPose; + public Rotations leftLegPose; + public Rotations rightLegPose; ++ public boolean canMove = true; // Paper + + public ArmorStand(EntityType type, Level world) { + super(type, world); +@@ -858,4 +860,13 @@ public class ArmorStand extends LivingEntity { + private EntityDimensions getDimensionsMarker(boolean flag) { + return flag ? ArmorStand.MARKER_DIMENSIONS : (this.isBaby() ? ArmorStand.BABY_DIMENSIONS : this.getType().getDimensions()); + } ++ ++ // Paper start ++ @Override ++ public void move(MoverType type, Vec3 movement) { ++ if (this.canMove) { ++ super.move(type, movement); ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 6b292162fb8c6416b1637b7b83e5113f6a35dbac..16f996d505b96da8a40c7709214ebbd2a0d0d9f3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -228,4 +228,15 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public boolean hasEquipmentLock(EquipmentSlot equipmentSlot, LockType lockType) { + return (getHandle().disabledSlots & (1 << CraftEquipmentSlot.getNMS(equipmentSlot).getFilterFlag() + lockType.ordinal() * 8)) != 0; + } ++ // Paper start ++ @Override ++ public boolean canMove() { ++ return getHandle().canMove; ++ } ++ ++ @Override ++ public void setCanMove(boolean move) { ++ getHandle().canMove = move; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0130-Properly-fix-item-duplication-bug.patch b/Remapped-Spigot-Server-Patches/0130-Properly-fix-item-duplication-bug.patch new file mode 100644 index 000000000..aa2398aa8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0130-Properly-fix-item-duplication-bug.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Tue, 27 Dec 2016 01:57:57 +0000 +Subject: [PATCH] Properly fix item duplication bug + +Credit to prplz for figuring out the real issue + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3cde25c2479adcc4ce3014e5ac2ec0710bffeea9..4ff66138fa43cf932b95d6d3dc050a9cd7b28ad4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2066,8 +2066,8 @@ public class ServerPlayer extends Player implements ContainerListener { + } + + @Override +- public boolean isImmobile() { +- return super.isImmobile() || !getBukkitEntity().isOnline(); ++ protected boolean isImmobile() { ++ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1b92c669bbe69bcc07a554b7b43ee99bfebc1af4..ecc393ad94332ec2a59d29f30bd60bade4e1f18e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2818,7 +2818,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + public final boolean isDisconnected() { +- return !this.player.joining && !this.connection.isConnected(); ++ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper + } + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0131-String-based-Action-Bar-API.patch b/Remapped-Spigot-Server-Patches/0131-String-based-Action-Bar-API.patch new file mode 100644 index 000000000..416d50b18 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0131-String-based-Action-Bar-API.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 27 Dec 2016 15:02:42 -0500 +Subject: [PATCH] String based Action Bar API + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 67fa685f4b8de3eae1431c0de399c246678b542a..7b36274718b7cce24ac00530697f145648d52590 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -58,7 +58,7 @@ public class Util { + private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); + private static final ExecutorService IO_POOL = makeIoExecutor(); + public static LongSupplier timeSource = System::nanoTime; +- public static final UUID NIL_UUID = new UUID(0L, 0L); ++ public static final UUID NIL_UUID = new UUID(0L, 0L); public static final UUID getNullUUID() {return NIL_UUID;} // Paper OBFHELPER + private static final Logger LOGGER = LogManager.getLogger(); + + public static Collector, ?, Map> toMap() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fc19b4cacd223b928fbdf922b828beaed630bf2e..d6d4e5ab7551f802dc2d3ab055d340d471ed0bc2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -242,6 +242,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + // Paper start ++ @Override ++ public void sendActionBar(BaseComponent[] message) { ++ if (getHandle().connection == null) return; ++ getHandle().connection.send(new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.ACTIONBAR, message, -1, -1, -1)); ++ } ++ ++ @Override ++ public void sendActionBar(String message) { ++ if (getHandle().connection == null || message == null || message.isEmpty()) return; ++ getHandle().connection.send(new ClientboundSetTitlesPacket(ClientboundSetTitlesPacket.Type.ACTIONBAR, CraftChatMessage.fromStringOrNull(message))); ++ } ++ ++ @Override ++ public void sendActionBar(char alternateChar, String message) { ++ if (message == null || message.isEmpty()) return; ++ sendActionBar(org.bukkit.ChatColor.translateAlternateColorCodes(alternateChar, message)); ++ } ++ + @Override + public void setPlayerListHeaderFooter(BaseComponent[] header, BaseComponent[] footer) { + if (header != null) { diff --git a/Remapped-Spigot-Server-Patches/0132-Firework-API-s.patch b/Remapped-Spigot-Server-Patches/0132-Firework-API-s.patch new file mode 100644 index 000000000..e3dc3d318 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0132-Firework-API-s.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 28 Dec 2016 07:18:33 +0100 +Subject: [PATCH] Firework API's + + +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index 4c8f249e45e5deb7628997d4dbd9dab613ac5241..a91bf94ed9f2f353a685194fc91c4b101ccc1232 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -153,6 +153,7 @@ public class CompoundTag implements Tag { + return NbtUtils.loadUUID(this.get(key)); + } + ++ public final boolean hasUUID(String s) { return this.hasUUID(s); } // Paper - OBFHELPER + public boolean hasUUID(String key) { + Tag nbtbase = this.get(key); + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +index 28a2c6a0fbc8b4c38f3899698504d8ca0d7ba3af..5669be107b580075fdffbcbb490513593a57fc9f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +@@ -37,7 +37,8 @@ public class FireworkRocketEntity extends Projectile { + public static final EntityDataAccessor DATA_SHOT_AT_ANGLE = SynchedEntityData.defineId(FireworkRocketEntity.class, EntityDataSerializers.BOOLEAN); + private int life; + public int lifetime; +- private LivingEntity attachedToEntity; ++ public LivingEntity attachedToEntity; // Paper - public ++ public java.util.UUID spawningEntity; // Paper + + public FireworkRocketEntity(EntityType type, Level world) { + super(type, world); +@@ -284,6 +285,11 @@ public class FireworkRocketEntity extends Projectile { + } + + tag.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE)); ++ // Paper start ++ if (this.spawningEntity != null) { ++ tag.setUUID("SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -300,7 +306,11 @@ public class FireworkRocketEntity extends Projectile { + if (tag.contains("ShotAtAngle")) { + this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, tag.getBoolean("ShotAtAngle")); + } +- ++ // Paper start ++ if (tag.hasUUID("SpawningEntity")) { ++ this.spawningEntity = tag.getUUID("SpawningEntity"); ++ } ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java +index 8a358872d3c8357775451e7dffe267cf9121f211..e1e58b7035e6dbafdad0a04cc5333464fc4febb8 100644 +--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java ++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java +@@ -205,6 +205,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + + if (flag1) { + object = new FireworkRocketEntity(world, projectile, shooter, shooter.getX(), shooter.getEyeY() - 0.15000000596046448D, shooter.getZ(), true); ++ ((FireworkRocketEntity) object).spawningEntity = shooter.getUUID(); // Paper + } else { + object = getArrow(world, shooter, crossbow, projectile); + if (creative || simulated != 0.0F) { +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index d3a045fc99ef77fa0905aac7c73a2e84772c73cf..dba52063d402eb2371441fa244b730a3313444fc 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -27,6 +27,7 @@ public class FireworkRocketItem extends Item { + Vec3 vec3d = context.getClickLocation(); + Direction enumdirection = context.getClickedFace(); + FireworkRocketEntity entityfireworks = new FireworkRocketEntity(world, context.getPlayer(), vec3d.x + (double) enumdirection.getStepX() * 0.15D, vec3d.y + (double) enumdirection.getStepY() * 0.15D, vec3d.z + (double) enumdirection.getStepZ() * 0.15D, itemstack); ++ entityfireworks.spawningEntity = context.getPlayer().getUUID(); // Paper + + world.addFreshEntity(entityfireworks); + itemstack.shrink(1); +@@ -41,7 +42,11 @@ public class FireworkRocketItem extends Item { + ItemStack itemstack = user.getItemInHand(hand); + + if (!world.isClientSide) { +- world.addFreshEntity(new FireworkRocketEntity(world, itemstack, user)); ++ // Paper start ++ final FireworkRocketEntity entityfireworks = new FireworkRocketEntity(world, itemstack, user); ++ entityfireworks.spawningEntity = user.getUUID(); ++ world.addFreshEntity(entityfireworks); ++ // Paper end + if (!user.abilities.instabuild) { + itemstack.shrink(1); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index 5901a53b25449430ed02a80b022f83755f83a440..0fbbdd6e3fda3f834d0b0d68d868dbff1aebb673 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.entity; + + import java.util.Random; ++import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.projectile.FireworkRocketEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; +@@ -78,4 +79,17 @@ public class CraftFirework extends CraftProjectile implements Firework { + public void setShotAtAngle(boolean shotAtAngle) { + getHandle().getEntityData().set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, shotAtAngle); + } ++ ++ // Paper start ++ @Override ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().spawningEntity; ++ } ++ ++ @Override ++ public org.bukkit.entity.LivingEntity getBoostedEntity() { ++ LivingEntity boostedEntity = getHandle().attachedToEntity; ++ return boostedEntity != null ? (org.bukkit.entity.LivingEntity) boostedEntity.getBukkitEntity() : null; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0133-PlayerTeleportEndGatewayEvent.patch b/Remapped-Spigot-Server-Patches/0133-PlayerTeleportEndGatewayEvent.patch new file mode 100644 index 000000000..98fb21f40 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0133-PlayerTeleportEndGatewayEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 31 Dec 2016 21:44:50 -0500 +Subject: [PATCH] PlayerTeleportEndGatewayEvent + +Allows you to access the Gateway being used in a teleport event + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index 07d89af8111673087b0534ca9fac043d3e89662b..2c974f9801d209907733bed8e6c4c9ef46e2b610 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -10,6 +10,7 @@ import net.minecraft.data.worldgen.Features; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; +@@ -177,7 +178,7 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity implements + location.setPitch(player.getLocation().getPitch()); + location.setYaw(player.getLocation().getYaw()); + +- PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.END_GATEWAY); ++ PlayerTeleportEvent teleEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(player, player.getLocation(), location, new org.bukkit.craftbukkit.block.CraftEndGateway(MCUtil.toLocation(level, this.getBlockPos()).getBlock())); // Paper + Bukkit.getPluginManager().callEvent(teleEvent); + if (teleEvent.isCancelled()) { + return; diff --git a/Remapped-Spigot-Server-Patches/0134-Provide-E-TE-Chunk-count-stat-methods.patch b/Remapped-Spigot-Server-Patches/0134-Provide-E-TE-Chunk-count-stat-methods.patch new file mode 100644 index 000000000..7b2dc85ab --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0134-Provide-E-TE-Chunk-count-stat-methods.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 Jan 2017 15:24:46 -0500 +Subject: [PATCH] Provide E/TE/Chunk count stat methods + +Provides counts without the ineffeciency of using .getEntities().size() +which creates copy of the collections. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ca189e5d160d2655175c9fab9366ff93bded2fee..6782888f7df4eea4e6378ee850424e14c5136afd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -264,6 +264,48 @@ public class CraftWorld implements World { + private int waterAmbientSpawn = -1; + private int ambientSpawn = -1; + ++ // Paper start - Provide fast information methods ++ public int getEntityCount() { ++ int ret = 0; ++ for (net.minecraft.world.entity.Entity entity : world.entitiesById.values()) { ++ if (entity.isChunkLoaded()) { ++ ++ret; ++ } ++ } ++ return ret; ++ } ++ public int getTileEntityCount() { ++ // We don't use the full world tile entity list, so we must iterate chunks ++ Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; ++ int size = 0; ++ for (ChunkHolder playerchunk : chunks.values()) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk(); ++ if (chunk == null) { ++ continue; ++ } ++ size += chunk.blockEntities.size(); ++ } ++ return size; ++ } ++ public int getTickableTileEntityCount() { ++ return world.tickableBlockEntities.size(); ++ } ++ public int getChunkCount() { ++ int ret = 0; ++ ++ for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) { ++ if (chunkHolder.getTickingChunk() != null) { ++ ++ret; ++ } ++ } ++ ++ return ret; ++ } ++ public int getPlayerCount() { ++ return world.players.size(); ++ } ++ // Paper end ++ + private static final Random rand = new Random(); + + public CraftWorld(ServerLevel world, ChunkGenerator gen, Environment env) { diff --git a/Remapped-Spigot-Server-Patches/0135-Enforce-Sync-Player-Saves.patch b/Remapped-Spigot-Server-Patches/0135-Enforce-Sync-Player-Saves.patch new file mode 100644 index 000000000..09ab2be15 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0135-Enforce-Sync-Player-Saves.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 Jan 2017 15:41:58 -0500 +Subject: [PATCH] Enforce Sync Player Saves + +Saving players async is extremely dangerous. This will force it to main +the same way we handle async chunk loads. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 59fb19cfebe4f488fd02f02db31029d44b65e408..cebf536e9d16d44c4b2a91b5b4be053cd7f44045 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1043,11 +1043,13 @@ public abstract class PlayerList { + } + + public void saveAll() { ++ MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper + for (int i = 0; i < this.players.size(); ++i) { +- this.save((ServerPlayer) this.players.get(i)); ++ this.savePlayerFile((EntityPlayer) this.players.get(i)); + } + MinecraftTimings.savePlayers.stopTiming(); // Paper ++ return null; }); // Paper - ensure main + } + + public UserWhiteList getWhiteList() { diff --git a/Remapped-Spigot-Server-Patches/0136-Don-t-allow-entities-to-ride-themselves-572.patch b/Remapped-Spigot-Server-Patches/0136-Don-t-allow-entities-to-ride-themselves-572.patch new file mode 100644 index 000000000..da9f57d58 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0136-Don-t-allow-entities-to-ride-themselves-572.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Sun, 8 Jan 2017 04:31:36 +0000 +Subject: [PATCH] Don't allow entities to ride themselves - #572 + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c3aece8e5001828edea304b2a8377e9a28b34cfe..a9ed1c3ce6361a86dd58501f5b0ce5d6bbfb8adf 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2045,6 +2045,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + protected boolean addPassenger(Entity entity) { // CraftBukkit ++ if (entity == this) throw new IllegalArgumentException("Entities cannot become a passenger of themselves"); // Paper - issue 572 + if (entity.getVehicle() != this) { + throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); + } else { diff --git a/Remapped-Spigot-Server-Patches/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/Remapped-Spigot-Server-Patches/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch new file mode 100644 index 000000000..44aa5bf7e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch @@ -0,0 +1,332 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 16:31:46 -0500 +Subject: [PATCH] ExperienceOrbs API for Reason/Source/Triggering player + +Adds lots of information about why this orb exists. + +Replaces isFromBottle() with logic that persists entity reloads too. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index ed4309d5e567b20fd4aa025e7c82d8943bf1d8e1..26ce794cb8d089c03fab5dd0a0c910783d10b72e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -409,7 +409,7 @@ public class ServerPlayerGameMode { + + // Drop event experience + if (flag && event != null) { +- iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop()); ++ iblockdata.getBlock().dropExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } + + return true; +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index f932fc4f8240b48f8e518af05d1521bc8ff9cbee..3ddb0a9f15c920c9a2080f76edfda0504c1e287a 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -30,13 +30,63 @@ public class ExperienceOrb extends Entity { + public int value; + private Player followingPlayer; + private int followingTime; ++ // Paper start ++ public java.util.UUID sourceEntityId; ++ public java.util.UUID triggerEntityId; ++ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ ++ private void loadPaperNBT(CompoundTag nbttagcompound) { ++ if (!nbttagcompound.contains("Paper.ExpData", 10)) { // 10 = compound ++ return; ++ } ++ CompoundTag comp = nbttagcompound.getCompound("Paper.ExpData"); ++ if (comp.hasUUID("source")) { ++ this.sourceEntityId = comp.getUUID("source"); ++ } ++ if (comp.hasUUID("trigger")) { ++ this.triggerEntityId = comp.getUUID("trigger"); ++ } ++ if (comp.contains("reason")) { ++ String reason = comp.getString("reason"); ++ try { ++ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason); ++ } catch (Exception e) { ++ this.level.getCraftServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason); ++ } ++ } ++ } ++ private void savePaperNBT(CompoundTag nbttagcompound) { ++ CompoundTag comp = new CompoundTag(); ++ if (this.sourceEntityId != null) { ++ comp.setUUID("source", this.sourceEntityId); ++ } ++ if (this.triggerEntityId != null) { ++ comp.setUUID("trigger", triggerEntityId); ++ } ++ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) { ++ comp.putString("reason", this.spawnReason.name()); ++ } ++ nbttagcompound.put("Paper.ExpData", comp); ++ } + + public ExperienceOrb(Level world, double x, double y, double z, int amount) { ++ this(world, x, y, z, amount, null, null); ++ } ++ ++ public EntityExperienceOrb(Level world, double d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { ++ this(world, d0, d1, d2, i, reason, triggerId, null); ++ } ++ ++ public EntityExperienceOrb(Level world, double d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { + this(EntityType.EXPERIENCE_ORB, world); +- this.setPos(x, y, z); ++ this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null; ++ this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null; ++ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // Paper end ++ this.setPos(d0, d1, d2); + this.yRot = (float) (this.random.nextDouble() * 360.0D); + this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D); +- this.value = amount; ++ this.value = i; + } + + public ExperienceOrb(EntityType type, Level world) { +@@ -167,6 +217,7 @@ public class ExperienceOrb extends Entity { + tag.putShort("Health", (short) this.health); + tag.putShort("Age", (short) this.age); + tag.putShort("Value", (short) this.value); ++ this.savePaperNBT(tag); // Paper + } + + @Override +@@ -174,6 +225,7 @@ public class ExperienceOrb extends Entity { + this.health = tag.getShort("Health"); + this.age = tag.getShort("Age"); + this.value = tag.getShort("Value"); ++ this.loadPaperNBT(tag); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 1131d86080b3100437aa18a00c6277fcea4b7ea8..c6aa5328907f85cd210b1c20ff407e60d9b03349 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1591,7 +1591,8 @@ public abstract class LivingEntity extends Entity { + int j = ExperienceOrb.getExperienceValue(i); + + i -= j; +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j)); ++ LivingEntity attacker = lastHurtByPlayer != null ? lastHurtByPlayer : lastHurtByMob; // Paper ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this)); // Paper + } + this.expToDrop = 0; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java +index ab2a19554aa1541e924104a70364f957ff8b33f9..e0f2a70870ff97ae2e8f216a202787bbcba6c6a9 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java +@@ -260,7 +260,7 @@ public abstract class Animal extends AgableMob { + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience)); ++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 83fcfd888a335e3c054174e1f55e92fea878f7ab..c2d98222f575d7383e4c040730f6d531bdb0d7b6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -1306,7 +1306,7 @@ public class Fox extends Animal { + if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 2ae59200ed67ab68645b569ba03839e8cedc9aa8..c54f4b83b9f2fdb15ddb363be0a179a05eb3693b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -561,7 +561,7 @@ public class Turtle extends Animal { + Random random = this.animal.getRandom(); + + if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), random.nextInt(7) + 1)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), random.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 39298b69918da890c3faa516f80d1a69adb88fe2..ae3cf71f14526e1f356216dfaa899c8f5083d46d 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -661,7 +661,7 @@ public class EnderDragon extends Mob implements Enemy { + int j = ExperienceOrb.getExperienceValue(amount); + + amount -= j; +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j, org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 5648a4a4d8511ac8c46c61245a7ff83753a3e51f..a66fab2e04a5d87ced139ed15d2434c5ffcec695 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -599,7 +599,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + + if (offer.shouldRewardExp()) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index 15570b9ba2443ce8c6f48dfbc13cdf45de8b45ac..69d92590d265abe8a04d8bf48bbe9a6ae606ae50 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -188,7 +188,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + if (offer.shouldRewardExp()) { + int i = 3 + this.random.nextInt(4); + +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index fa078167dd9e0cae80516549eef0e554c13938a3..7bff012f3cd4458673ee02e5f5f830fc0ef983a3 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -500,7 +500,7 @@ public class FishingHook extends Projectile { + this.level.addFreshEntity(entityitem); + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { +- entityhuman.level.addFreshEntity(new ExperienceOrb(entityhuman.level, entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop())); ++ entityhuman.level.addFreshEntity(new ExperienceOrb(entityhuman.level, entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper + } + // CraftBukkit end + if (itemstack1.getItem().is((Tag) ItemTags.FISHES)) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +index 85b8f8f52c5035054ad9f665fce735260a54c270..42c7371355b6e36e31daf055317f015240761b8b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +@@ -54,7 +54,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile { + int j = ExperienceOrb.getExperienceValue(i); + + i -= j; +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY(), this.getZ(), j, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, getOwner(), this)); // Paper + } + + this.remove(); +diff --git a/src/main/java/net/minecraft/world/inventory/FurnaceResultSlot.java b/src/main/java/net/minecraft/world/inventory/FurnaceResultSlot.java +index ea6e1a96bd1fa9fbb87f65a169aa1e5af0589f34..5b9111d502bc12ab9e5c37e4d66c21aa37007b53 100644 +--- a/src/main/java/net/minecraft/world/inventory/FurnaceResultSlot.java ++++ b/src/main/java/net/minecraft/world/inventory/FurnaceResultSlot.java +@@ -7,7 +7,7 @@ import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; + + public class FurnaceResultSlot extends Slot { + +- private final Player player; ++ private final Player player; public final Player getPlayer() { return this.player; } // Paper OBFHELPER + private int removeCount; + + public FurnaceResultSlot(Player player, Container inventory, int index, int x, int y) { +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index e8bc37e1f7aebd192f048d7b056a41c50ceef9f5..e9e830117fe3e4e02a51eef8671a3d3b48c2858e 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -93,7 +93,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + int k = ExperienceOrb.getExperienceValue(j); + + j -= k; +- world.addFreshEntity(new ExperienceOrb(world, (double) blockposition.getX(), (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, k)); ++ world.addFreshEntity(new ExperienceOrb(world, (double) blockposition.getX(), (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, k, org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player)); // Paper + } + + world.levelEvent(1042, blockposition, 0); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 2ae786b8fc6da19ca2a40252b0606f9e06d31ded..9d2e4adddae481735053c64eec0ee7259c61f1a4 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -267,13 +267,13 @@ public class Block extends BlockBehaviour implements ItemLike { + } + } + +- public void popExperience(ServerLevel world, BlockPos pos, int size) { +- if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { +- while (size > 0) { +- int j = ExperienceOrb.getExperienceValue(size); ++ public void dropExperience(ServerLevel worldserver, BlockPos blockposition, int i, net.minecraft.server.level.ServerPlayer player) { // Paper ++ if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { ++ while (i > 0) { ++ int j = ExperienceOrb.getExperienceValue(i); + +- size -= j; +- world.addFreshEntity(new ExperienceOrb(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, j)); ++ i -= j; ++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, j, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, player)); // Paper + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 7a2554c5cd18e0c5e482ba8ba68a098d533b6a4f..8c55c1d88ef2e20e82bcdae0b9b3d381e562051f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -601,7 +601,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + int k = ExperienceOrb.getExperienceValue(j); + + j -= k; +- world.addFreshEntity(new ExperienceOrb(world, vec3d.x, vec3d.y, vec3d.z, k)); ++ world.addFreshEntity(new ExperienceOrb(world, vec3d.x, vec3d.y, vec3d.z, k, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman)); // Paper + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 6782888f7df4eea4e6378ee850424e14c5136afd..88658d4deacc29128c537e2e02fdc8f684090a2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1817,7 +1817,7 @@ public class CraftWorld implements World { + } else if (TNTPrimed.class.isAssignableFrom(clazz)) { + entity = new PrimedTnt(world, x, y, z, null); + } else if (ExperienceOrb.class.isAssignableFrom(clazz)) { +- entity = new net.minecraft.world.entity.ExperienceOrb(world, x, y, z, 0); ++ entity = new net.minecraft.world.entity.ExperienceOrb(world, x, y, z, 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null); // Paper + } else if (LightningStrike.class.isAssignableFrom(clazz)) { + entity = net.minecraft.world.entity.EntityType.LIGHTNING_BOLT.create(world); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +index 3b450d97302bab30cdb975c8332b81318470503e..d5b8fd76ec3bd7d2621231480eb3e694a90aa037 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import SpawnReason; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.ExperienceOrb; +@@ -19,6 +20,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + getHandle().value = value; + } + ++ // Paper start ++ public java.util.UUID getTriggerEntityId() { ++ return getHandle().triggerEntityId; ++ } ++ public java.util.UUID getSourceEntityId() { ++ return getHandle().sourceEntityId; ++ } ++ public SpawnReason getSpawnReason() { ++ return getHandle().spawnReason; ++ } ++ // Paper end ++ + @Override + public net.minecraft.world.entity.ExperienceOrb getHandle() { + return (net.minecraft.world.entity.ExperienceOrb) entity; diff --git a/Remapped-Spigot-Server-Patches/0138-Cap-Entity-Collisions.patch b/Remapped-Spigot-Server-Patches/0138-Cap-Entity-Collisions.patch new file mode 100644 index 000000000..7b28bfe32 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0138-Cap-Entity-Collisions.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 22 Jan 2017 18:07:56 -0500 +Subject: [PATCH] Cap Entity Collisions + +Limit a single entity to colliding a max of configurable times per tick. +This will alleviate issues where living entities are hoarded in 1x1 pens + +This is not tied to the maxEntityCramming rule. Cramming will still apply +just as it does in Vanilla, but entity pushing logic will be capped. + +You can set this to 0 to disable collisions. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2dc58b9f769ea43b737804456aafab47ecc143b8..c611b5a63498f5ad1f50a75ccd5d7299e27df7e3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -324,4 +324,10 @@ public class PaperWorldConfig { + log("Treasure Maps will return already discovered locations"); + } + } ++ ++ public int maxCollisionsPerEntity; ++ private void maxEntityCollision() { ++ maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); ++ log( "Max Entity Collisions: " + maxCollisionsPerEntity ); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a9ed1c3ce6361a86dd58501f5b0ce5d6bbfb8adf..a017fa55002d6674124befa3f6e81eb70c9ce8f7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -267,6 +267,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index c6aa5328907f85cd210b1c20ff407e60d9b03349..3908f54e2216c635d47f8256bac455e7207a5bc6 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2900,8 +2900,11 @@ public abstract class LivingEntity extends Entity { + } + } + +- for (j = 0; j < list.size(); ++j) { ++ numCollisions = Math.max(0, numCollisions - level.paperConfig.maxCollisionsPerEntity); // Paper ++ for (j = 0; j < list.size() && numCollisions < level.paperConfig.maxCollisionsPerEntity; ++j) { // Paper + Entity entity = (Entity) list.get(j); ++ entity.numCollisions++; // Paper ++ numCollisions++; // Paper + + this.doPush(entity); + } diff --git a/Remapped-Spigot-Server-Patches/0139-Remove-CraftScheduler-Async-Task-Debugger.patch b/Remapped-Spigot-Server-Patches/0139-Remove-CraftScheduler-Async-Task-Debugger.patch new file mode 100644 index 000000000..db58842a9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0139-Remove-CraftScheduler-Async-Task-Debugger.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Feb 2017 00:04:04 -0500 +Subject: [PATCH] Remove CraftScheduler Async Task Debugger + +I have not once ever seen this system help debug a crash. +One report of a suspected memory leak with the system. + +This adds additional overhead to asynchronous task dispatching + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 9b6d9373abb59a30c2835ca891282d07559281f5..0e0f361c3af363539d5d1d865603114bdb84fd67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -430,7 +430,7 @@ public class CraftScheduler implements BukkitScheduler { + } + parsePending(); + } else { +- debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); ++ //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper + executor.execute(new ServerSchedulerReportingWrapper(task)); // 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) +@@ -447,7 +447,7 @@ public class CraftScheduler implements BukkitScheduler { + pending.addAll(temp); + temp.clear(); + MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); +- debugHead = debugHead.getNextHead(currentTick); ++ //debugHead = debugHead.getNextHead(currentTick); // Paper + } + + private void addTask(final CraftTask task) { +@@ -507,10 +507,15 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public String toString() { ++ // Paper start ++ return ""; ++ /* + int debugTick = currentTick; + StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{'); + debugHead.debugTo(string); + return string.append('}').toString(); ++ */ ++ // Paper end + } + + @Deprecated diff --git a/Remapped-Spigot-Server-Patches/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch b/Remapped-Spigot-Server-Patches/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch new file mode 100644 index 000000000..740c39b43 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 7 Feb 2017 16:55:35 -0600 +Subject: [PATCH] Make targetSize more aggressive in the chunk unload queue + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 175bf535066afc42de8a3f0d11c46af66f3e3e52..3b6f35b695117bd2b0c71b994efc55fa1084eddc 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -119,7 +119,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final PlayerMap playerMap; + public final Int2ObjectMap entityMap; + private final Long2ByteMap chunkTypeCache; +- private final Queue unloadQueue; ++ private final Queue unloadQueue; private final Queue getUnloadQueueTasks() { return this.unloadQueue; } // Paper - OBFHELPER + private int viewDistance; + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() +@@ -177,7 +177,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMap = new PlayerMap(); + this.entityMap = new Int2ObjectOpenHashMap(); + this.chunkTypeCache = new Long2ByteOpenHashMap(); +- this.unloadQueue = Queues.newConcurrentLinkedQueue(); ++ this.unloadQueue = new com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue<>(); // Paper - need constant-time size() + this.structureManager = definedstructuremanager; + this.storageFolder = convertable_conversionsession.getDimensionPath(worldserver.dimension()); + this.level = worldserver; +@@ -435,7 +435,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Spigot start + org.spigotmc.SlackActivityAccountant activityAccountant = this.level.getServer().slackActivityAccountant; + activityAccountant.startActivity(0.5); +- int targetSize = (int) (this.toDrop.size() * UNLOAD_QUEUE_RESIZE_FACTOR); ++ int targetSize = Math.min(this.toDrop.size() - 100, (int) (this.toDrop.size() * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive + // Spigot end + while (longiterator.hasNext()) { // Spigot + long j = longiterator.nextLong(); +@@ -457,7 +457,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + Runnable runnable; + +- while ((shouldKeepTicking.getAsBoolean() || this.unloadQueue.size() > 2000) && (runnable = (Runnable) this.unloadQueue.poll()) != null) { ++ int queueTarget = Math.min(this.getUnloadQueueTasks().size() - 100, (int) (this.getUnloadQueueTasks().size() * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Target this queue as well ++ while ((shouldKeepTicking.getAsBoolean() || this.getUnloadQueueTasks().size() > queueTarget) && (runnable = (Runnable)this.getUnloadQueueTasks().poll()) != null) { // Paper - Target this queue as well + runnable.run(); + } + diff --git a/Remapped-Spigot-Server-Patches/0141-Do-not-let-armorstands-drown.patch b/Remapped-Spigot-Server-Patches/0141-Do-not-let-armorstands-drown.patch new file mode 100644 index 000000000..d8f5fab54 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0141-Do-not-let-armorstands-drown.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 18 Feb 2017 19:29:58 -0600 +Subject: [PATCH] Do not let armorstands drown + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 3908f54e2216c635d47f8256bac455e7207a5bc6..c1786fcabeaee5732e9197db04268c5c4e164339 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -335,6 +335,7 @@ public abstract class LivingEntity extends Entity { + super.checkFallDamage(heightDifference, onGround, landedState, landedPosition); + } + ++ public boolean canBreatheUnderwater() { return this.canBreatheUnderwater(); } // Paper - OBFHELPER + public boolean canBreatheUnderwater() { + return this.getMobType() == MobType.UNDEAD; + } +@@ -378,7 +379,7 @@ public abstract class LivingEntity extends Entity { + + if (this.isAlive()) { + if (this.isEyeInFluid((Tag) FluidTags.WATER) && !this.level.getBlockState(new BlockPos(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) { +- if (!this.canBreatheUnderwater() && !MobEffectUtil.c(this) && !flag1) { ++ if (!this.canBreatheUnderwater() && !MobEffectUtil.c(this) && !flag1) { // Paper - use OBFHELPER so it can be overridden + this.setAirSupply(this.decreaseAirSupply(this.getAirSupply())); + if (this.getAirSupply() == -20) { + this.setAirSupply(0); +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 8b6ec9ddf0d47bf4369b247e764f75893ab15781..59239e202e8e99870ce3be515d2f040ad9786892 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -868,5 +868,10 @@ public class ArmorStand extends LivingEntity { + super.move(type, movement); + } + } ++ ++ @Override ++ public boolean canBreatheUnderwater() { // Skips a bit of damage handling code, probably a micro-optimization ++ return true; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0142-Properly-handle-async-calls-to-restart-the-server.patch b/Remapped-Spigot-Server-Patches/0142-Properly-handle-async-calls-to-restart-the-server.patch new file mode 100644 index 000000000..761e1f8b8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0142-Properly-handle-async-calls-to-restart-the-server.patch @@ -0,0 +1,307 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 12 May 2017 23:34:11 -0500 +Subject: [PATCH] Properly handle async calls to restart the server + +The watchdog thread calls the server restart function asynchronously. Prior to +this change, it attempted to do several non-safe operations from the watchdog +thread, rather than the main. Specifically, because of a separate upstream change, +it causes player entities to be ticked asynchronously, among other things. + +This is dangerous. + +This patch moves the old handling into a synchronous variant, for calls from the +restart command, and adds separate handling for async calls, such as those from +the watchdog thread. + +When calling from the watchdog thread, we cannot assume the main thread is in a +tickable state; it may be completely deadlocked. In order to handle this, we mark +the server as stopping, in order to account for situations where the server should +complete a tick reasonbly soon, i.e. 99% of cases. + +Should the server not enter a state where it is stopping within 10 seconds, We +will assume that the server has in fact deadlocked and will proceed to force +kill the server. + +This modification does not force restart the server should we actually enter a +deadlocked state where the server is stopping, whereas this will in most cases +exit within a reasonable amount of time, to put a fixed limit on a process that +will have plugins and worlds saving to the disk has a high potential to result +in corruption/dataloss. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 211251fe7cd08074c040df2f4642f37d5f90d856..f41d79c6630fd8daae28476ffc854f7e65d841e6 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -199,6 +199,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> levels; + private PlayerList playerList; + private volatile boolean running; ++ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart + private boolean stopped; + private int tickCount; + protected final Proxy proxy; +@@ -858,7 +859,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && new File( split[0] ).isFile() ) ++ // Paper - extract method and cleanup ++ boolean isRestarting = addShutdownHook( restartScript ); ++ if ( isRestarting ) + { +- System.out.println( "Attempting to restart with " + restartScript ); ++ System.out.println( "Attempting to restart with " + SpigotConfig.restartScript ); ++ } else ++ { ++ System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); ++ } ++ // Stop the watchdog ++ WatchdogThread.doStop(); + +- // Disable Watchdog +- WatchdogThread.doStop(); ++ shutdownServer( isRestarting ); ++ // Paper end ++ } catch ( Exception ex ) ++ { ++ ex.printStackTrace(); ++ } ++ } + +- // Kick all players +- for ( ServerPlayer p : (List) MinecraftServer.getServer().getPlayerList().players ) +- { +- p.connection.disconnect(SpigotConfig.restartMessage); +- } +- // Give the socket a chance to send the packets +- try +- { +- Thread.sleep( 100 ); +- } catch ( InterruptedException ex ) +- { +- } +- // Close the socket so we can rebind with the new process +- MinecraftServer.getServer().getConnection().stop(); ++ // Paper start - sync copied from above with minor changes, async added ++ private static void shutdownServer(boolean isRestarting) ++ { ++ if ( MinecraftServer.getServer().isSameThread() ) ++ { ++ // Kick all players ++ for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) ++ { ++ p.connection.disconnect(SpigotConfig.restartMessage); ++ } ++ // Give the socket a chance to send the packets ++ try ++ { ++ Thread.sleep( 100 ); ++ } catch ( InterruptedException ex ) ++ { ++ } + +- // Give time for it to kick in +- try +- { +- Thread.sleep( 100 ); +- } catch ( InterruptedException ex ) +- { +- } ++ closeSocket(); + +- // Actually shutdown +- try +- { +- MinecraftServer.getServer().close(); +- } catch ( Throwable t ) +- { +- } ++ // Actually shutdown ++ try ++ { ++ MinecraftServer.getServer().close(); // calls stop() ++ } catch ( Throwable t ) ++ { ++ } ++ ++ // Actually stop the JVM ++ System.exit( 0 ); + +- // This will be done AFTER the server has completely halted +- Thread shutdownHook = new Thread() ++ } else ++ { ++ // Mark the server to shutdown at the end of the tick ++ MinecraftServer.getServer().safeShutdown( false, isRestarting ); ++ ++ // wait 10 seconds to see if we're actually going to try shutdown ++ try ++ { ++ Thread.sleep( 10000 ); ++ } ++ catch (InterruptedException ignored) ++ { ++ } ++ ++ // Check if we've actually hit a state where the server is going to safely shutdown ++ // if we have, let the server stop as usual ++ if (MinecraftServer.getServer().isStopped()) return; ++ ++ // If the server hasn't stopped by now, assume worse case and kill ++ closeSocket(); ++ System.exit( 0 ); ++ } ++ } ++ // Paper end ++ ++ // Paper - Split from moved code ++ private static void closeSocket() ++ { ++ // Close the socket so we can rebind with the new process ++ MinecraftServer.getServer().getConnection().stop(); ++ ++ // Give time for it to kick in ++ try ++ { ++ Thread.sleep( 100 ); ++ } catch ( InterruptedException ex ) ++ { ++ } ++ } ++ // Paper end ++ ++ // Paper start - copied from above and modified to return if the hook registered ++ private static boolean addShutdownHook(String restartScript) ++ { ++ String[] split = restartScript.split( " " ); ++ if ( split.length > 0 && new File( split[0] ).isFile() ) ++ { ++ Thread shutdownHook = new Thread() ++ { ++ @Override ++ public void run() + { +- @Override +- public void run() ++ try + { +- try ++ String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); ++ if ( os.contains( "win" ) ) + { +- String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); +- if ( os.contains( "win" ) ) +- { +- Runtime.getRuntime().exec( "cmd /c start " + restartScript ); +- } else +- { +- Runtime.getRuntime().exec( "sh " + restartScript ); +- } +- } catch ( Exception e ) ++ Runtime.getRuntime().exec( "cmd /c start " + restartScript ); ++ } else + { +- e.printStackTrace(); ++ Runtime.getRuntime().exec( "sh " + restartScript ); + } ++ } catch ( Exception e ) ++ { ++ e.printStackTrace(); + } +- }; +- +- shutdownHook.setDaemon( true ); +- Runtime.getRuntime().addShutdownHook( shutdownHook ); +- } else +- { +- System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); +- +- // Actually shutdown +- try +- { +- MinecraftServer.getServer().close(); +- } catch ( Throwable t ) +- { + } +- } +- System.exit( 0 ); +- } catch ( Exception ex ) ++ }; ++ ++ shutdownHook.setDaemon( true ); ++ Runtime.getRuntime().addShutdownHook( shutdownHook ); ++ return true; ++ } else + { +- ex.printStackTrace(); ++ return false; + } + } ++ // Paper end ++ + } diff --git a/Remapped-Spigot-Server-Patches/0143-Add-system-property-to-disable-book-size-limits.patch b/Remapped-Spigot-Server-Patches/0143-Add-system-property-to-disable-book-size-limits.patch new file mode 100644 index 000000000..ebfc39ca7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0143-Add-system-property-to-disable-book-size-limits.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 13 May 2017 20:11:21 -0500 +Subject: [PATCH] Add system property to disable book size limits + +If anyone comes in with a watchdog crash related to books after this patch +you will not only be publicly shamed but also made an example of. + +Disables the security limits on books entirely, allowing plugins AND players +to make books with as much data as they want. Do not use this without +limiting incoming data from packets in some other way. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index a592d4a286a775a61192dde2a4d21a0681090415..80397e223990f11c9aa413f3f4ebd7c1b8ce1cff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -43,6 +43,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + static final int MAX_PAGES = 100; + static final int MAX_PAGE_LENGTH = 320; // 256 limit + 64 characters to allow for psuedo colour codes + static final int MAX_TITLE_LENGTH = 32; ++ private static final boolean OVERRIDE_CHECKS = Boolean.getBoolean("disable.book-limits"); // Paper - Add override + + protected String title; + protected String author; +@@ -245,7 +246,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + if (title == null) { + this.title = null; + return true; +- } else if (title.length() > MAX_TITLE_LENGTH) { ++ } else if (title.length() > MAX_TITLE_LENGTH && !OVERRIDE_CHECKS) { // Paper - Add override + return false; + } + +@@ -442,7 +443,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + String validatePage(String page) { + if (page == null) { + page = ""; +- } else if (page.length() > MAX_PAGE_LENGTH) { ++ } else if (page.length() > MAX_PAGE_LENGTH && !OVERRIDE_CHECKS) { // Paper - Add override + page = page.substring(0, MAX_PAGE_LENGTH); + } + return page; +@@ -452,7 +453,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + // asserted: page != null + if (this.pages == null) { + this.pages = new ArrayList(); +- } else if (this.pages.size() >= MAX_PAGES) { ++ } else if (this.pages.size() >= MAX_PAGES && !OVERRIDE_CHECKS) {// Paper - Add override + return; + } + this.pages.add(page); diff --git a/Remapped-Spigot-Server-Patches/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/Remapped-Spigot-Server-Patches/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch new file mode 100644 index 000000000..0e3605cc7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 16 May 2017 21:29:08 -0500 +Subject: [PATCH] Add option to make parrots stay on shoulders despite movement + +Makes parrots not fall off whenever the player changes height, or touches water, or gets hit by a passing leaf. +Instead, switches the behavior so that players have to sneak to make the birds leave. + +I suspect Mojang may switch to this behavior before full release. + +To be converted into a Paper-API event at some point in the future? + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c611b5a63498f5ad1f50a75ccd5d7299e27df7e3..9d1cddc6038f0fd0286e4a32013ae98ff0b00dd1 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -330,4 +330,10 @@ public class PaperWorldConfig { + maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); + log( "Max Entity Collisions: " + maxCollisionsPerEntity ); + } ++ ++ public boolean parrotsHangOnBetter; ++ private void parrotsHangOnBetter() { ++ parrotsHangOnBetter = getBoolean("parrots-are-unaffected-by-player-movement", false); ++ log("Parrots are unaffected by player movement: " + parrotsHangOnBetter); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ecc393ad94332ec2a59d29f30bd60bade4e1f18e..6a922e3522ac99a8e317a5f5f51fbb597baaf63e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2051,6 +2051,13 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + switch (packet.getAction()) { + case PRESS_SHIFT_KEY: + this.player.setShiftKeyDown(true); ++ ++ // Paper start - Hang on! ++ if (this.player.level.paperConfig.parrotsHangOnBetter) { ++ this.player.removeEntitiesOnShoulder(); ++ } ++ // Paper end ++ + break; + case RELEASE_SHIFT_KEY: + this.player.setShiftKeyDown(false); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index c11d5aa115d10e3c12863cf9d42c60194d63b690..ae10c531ae69eaf6b78a342dcedb89c39fd8dbcc 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -529,7 +529,7 @@ public abstract class Player extends LivingEntity { + this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft()); + this.playShoulderEntityAmbientSound(this.getShoulderEntityRight()); + if (!this.level.isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping()) { +- this.removeEntitiesOnShoulder(); ++ if (!this.level.paperConfig.parrotsHangOnBetter) this.removeEntitiesOnShoulder(); // Paper - Hang on! + } + + } diff --git a/Remapped-Spigot-Server-Patches/0145-Add-configuration-option-to-prevent-player-names-fro.patch b/Remapped-Spigot-Server-Patches/0145-Add-configuration-option-to-prevent-player-names-fro.patch new file mode 100644 index 000000000..2f0b8509c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0145-Add-configuration-option-to-prevent-player-names-fro.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Fri, 9 Jun 2017 07:24:34 -0700 +Subject: [PATCH] Add configuration option to prevent player names from being + suggested + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 4e2f243faa209925dcb7c3ef89df3ed875c5ff78..48319aaf1c525c6fb7bdee5c2f570a0d056d4eae 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -268,4 +268,9 @@ public class PaperConfig { + flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage); + flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage); + } ++ ++ public static boolean suggestPlayersWhenNullTabCompletions = true; ++ private static void suggestPlayersWhenNull() { ++ suggestPlayersWhenNullTabCompletions = getBoolean("settings.suggest-player-names-when-null-tab-completions", suggestPlayersWhenNullTabCompletions); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 9365fd2bcf74755c90c4ae9b550969e97a22c639..d198dad80e0fb41a5bde66944d0e41509a9c1c43 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2300,5 +2300,10 @@ public final class CraftServer implements Server { + commandMap.registerServerAliases(); + return true; + } ++ ++ @Override ++ public boolean suggestPlayerNamesWhenNullTabCompletions() { ++ return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0146-Use-TerminalConsoleAppender-for-console-improvements.patch b/Remapped-Spigot-Server-Patches/0146-Use-TerminalConsoleAppender-for-console-improvements.patch new file mode 100644 index 000000000..d43b61bcf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0146-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -0,0 +1,558 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Fri, 9 Jun 2017 19:03:43 +0200 +Subject: [PATCH] Use TerminalConsoleAppender for console improvements + +Rewrite console improvements (console colors, tab completion, +persistent input line, ...) using JLine 3.x and TerminalConsoleAppender. + +New features: + - Support console colors for Vanilla commands + - Add console colors for warnings and errors + - Server can now be turned off safely using CTRL + C. JLine catches + the signal and the implementation shuts down the server cleanly. + - Support console colors and persistent input line when running in + IntelliJ IDEA + +Other changes: + - Server starts 1-2 seconds faster thanks to optimizations in Log4j + configuration + +diff --git a/pom.xml b/pom.xml +index a5d87d22cb1588d15e08da3b37e51c5e261c7799..3841fe3630c090f8a468333d43caeb2b5841329d 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -57,10 +57,26 @@ + compile + + +- jline +- jline +- 2.12.1 +- compile ++ net.minecrell ++ terminalconsoleappender ++ 1.2.0 ++ ++ ++ org.jline ++ jline-terminal-jansi ++ 3.12.1 ++ runtime ++ ++ ++ ++ org.apache.logging.log4j ++ log4j-core ++ runtime + + + org.apache.logging.log4j +@@ -334,10 +350,18 @@ + + META-INF/services/java.sql.Driver + ++ + + + + ++ ++ ++ com.github.edwgiz ++ maven-shade-plugin.log4j2-cachefile-transformer ++ 2.13.1 ++ ++ + + + org.apache.maven.plugins +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4070b59e261f0f1ac4beec47b11492f4724bf27 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.console; ++ ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecrell.terminalconsole.SimpleTerminalConsole; ++import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; ++import org.jline.reader.LineReader; ++import org.jline.reader.LineReaderBuilder; ++ ++public final class PaperConsole extends SimpleTerminalConsole { ++ ++ private final DedicatedServer server; ++ ++ public PaperConsole(DedicatedServer server) { ++ this.server = server; ++ } ++ ++ @Override ++ protected LineReader buildReader(LineReaderBuilder builder) { ++ return super.buildReader(builder ++ .appName("Paper") ++ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) ++ .completer(new ConsoleCommandCompleter(this.server)) ++ ); ++ } ++ ++ @Override ++ protected boolean isRunning() { ++ return !this.server.isStopped() && this.server.isRunning(); ++ } ++ ++ @Override ++ protected void runCommand(String command) { ++ this.server.handleConsoleInput(command, this.server.createCommandSourceStack()); ++ } ++ ++ @Override ++ protected void shutdown() { ++ this.server.halt(false); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.console; ++ ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; ++ ++public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { ++ ++ private static final Logger LOGGER = LogManager.getRootLogger(); ++ ++ @Override ++ public void sendRawMessage(String message) { ++ // TerminalConsoleAppender supports color codes directly in log messages ++ LOGGER.info(message); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f41d79c6630fd8daae28476ffc854f7e65d841e6..4b3341877629c7065496fb0f0b4d509f5a48db6d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -12,6 +12,7 @@ import com.mojang.datafixers.DataFixer; + import io.netty.buffer.ByteBuf; + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import it.unimi.dsi.fastutil.longs.LongIterator; + import java.awt.image.BufferedImage; + import java.io.BufferedWriter; +@@ -161,7 +162,7 @@ import org.apache.logging.log4j.Logger; + import com.mojang.serialization.DynamicOps; + import com.mojang.serialization.Lifecycle; + import com.google.common.collect.ImmutableSet; +-import jline.console.ConsoleReader; ++// import jline.console.ConsoleReader; // Paper + import joptsimple.OptionSet; + import net.minecraft.resources.RegistryReadOps; + import net.minecraft.server.bossevents.CustomBossEvents; +@@ -253,7 +254,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; +@@ -322,7 +323,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Trim to filter lines which are just spaces +- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack()); ++ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); + } + // CraftBukkit end + } +@@ -138,6 +141,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.error("Exception handling console input", ioexception); + } + ++ */ ++ // Paper end + } + }; + +@@ -149,6 +154,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); + ++ // Paper start - Not needed with TerminalConsoleAppender ++ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger(); ++ /* + final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); + for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { + if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { +@@ -157,6 +165,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader).start(); ++ */ ++ // Paper end + + System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream()); + System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream()); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index d750bef20312cc97e3446bbb2d2dc03f90d47f1c..7d6256f65d369fcbcfe1fffe7ac264788a38540b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -146,8 +146,7 @@ public abstract class PlayerList { + + public PlayerList(MinecraftServer server, RegistryAccess.RegistryHolder registryManager, PlayerDataStorage saveHandler, int maxPlayers) { + this.cserver = server.server = new CraftServer((DedicatedServer) server, this); +- server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance(); +- server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server)); ++ server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper + // CraftBukkit end + + this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index d198dad80e0fb41a5bde66944d0e41509a9c1c43..7b2ece40c09ba336a0c2a84321401619801c64c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -46,7 +46,6 @@ import java.util.function.Consumer; + import java.util.logging.Level; + import java.util.logging.Logger; + import javax.imageio.ImageIO; +-import jline.console.ConsoleReader; + import net.minecraft.advancements.Advancement; + import net.minecraft.commands.CommandSourceStack; + import net.minecraft.commands.Commands; +@@ -60,6 +59,7 @@ import net.minecraft.resources.RegistryReadOps; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.ConsoleInput; ++//import jline.console.ConsoleReader; // Paper + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.bossevents.CustomBossEvent; + import net.minecraft.server.commands.ReloadCommand; +@@ -1204,9 +1204,13 @@ public final class CraftServer implements Server { + return logger; + } + ++ // Paper start - JLine update ++ /* + public ConsoleReader getReader() { + return console.reader; + } ++ */ ++ // Paper end + + @Override + public PluginCommand getPluginCommand(String name) { +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index b849b2afd009da433fe6cea5837b3ee9bb5c52b4..60d9980ccca6f1ac55b70f7684b917ddceac380a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -12,7 +12,7 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import joptsimple.OptionParser; + import joptsimple.OptionSet; +-import org.fusesource.jansi.AnsiConsole; ++import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper + + public class Main { + public static boolean useJline = true; +@@ -185,6 +185,8 @@ public class Main { + } + + try { ++ // Paper start - Handled by TerminalConsoleAppender ++ /* + // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals + String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); + String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); +@@ -202,9 +204,18 @@ public class Main { + // This ensures the terminal literal will always match the jline implementation + System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName()); + } ++ */ ++ ++ if (options.has("nojline")) { ++ System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); ++ useJline = false; ++ } ++ // Paper end + + if (options.has("noconsole")) { + useConsole = false; ++ useJline = false; // Paper ++ System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper + } + + if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { +@@ -232,7 +243,7 @@ public class Main { + System.out.println("Unable to read system info"); + } + // Paper end +- ++ System.setProperty( "library.jansi.version", "Paper" ); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +index 4580642e0ee79e6d9c9bef0344e643bbc551205c..829c62b6d55cb5706be3ce6bdc758d6b204844ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +@@ -5,15 +5,13 @@ import java.util.EnumMap; + import java.util.Map; + import java.util.regex.Matcher; + import java.util.regex.Pattern; +-import jline.Terminal; ++//import jline.Terminal; + import org.bukkit.Bukkit; + import org.bukkit.ChatColor; + import org.bukkit.command.ConsoleCommandSender; + import org.bukkit.craftbukkit.CraftServer; +-import org.fusesource.jansi.Ansi; +-import org.fusesource.jansi.Ansi.Attribute; + +-public class ColouredConsoleSender extends CraftConsoleCommandSender { ++public class ColouredConsoleSender /*extends CraftConsoleCommandSender */{/* // Paper - disable + private final Terminal terminal; + private final Map replacements = new EnumMap(ChatColor.class); + private final ChatColor[] colors = ChatColor.values(); +@@ -93,5 +91,5 @@ public class ColouredConsoleSender extends CraftConsoleCommandSender { + } else { + return new ColouredConsoleSender(); + } +- } ++ }*/ // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index befcc19f9b56df9096b98a23b0020f1db793ea5b..a957695457cf3252848ce6ef37069692841b8e28 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -4,20 +4,31 @@ import java.util.Collections; + import java.util.List; + import java.util.concurrent.ExecutionException; + import java.util.logging.Level; +-import jline.console.completer.Completer; ++import net.minecraft.server.dedicated.DedicatedServer; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.util.Waitable; ++ ++// Paper start - JLine update ++import org.jline.reader.Candidate; ++import org.jline.reader.Completer; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++// Paper end + import org.bukkit.event.server.TabCompleteEvent; + + public class ConsoleCommandCompleter implements Completer { +- private final CraftServer server; ++ private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer + +- public ConsoleCommandCompleter(CraftServer server) { ++ public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; + } + ++ // Paper start - Change method signature for JLine update + @Override +- public int complete(final String buffer, final int cursor, final List candidates) { ++ public void complete(LineReader reader, ParsedLine line, List candidates) { ++ final CraftServer server = this.server.server; ++ final String buffer = line.line(); ++ // Paper end + Waitable> waitable = new Waitable>() { + @Override + protected List evaluate() { +@@ -29,25 +40,37 @@ public class ConsoleCommandCompleter implements Completer { + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); + } + }; +- this.server.getServer().processQueue.add(waitable); ++ server.getServer().processQueue.add(waitable); // Paper - Remove "this." + try { + List offers = waitable.get(); + if (offers == null) { +- return cursor; ++ return; // Paper - Method returns void ++ } ++ ++ // Paper start - JLine update ++ for (String completion : offers) { ++ if (completion.isEmpty()) { ++ continue; ++ } ++ ++ candidates.add(new Candidate(completion)); + } +- candidates.addAll(offers); ++ // Paper end + ++ // Paper start - JLine handles cursor now ++ /* + final int lastSpace = buffer.lastIndexOf(' '); + if (lastSpace == -1) { + return cursor - buffer.length(); + } else { + return cursor - (buffer.length() - lastSpace - 1); + } ++ */ ++ // Paper end + } catch (ExecutionException e) { +- this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); ++ server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); // Paper - Remove "this." + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } +- return cursor; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index 70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe..449e99d1b673870ed6892f6ab2c715a2db35c35d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -17,7 +17,7 @@ public class ServerShutdownThread extends Thread { + server.close(); + } finally { + try { +- server.reader.getTerminal().restore(); ++ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender + } catch (Exception e) { + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java +index 99564fed7ce77e29dbdc591bcfe656af741acf8a..9a2da548b8860b496e396564b2c8f6383f020193 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java +@@ -5,12 +5,12 @@ import java.io.IOException; + import java.io.OutputStream; + import java.util.logging.Level; + import java.util.logging.Logger; +-import jline.console.ConsoleReader; ++//import jline.console.ConsoleReader; + import org.bukkit.craftbukkit.Main; +-import org.fusesource.jansi.Ansi; +-import org.fusesource.jansi.Ansi.Erase; ++//import org.fusesource.jansi.Ansi; ++//import org.fusesource.jansi.Ansi.Erase; + +-public class TerminalConsoleWriterThread extends Thread { ++public class TerminalConsoleWriterThread /*extends Thread*/ {/* // Paper - disable + private final ConsoleReader reader; + private final OutputStream output; + +@@ -54,5 +54,5 @@ public class TerminalConsoleWriterThread extends Thread { + Logger.getLogger(TerminalConsoleWriterThread.class.getName()).log(Level.SEVERE, null, ex); + } + } +- } ++ }*/ + } +diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties +new file mode 100644 +index 0000000000000000000000000000000000000000..0694b21465fb9e4164e71862ff24b62241b191f2 +--- /dev/null ++++ b/src/main/resources/log4j2.component.properties +@@ -0,0 +1 @@ ++log4j.skipJansi=true +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 722ca84968cbbbdeffd09939abff0cccd0a84010..620b9490e5f159080e50289d127404a1b56adbef 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,17 +1,14 @@ + + + +- +- +- + + + +- +- +- ++ ++ ++ + +- ++ + + + +@@ -24,10 +21,9 @@ + + + +- + +- + ++ + + + diff --git a/Remapped-Spigot-Server-Patches/0147-provide-a-configurable-option-to-disable-creeper-lin.patch b/Remapped-Spigot-Server-Patches/0147-provide-a-configurable-option-to-disable-creeper-lin.patch new file mode 100644 index 000000000..a511c4d91 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0147-provide-a-configurable-option-to-disable-creeper-lin.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Jun 2017 21:01:18 +0100 +Subject: [PATCH] provide a configurable option to disable creeper lingering + effect spawns + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9d1cddc6038f0fd0286e4a32013ae98ff0b00dd1..90ca51dfdbb3045dd528450225cba96f5834166e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -336,4 +336,10 @@ public class PaperWorldConfig { + parrotsHangOnBetter = getBoolean("parrots-are-unaffected-by-player-movement", false); + log("Parrots are unaffected by player movement: " + parrotsHangOnBetter); + } ++ ++ public boolean disableCreeperLingeringEffect; ++ private void setDisableCreeperLingeringEffect() { ++ disableCreeperLingeringEffect = getBoolean("disable-creeper-lingering-effect", false); ++ log("Creeper lingering effect: " + disableCreeperLingeringEffect); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index bb3226310158139c9fcfe204554caffcbb62798c..8f8d0a23d011936150854a0606be3d63b18c57af 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -261,7 +261,7 @@ public class Creeper extends Monster { + private void spawnLingeringCloud() { + Collection collection = this.getActiveEffects(); + +- if (!collection.isEmpty()) { ++ if (!collection.isEmpty() && !level.paperConfig.disableCreeperLingeringEffect) { // Paper + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level, this.getX(), this.getY(), this.getZ()); + + entityareaeffectcloud.setOwner(this); // CraftBukkit diff --git a/Remapped-Spigot-Server-Patches/0148-Item-canEntityPickup.patch b/Remapped-Spigot-Server-Patches/0148-Item-canEntityPickup.patch new file mode 100644 index 000000000..569a928f3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0148-Item-canEntityPickup.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 5 May 2017 03:57:17 -0500 +Subject: [PATCH] Item#canEntityPickup + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 46f0ebfc99352ec8b64bdff2c6bb8d17ecfeb619..eb35c69bb777ba8d83b2304ff9f862512643e745 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -605,6 +605,11 @@ public abstract class Mob extends LivingEntity { + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (!entityitem.removed && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(entityitem.getItem())) { ++ // Paper Start ++ if (!entityitem.canMobPickup) { ++ continue; ++ } ++ // Paper End + this.pickUpItem(entityitem); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 78dc5ac986afaba04176f64afbb035442cd41d38..7aba507e171f34e213b3c034e345e7397a44d2b5 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -46,6 +46,7 @@ public class ItemEntity extends Entity { + private UUID owner; + public final float bobOffs; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit ++ public boolean canMobPickup = true; // Paper + + public ItemEntity(EntityType type, Level world) { + super(type, world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 6e394ce6ea4177e1758e27074a4fd54d716edc3d..9a410f557988d737c3b930a79ef2ccb2b5c8b406 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -49,6 +49,16 @@ public class CraftItem extends CraftEntity implements Item { + item.age = value; + } + ++ // Paper Start ++ public boolean canMobPickup() { ++ return item.canMobPickup; ++ } ++ ++ public void setCanMobPickup(boolean canMobPickup) { ++ item.canMobPickup = canMobPickup; ++ } ++ // Paper End ++ + @Override + public void setOwner(UUID uuid) { + item.setOwner(uuid); diff --git a/Remapped-Spigot-Server-Patches/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch b/Remapped-Spigot-Server-Patches/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch new file mode 100644 index 000000000..a5081735f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 May 2017 06:26:09 -0500 +Subject: [PATCH] PlayerPickupItemEvent#setFlyAtPlayer + + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 7aba507e171f34e213b3c034e345e7397a44d2b5..72d1e83ac5e4ae3c943ca5ec1058f0d7ad2903cd 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -362,6 +362,7 @@ public class ItemEntity extends Entity { + // CraftBukkit start - fire PlayerPickupItemEvent + int canHold = player.inventory.canHold(itemstack); + int remaining = i - canHold; ++ boolean flyAtPlayer = false; // Paper + + if (this.pickupDelay <= 0 && canHold > 0) { + itemstack.setCount(canHold); +@@ -369,8 +370,14 @@ public class ItemEntity extends Entity { + PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); + playerEvent.setCancelled(!player.canPickUpLoot); + this.level.getCraftServer().getPluginManager().callEvent(playerEvent); ++ flyAtPlayer = playerEvent.getFlyAtPlayer(); // Paper + if (playerEvent.isCancelled()) { + itemstack.setCount(i); // SPIGOT-5294 - restore count ++ // Paper Start ++ if (flyAtPlayer) { ++ player.take(this, i); ++ } ++ // Paper End + return; + } + +@@ -400,7 +407,11 @@ public class ItemEntity extends Entity { + // CraftBukkit end + + if (this.pickupDelay == 0 && (this.owner == null || this.owner.equals(player.getUUID())) && player.inventory.add(itemstack)) { +- player.take(this, i); ++ // Paper Start ++ if (flyAtPlayer) { ++ player.take(this, i); ++ } ++ // Paper End + if (itemstack.isEmpty()) { + this.remove(); + itemstack.setCount(i); diff --git a/Remapped-Spigot-Server-Patches/0150-PlayerAttemptPickupItemEvent.patch b/Remapped-Spigot-Server-Patches/0150-PlayerAttemptPickupItemEvent.patch new file mode 100644 index 000000000..1c5b10dce --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0150-PlayerAttemptPickupItemEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 11 Jun 2017 16:30:30 -0500 +Subject: [PATCH] PlayerAttemptPickupItemEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 72d1e83ac5e4ae3c943ca5ec1058f0d7ad2903cd..de69f7c3c0ee1e74682b0db91bdaae09175690e9 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -35,6 +35,7 @@ import net.minecraft.stats.Stats; + import org.bukkit.event.entity.EntityPickupItemEvent; + import org.bukkit.event.player.PlayerPickupItemEvent; + // CraftBukkit end ++import org.bukkit.event.player.PlayerAttemptPickupItemEvent; // Paper + + public class ItemEntity extends Entity { + +@@ -364,6 +365,22 @@ public class ItemEntity extends Entity { + int remaining = i - canHold; + boolean flyAtPlayer = false; // Paper + ++ // Paper start ++ if (this.pickupDelay <= 0) { ++ PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ this.level.getCraftServer().getPluginManager().callEvent(attemptEvent); ++ ++ flyAtPlayer = attemptEvent.getFlyAtPlayer(); ++ if (attemptEvent.isCancelled()) { ++ if (flyAtPlayer) { ++ player.take(this, i); ++ } ++ ++ return; ++ } ++ } ++ // Paper end ++ + if (this.pickupDelay <= 0 && canHold > 0) { + itemstack.setCount(canHold); + // Call legacy event diff --git a/Remapped-Spigot-Server-Patches/0151-Add-UnknownCommandEvent.patch b/Remapped-Spigot-Server-Patches/0151-Add-UnknownCommandEvent.patch new file mode 100644 index 000000000..9979b7ace --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0151-Add-UnknownCommandEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sweepyoface +Date: Sat, 17 Jun 2017 18:48:21 -0400 +Subject: [PATCH] Add UnknownCommandEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 7b2ece40c09ba336a0c2a84321401619801c64c8..b9e39a4c0aea3de96d1774ae0d0d23ff82162c7e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -790,7 +790,13 @@ public final class CraftServer implements Server { + + // Spigot start + if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { +- sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage); ++ // Paper start ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(sender, commandLine, org.spigotmc.SpigotConfig.unknownCommandMessage); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.message() != null) { ++ sender.sendMessage(event.message()); ++ } ++ // Paper end + } + // Spigot end + diff --git a/Remapped-Spigot-Server-Patches/0152-Basic-PlayerProfile-API.patch b/Remapped-Spigot-Server-Patches/0152-Basic-PlayerProfile-API.patch new file mode 100644 index 000000000..ce8cf66ee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0152-Basic-PlayerProfile-API.patch @@ -0,0 +1,577 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 15 Jan 2018 22:11:48 -0500 +Subject: [PATCH] Basic PlayerProfile API + +Establishes base extension of profile systems for future edits too + +diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f559897e408eee8617af0bb77fa4635e07ccdce +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -0,0 +1,303 @@ ++package com.destroystokyo.paper.profile; ++ ++import PlayerProfile; ++import ProfileProperty; ++import com.destroystokyo.paper.PaperConfig; ++import com.google.common.base.Charsets; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.authlib.properties.PropertyMap; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.players.GameProfileCache; ++import org.apache.commons.lang3.Validate; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.spigotmc.SpigotConfig; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.AbstractSet; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.Objects; ++import java.util.Set; ++import java.util.UUID; ++ ++public class CraftPlayerProfile implements PlayerProfile { ++ ++ private GameProfile profile; ++ private final PropertySet properties = new PropertySet(); ++ ++ public CraftPlayerProfile(CraftPlayer player) { ++ this.profile = player.getHandle().getGameProfile(); ++ } ++ ++ public CraftPlayerProfile(UUID id, String name) { ++ this.profile = new GameProfile(id, name); ++ } ++ ++ public CraftPlayerProfile(GameProfile profile) { ++ Validate.notNull(profile, "GameProfile cannot be null!"); ++ this.profile = profile; ++ } ++ ++ @Override ++ public boolean hasProperty(String property) { ++ return profile.getProperties().containsKey(property); ++ } ++ ++ @Override ++ public void setProperty(ProfileProperty property) { ++ String name = property.getName(); ++ PropertyMap properties = profile.getProperties(); ++ properties.removeAll(name); ++ properties.put(name, new Property(name, property.getValue(), property.getSignature())); ++ } ++ ++ public GameProfile getGameProfile() { ++ return profile; ++ } ++ ++ @Nullable ++ @Override ++ public UUID getId() { ++ return profile.getId(); ++ } ++ ++ @Override ++ public UUID setId(@Nullable UUID uuid) { ++ GameProfile prev = this.profile; ++ this.profile = new GameProfile(uuid, prev.getName()); ++ copyProfileProperties(prev, this.profile); ++ return prev.getId(); ++ } ++ ++ @Nullable ++ @Override ++ public String getName() { ++ return profile.getName(); ++ } ++ ++ @Override ++ public String setName(@Nullable String name) { ++ GameProfile prev = this.profile; ++ this.profile = new GameProfile(prev.getId(), name); ++ copyProfileProperties(prev, this.profile); ++ return prev.getName(); ++ } ++ ++ @Nonnull ++ @Override ++ public Set getProperties() { ++ return properties; ++ } ++ ++ @Override ++ public void setProperties(Collection properties) { ++ properties.forEach(this::setProperty); ++ } ++ ++ @Override ++ public void clearProperties() { ++ profile.getProperties().clear(); ++ } ++ ++ @Override ++ public boolean removeProperty(String property) { ++ return !profile.getProperties().removeAll(property).isEmpty(); ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ CraftPlayerProfile that = (CraftPlayerProfile) o; ++ return Objects.equals(profile, that.profile); ++ } ++ ++ @Override ++ public int hashCode() { ++ return profile.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return profile.toString(); ++ } ++ ++ @Override ++ public CraftPlayerProfile clone() { ++ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); ++ clone.setProperties(getProperties()); ++ return clone; ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return profile.isComplete(); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ MinecraftServer server = MinecraftServer.getServer(); ++ return completeFromCache(false, PaperConfig.isProxyOnlineMode()); ++ } ++ ++ public boolean completeFromCache(boolean onlineMode) { ++ return completeFromCache(false, onlineMode); ++ } ++ ++ public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ String name = profile.getName(); ++ GameProfileCache userCache = server.getProfileCache(); ++ if (profile.getId() == null) { ++ final GameProfile profile; ++ if (onlineMode) { ++ profile = lookupUUID ? userCache.get(name) : userCache.getProfileIfCached(name); ++ } else { ++ // Make an OfflinePlayer using an offline mode UUID since the name has no profile ++ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); ++ } ++ if (profile != null) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ } ++ } ++ ++ if ((profile.getName() == null || !hasTextures()) && profile.getId() != null) { ++ GameProfile profile = userCache.get(this.profile.getId()); ++ if (profile != null) { ++ if (this.profile.getName() == null) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ } else { ++ copyProfileProperties(profile, this.profile); ++ } ++ } ++ } ++ return this.profile.isComplete(); ++ } ++ ++ public boolean complete(boolean textures) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ return complete(textures, PaperConfig.isProxyOnlineMode()); ++ } ++ public boolean complete(boolean textures, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); ++ if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) { ++ GameProfile result = server.getSessionService().fillProfileProperties(profile, true); ++ if (result != null) { ++ copyProfileProperties(result, this.profile, true); ++ } ++ if (this.profile.isComplete()) { ++ server.getProfileCache().saveProfile(this.profile); ++ } ++ } ++ return profile.isComplete() && (!onlineMode || !textures || hasTextures()); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target) { ++ copyProfileProperties(source, target, false); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { ++ PropertyMap sourceProperties = source.getProperties(); ++ PropertyMap targetProperties = target.getProperties(); ++ if (clearTarget) targetProperties.clear(); ++ if (sourceProperties.isEmpty()) { ++ return; ++ } ++ ++ for (Property property : sourceProperties.values()) { ++ targetProperties.removeAll(property.getName()); ++ targetProperties.put(property.getName(), property); ++ } ++ } ++ ++ private static ProfileProperty toBukkit(Property property) { ++ return new ProfileProperty(property.getName(), property.getValue(), property.getSignature()); ++ } ++ ++ public static PlayerProfile asBukkitCopy(GameProfile gameProfile) { ++ CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName()); ++ copyProfileProperties(gameProfile, profile.profile); ++ return profile; ++ } ++ ++ public static PlayerProfile asBukkitMirror(GameProfile profile) { ++ return new CraftPlayerProfile(profile); ++ } ++ ++ public static Property asAuthlib(ProfileProperty property) { ++ return new Property(property.getName(), property.getValue(), property.getSignature()); ++ } ++ ++ public static GameProfile asAuthlibCopy(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return asAuthlib(craft.clone()); ++ } ++ ++ public static GameProfile asAuthlib(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return craft.getGameProfile(); ++ } ++ ++ private class PropertySet extends AbstractSet { ++ ++ @Override ++ @Nonnull ++ public Iterator iterator() { ++ return new ProfilePropertyIterator(profile.getProperties().values().iterator()); ++ } ++ ++ @Override ++ public int size() { ++ return profile.getProperties().size(); ++ } ++ ++ @Override ++ public boolean add(ProfileProperty property) { ++ setProperty(property); ++ return true; ++ } ++ ++ @Override ++ public boolean addAll(Collection c) { ++ //noinspection unchecked ++ setProperties((Collection) c); ++ return true; ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); ++ } ++ ++ private class ProfilePropertyIterator implements Iterator { ++ private final Iterator iterator; ++ ++ ProfilePropertyIterator(Iterator iterator) { ++ this.iterator = iterator; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public ProfileProperty next() { ++ return toBukkit(iterator.next()); ++ } ++ ++ @Override ++ public void remove() { ++ iterator.remove(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d64d45eb01c65864fca1077982d89bc05e0f811b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +@@ -0,0 +1,31 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.*; ++import com.mojang.authlib.minecraft.MinecraftSessionService; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; ++ ++import java.net.Proxy; ++ ++public class PaperAuthenticationService extends YggdrasilAuthenticationService { ++ private final Environment environment; ++ public PaperAuthenticationService(Proxy proxy) { ++ super(proxy); ++ this.environment = (Environment)EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD);; ++ } ++ ++ @Override ++ public UserAuthentication createUserAuthentication(Agent agent) { ++ return new PaperUserAuthentication(this, agent); ++ } ++ ++ @Override ++ public MinecraftSessionService createMinecraftSessionService() { ++ return new PaperMinecraftSessionService(this, this.environment); ++ } ++ ++ @Override ++ public GameProfileRepository createProfileRepository() { ++ return new PaperGameProfileRepository(this, this.environment); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +new file mode 100644 +index 0000000000000000000000000000000000000000..582c169c85ac66f1f9430f79042e4655f776c157 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -0,0 +1,18 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Agent; ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.ProfileLookupCallback; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; ++ ++public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { ++ public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService, Environment environment) { ++ super(authenticationService, environment); ++ } ++ ++ @Override ++ public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { ++ super.findProfilesByNames(names, agent, callback); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..93d73c27340645c7502acafdc0b2cfbc1a759dd8 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.minecraft.MinecraftProfileTexture; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; ++ ++import java.util.Map; ++ ++public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { ++ protected PaperMinecraftSessionService(YggdrasilAuthenticationService authenticationService, Environment environment) { ++ super(authenticationService, environment); ++ } ++ ++ @Override ++ public Map getTextures(GameProfile profile, boolean requireSecure) { ++ return super.getTextures(profile, requireSecure); ++ } ++ ++ @Override ++ public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { ++ return super.fillProfileProperties(profile, requireSecure); ++ } ++ ++ @Override ++ protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) { ++ return super.fillGameProfile(profile, requireSecure); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3cdd06d3af7ff94f1fe1a11b9a9275e17c695a38 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java +@@ -0,0 +1,12 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Agent; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; ++import java.util.UUID; ++ ++public class PaperUserAuthentication extends YggdrasilUserAuthentication { ++ public PaperUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) { ++ super(authenticationService, UUID.randomUUID().toString(), agent); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index d29fe67b7d39e368a873368a6be16042429e9209..68c3f069f8f832ab3d146748348aded69b5ad823 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -1,6 +1,8 @@ + package net.minecraft.server; + + import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import net.minecraft.core.BlockPos; +@@ -11,6 +13,7 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Level; + import org.apache.commons.lang.exception.ExceptionUtils; ++import com.mojang.authlib.GameProfile; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; + import org.bukkit.craftbukkit.CraftWorld; +@@ -345,6 +348,10 @@ public final class MCUtil { + return run.get(); + } + ++ public static PlayerProfile toBukkit(GameProfile profile) { ++ return CraftPlayerProfile.asBukkitMirror(profile); ++ } ++ + /** + * Calculates distance between 2 entities + * @param e1 +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 5d83a8d4c69144219219877c521c364d912d2452..2bfc54941ec34c75c2d59bda748c75730b9951f7 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -124,7 +124,7 @@ public class Main { + } + + File file = (File) optionset.valueOf("universe"); // CraftBukkit +- YggdrasilAuthenticationService yggdrasilauthenticationservice = new YggdrasilAuthenticationService(Proxy.NO_PROXY); ++ YggdrasilAuthenticationService yggdrasilauthenticationservice = new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY); // Paper + MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService(); + GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); + GameProfileCache usercache = new GameProfileCache(gameprofilerepository, new File(file, MinecraftServer.USERID_CACHE_FILE.getName())); +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index e8af352f813a5015d216fc590190ae8fdb03f77d..941b7e356c377fd8ad4e27409cd74c0046878396 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -45,7 +45,7 @@ public class GameProfileCache { + + private static final Logger LOGGER = LogManager.getLogger(); + private static boolean usesAuthentication; +- private final Map profilesByName = Maps.newConcurrentMap(); ++ private final Map profilesByName = Maps.newConcurrentMap();private final Map nameCache = profilesByName; // Paper - OBFHELPER // Paper + private final Map profilesByUUID = Maps.newConcurrentMap(); + private final GameProfileRepository profileRepository; + private final Gson gson = (new GsonBuilder()).create(); +@@ -110,6 +110,7 @@ public class GameProfileCache { + return com.destroystokyo.paper.PaperConfig.isProxyOnlineMode(); // Paper + } + ++ public void saveProfile(GameProfile gameprofile) { add(gameprofile); } // Paper - OBFHELPER + public synchronized void add(GameProfile gameprofile) { // Paper - synchronize + Calendar calendar = Calendar.getInstance(); + +@@ -159,6 +160,13 @@ public class GameProfileCache { + return gameprofile; + } + ++ // Paper start ++ @Nullable public GameProfile getProfileIfCached(String name) { ++ GameProfileCache.GameProfileInfo entry = this.nameCache.get(name.toLowerCase(Locale.ROOT)); ++ return entry == null ? null : entry.getProfile(); ++ } ++ // Paper end ++ + @Nullable + public GameProfile get(UUID uuid) { + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); +@@ -341,7 +349,7 @@ public class GameProfileCache { + + static class GameProfileInfo { + +- private final GameProfile profile; ++ private final GameProfile profile;public GameProfile getProfile() { return profile; } // Paper - OBFHELPER + private final Date expirationDate; + private volatile long lastAccess; + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b9e39a4c0aea3de96d1774ae0d0d23ff82162c7e..df68599520189e2699c8521d6c6ab7235612af33 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -223,6 +223,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + ++import javax.annotation.Nullable; // Paper ++import javax.annotation.Nonnull; // Paper ++ + public final class CraftServer implements Server { + private final String serverName = "Paper"; // Paper + private final String serverVersion; +@@ -2315,5 +2318,24 @@ public final class CraftServer implements Server { + public boolean suggestPlayerNamesWhenNullTabCompletions() { + return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; + } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { ++ return createProfile(uuid, null); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { ++ return createProfile(null, name); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { ++ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); ++ if (player != null) { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer)player); ++ } ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 313ddd6b64e395a8caab77b3da005e52006ab2d7..750661540f55d3c59119dcc909e706e571a2123b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -80,6 +80,13 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + } + + private void setProfile(GameProfile profile) { ++ // Paper start ++ if (profile != null) { ++ com.destroystokyo.paper.profile.CraftPlayerProfile paperProfile = new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); ++ paperProfile.completeFromCache(false, true); ++ profile = paperProfile.getGameProfile(); ++ } ++ // Paper end + this.profile = profile; + this.serializedProfile = (profile == null) ? null : NbtUtils.writeGameProfile(new CompoundTag(), profile); + } diff --git a/Remapped-Spigot-Server-Patches/0153-Shoulder-Entities-Release-API.patch b/Remapped-Spigot-Server-Patches/0153-Shoulder-Entities-Release-API.patch new file mode 100644 index 000000000..e71eeed52 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0153-Shoulder-Entities-Release-API.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 17 Jun 2017 15:18:30 -0400 +Subject: [PATCH] Shoulder Entities Release API + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index ae10c531ae69eaf6b78a342dcedb89c39fd8dbcc..3205bc2fb6c9031be68ff46dfca927e681163fa8 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1877,20 +1877,44 @@ public abstract class Player extends LivingEntity { + + } + ++ // Paper start ++ public Entity releaseLeftShoulderEntity() { ++ Entity entity = this.spawnEntityFromShoulder0(this.getShoulderEntityLeft()); ++ if (entity != null) { ++ this.setShoulderEntityLeft(new CompoundTag()); ++ } ++ return entity; ++ } ++ ++ public Entity releaseRightShoulderEntity() { ++ Entity entity = this.spawnEntityFromShoulder0(this.getShoulderEntityRight()); ++ if (entity != null) { ++ this.setShoulderEntityRight(new CompoundTag()); ++ } ++ return entity; ++ } ++ // Paper - maintain old signature + private boolean spawnEntityFromShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean +- if (!this.level.isClientSide && !nbttagcompound.isEmpty()) { ++ return spawnEntityFromShoulder0(nbttagcompound) != null; ++ } ++ ++ // Paper - return entity ++ private Entity spawnEntityFromShoulder0(@Nullable CompoundTag nbttagcompound) { ++ if (!this.level.isClientSide && nbttagcompound != null && !nbttagcompound.isEmpty()) { + return EntityType.create(nbttagcompound, this.level).map((entity) -> { // CraftBukkit + if (entity instanceof TamableAnimal) { + ((TamableAnimal) entity).setOwnerUUID(this.uuid); + } + + entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ()); +- return ((ServerLevel) this.level).addEntitySerialized(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit +- }).orElse(true); // CraftBukkit ++ boolean addedToWorld = ((ServerLevel) this.level).addEntitySerialized(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit ++ return addedToWorld ? entity : null; ++ }).orElse(null); // CraftBukkit // Paper - false -> null + } + +- return true; // CraftBukkit ++ return null; // Paper - return null + } ++ // Paper end + + @Override + public abstract boolean isSpectator(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index b5c0f3d91cf451a972f0cf293db03a306073c493..c62d01719f21762aa10294815ab88e450e4dce3f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -494,6 +494,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + getHandle().getCooldowns().addCooldown(CraftMagicNumbers.getItem(material), ticks); + } + ++ // Paper start ++ @Override ++ public org.bukkit.entity.Entity releaseLeftShoulderEntity() { ++ if (!getHandle().getShoulderEntityLeft().isEmpty()) { ++ Entity entity = getHandle().releaseLeftShoulderEntity(); ++ if (entity != null) { ++ return entity.getBukkitEntity(); ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity releaseRightShoulderEntity() { ++ if (!getHandle().getShoulderEntityRight().isEmpty()) { ++ Entity entity = getHandle().releaseRightShoulderEntity(); ++ if (entity != null) { ++ return entity.getBukkitEntity(); ++ } ++ } ++ ++ return null; ++ } ++ // Paper end ++ + @Override + public boolean discoverRecipe(NamespacedKey recipe) { + return discoverRecipes(Arrays.asList(recipe)) != 0; diff --git a/Remapped-Spigot-Server-Patches/0154-Profile-Lookup-Events.patch b/Remapped-Spigot-Server-Patches/0154-Profile-Lookup-Events.patch new file mode 100644 index 000000000..a9a4210f1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0154-Profile-Lookup-Events.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 17 Jun 2017 17:00:32 -0400 +Subject: [PATCH] Profile Lookup Events + +Adds a Pre Lookup Event and a Post Lookup Event so that plugins may prefill in profile data, and cache the responses from +profiles that had to be looked up. + +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +index 582c169c85ac66f1f9430f79042e4655f776c157..08fdb681a68e8be6e4062af0630957ce3e524806 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -1,11 +1,16 @@ + package com.destroystokyo.paper.profile; + ++import com.destroystokyo.paper.event.profile.LookupProfileEvent; ++import com.destroystokyo.paper.event.profile.PreLookupProfileEvent; ++import com.google.common.collect.Sets; + import com.mojang.authlib.Agent; + import com.mojang.authlib.Environment; ++import com.mojang.authlib.GameProfile; + import com.mojang.authlib.ProfileLookupCallback; + import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; + import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; + ++import java.util.Set; + public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService, Environment environment) { + super(authenticationService, environment); +@@ -13,6 +18,50 @@ public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + + @Override + public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { +- super.findProfilesByNames(names, agent, callback); ++ Set unfoundNames = Sets.newHashSet(); ++ for (String name : names) { ++ PreLookupProfileEvent event = new PreLookupProfileEvent(name); ++ event.callEvent(); ++ if (event.getUUID() != null) { ++ // Plugin provided UUI, we can skip network call. ++ GameProfile gameprofile = new GameProfile(event.getUUID(), name); ++ // We might even have properties! ++ Set profileProperties = event.getProfileProperties(); ++ if (!profileProperties.isEmpty()) { ++ for (ProfileProperty property : profileProperties) { ++ gameprofile.getProperties().put(property.getName(), CraftPlayerProfile.asAuthlib(property)); ++ } ++ } ++ callback.onProfileLookupSucceeded(gameprofile); ++ } else { ++ unfoundNames.add(name); ++ } ++ } ++ ++ // Some things were not found.... Proceed to look up. ++ if (!unfoundNames.isEmpty()) { ++ String[] namesArr = unfoundNames.toArray(new String[unfoundNames.size()]); ++ super.findProfilesByNames(namesArr, agent, new PreProfileLookupCallback(callback)); ++ } ++ } ++ ++ private static class PreProfileLookupCallback implements ProfileLookupCallback { ++ private final ProfileLookupCallback callback; ++ ++ PreProfileLookupCallback(ProfileLookupCallback callback) { ++ this.callback = callback; ++ } ++ ++ @Override ++ public void onProfileLookupSucceeded(GameProfile gameProfile) { ++ PlayerProfile from = CraftPlayerProfile.asBukkitMirror(gameProfile); ++ new LookupProfileEvent(from).callEvent(); ++ callback.onProfileLookupSucceeded(gameProfile); ++ } ++ ++ @Override ++ public void onProfileLookupFailed(GameProfile gameProfile, Exception e) { ++ callback.onProfileLookupFailed(gameProfile, e); ++ } + } + } diff --git a/Remapped-Spigot-Server-Patches/0155-Block-player-logins-during-server-shutdown.patch b/Remapped-Spigot-Server-Patches/0155-Block-player-logins-during-server-shutdown.patch new file mode 100644 index 000000000..f43b561da --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0155-Block-player-logins-during-server-shutdown.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 2 Jul 2017 21:35:56 -0500 +Subject: [PATCH] Block player logins during server shutdown + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index e3c1460c580ea348ee6d436018244441a5a1206e..fca291c34c67d552590320c6e6e9c08e21d19fa8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -66,6 +66,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + } + + public void tick() { ++ // Paper start - Do not allow logins while the server is shutting down ++ if (!MinecraftServer.getServer().isRunning()) { ++ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]); ++ return; ++ } ++ // Paper end + if (this.state == ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT) { + this.handleAcceptedLogin(); + } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { diff --git a/Remapped-Spigot-Server-Patches/0156-Entity-fromMobSpawner.patch b/Remapped-Spigot-Server-Patches/0156-Entity-fromMobSpawner.patch new file mode 100644 index 000000000..10fa81134 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0156-Entity-fromMobSpawner.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 18 Jun 2017 18:17:05 -0500 +Subject: [PATCH] Entity#fromMobSpawner() + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a017fa55002d6674124befa3f6e81eb70c9ce8f7..7e70bae5bc54ad17980789fa965fd539a7f193ea 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -267,6 +267,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end +@@ -1665,6 +1666,10 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + tag.setUUID("Paper.OriginWorld", origin.getWorld().getUID()); + tag.put("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); + } ++ // Save entity's from mob spawner status ++ if (spawnedViaMobSpawner) { ++ tag.putBoolean("Paper.FromMobSpawner", true); ++ } + // Paper end + return tag; + } catch (Throwable throwable) { +@@ -1797,6 +1802,8 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + origin = new org.bukkit.Location(originWorld, originTag.getDoubleAt(0), originTag.getDoubleAt(1), originTag.getDoubleAt(2)); + } ++ ++ spawnedViaMobSpawner = tag.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index ed631d5bfba5d2543e8eed017a7c484ad3ddb453..7541155048744a9af2017ec039cf99a246addb0b 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -160,6 +160,7 @@ public abstract class BaseSpawner { + } + // Spigot End + } ++ entity.spawnedViaMobSpawner = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { + Entity vehicle = entity.getVehicle(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index b028946de7c8f52091635fe154c816453f1ddc93..ecb5f5ca547930f91602d539e541964cd9f10287 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1100,5 +1100,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + Location origin = getHandle().origin; + return origin == null ? null : origin.clone(); + } ++ ++ @Override ++ public boolean fromMobSpawner() { ++ return getHandle().spawnedViaMobSpawner; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0157-Improve-the-Saddle-API-for-Horses.patch b/Remapped-Spigot-Server-Patches/0157-Improve-the-Saddle-API-for-Horses.patch new file mode 100644 index 000000000..85d5e4f33 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0157-Improve-the-Saddle-API-for-Horses.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 10 Dec 2016 16:24:06 -0500 +Subject: [PATCH] Improve the Saddle API for Horses + +Not all horses with Saddles have armor. This lets us break up the horses with saddles +and access their saddle state separately from an interface shared with Armor. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 116553ea587ab5ff3bc8a1530f51cd6efb7ae8ca..6136b3322a340d506ce744bcd15f71a158e46ad1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -5,6 +5,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes; + import org.apache.commons.lang.Validate; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.inventory.CraftInventoryAbstractHorse; ++import org.bukkit.craftbukkit.inventory.CraftSaddledInventory; + import org.bukkit.entity.AbstractHorse; + import org.bukkit.entity.AnimalTamer; + import org.bukkit.entity.Horse; +@@ -98,6 +99,6 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + + @Override + public AbstractHorseInventory getInventory() { +- return new CraftInventoryAbstractHorse(getHandle().inventory); ++ return new CraftSaddledInventory(getHandle().inventory); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java +index 7013059856c2471dc34112a1a2068b96b809dd96..b72b4260fc1c0e9928d70f97589d8db00849b9e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java +@@ -4,7 +4,7 @@ import net.minecraft.world.Container; + import org.bukkit.inventory.HorseInventory; + import org.bukkit.inventory.ItemStack; + +-public class CraftInventoryHorse extends CraftInventoryAbstractHorse implements HorseInventory { ++public class CraftInventoryHorse extends CraftSaddledInventory implements HorseInventory { + + public CraftInventoryHorse(Container inventory) { + super(inventory); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a617c07d445bacf5a13e0e3ff6481823cfc8477 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java +@@ -0,0 +1,12 @@ ++package org.bukkit.craftbukkit.inventory; ++ ++import net.minecraft.world.Container; ++import org.bukkit.inventory.SaddledHorseInventory; ++ ++public class CraftSaddledInventory extends CraftInventoryAbstractHorse implements SaddledHorseInventory { ++ ++ public CraftSaddledInventory(Container inventory) { ++ super(inventory); ++ } ++ ++} diff --git a/Remapped-Spigot-Server-Patches/0158-Implement-ensureServerConversions-API.patch b/Remapped-Spigot-Server-Patches/0158-Implement-ensureServerConversions-API.patch new file mode 100644 index 000000000..bc3d68abd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0158-Implement-ensureServerConversions-API.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 May 2016 22:43:12 -0400 +Subject: [PATCH] Implement ensureServerConversions API + +This will take a Bukkit ItemStack and run it through any conversions a server process would perform on it, +to ensure it meets latest minecraft expectations. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 32fa5ca0df07466e40817341d85d359b282f3078..41c8bbf93039add43695a52d2bfc0c2cef88463d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -346,5 +346,11 @@ public final class CraftItemFactory implements ItemFactory { + public net.kyori.adventure.text.@org.jetbrains.annotations.NotNull Component displayName(@org.jetbrains.annotations.NotNull ItemStack itemStack) { + return io.papermc.paper.adventure.PaperAdventure.asAdventure(CraftItemStack.asNMSCopy(itemStack).displayName()); + } ++ ++ // Paper start ++ @Override ++ public ItemStack ensureServerConversions(ItemStack item) { ++ return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0159-Implement-getI18NDisplayName.patch b/Remapped-Spigot-Server-Patches/0159-Implement-getI18NDisplayName.patch new file mode 100644 index 000000000..c27fb916c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0159-Implement-getI18NDisplayName.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 May 2016 23:59:38 -0400 +Subject: [PATCH] Implement getI18NDisplayName + +Gets the Display name as seen in the Client. +Currently the server only supports the English language. To override this, +You must replace the language file embedded in the server jar. + +diff --git a/src/main/java/net/minecraft/locale/Language.java b/src/main/java/net/minecraft/locale/Language.java +index 8a0d2e510aee160ec3a817fd72c23bc9dfeb6287..756b78a7dc3d3e76dc0a37375b304db0b6ecf684 100644 +--- a/src/main/java/net/minecraft/locale/Language.java ++++ b/src/main/java/net/minecraft/locale/Language.java +@@ -30,7 +30,7 @@ public abstract class Language { + + private static Language loadDefault() { + Builder builder = ImmutableMap.builder(); +- BiConsumer biconsumer = builder::put; ++ BiConsumer biconsumer = builder::put; // Paper - decompile fix + + try { + InputStream inputstream = Language.class.getResourceAsStream("/assets/minecraft/lang/en_us.json"); +@@ -87,10 +87,12 @@ public abstract class Language { + + } + ++ public static Language getInstance() { return getInstance(); } // Paper - OBFHELPER + public static Language getInstance() { + return Language.instance; + } + ++ public String translateKey(String key) { return getOrDefault(key); } // Paper - OBFHELPER + public abstract String getOrDefault(String key); + + public abstract boolean has(String key); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 41c8bbf93039add43695a52d2bfc0c2cef88463d..746755f76ae177b2eeccf66f8cd95e6ffd5acad9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -352,5 +352,18 @@ public final class CraftItemFactory implements ItemFactory { + public ItemStack ensureServerConversions(ItemStack item) { + return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); + } ++ ++ @Override ++ public String getI18NDisplayName(ItemStack item) { ++ net.minecraft.world.item.ItemStack nms = null; ++ if (item instanceof CraftItemStack) { ++ nms = ((CraftItemStack) item).handle; ++ } ++ if (nms == null) { ++ nms = CraftItemStack.asNMSCopy(item); ++ } ++ ++ return nms != null ? net.minecraft.locale.Language.getInstance().translateKey(nms.getItem().getDescriptionId()) : null; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0160-ProfileWhitelistVerifyEvent.patch b/Remapped-Spigot-Server-Patches/0160-ProfileWhitelistVerifyEvent.patch new file mode 100644 index 000000000..5051a6a19 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0160-ProfileWhitelistVerifyEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 3 Jul 2017 18:11:10 -0500 +Subject: [PATCH] ProfileWhitelistVerifyEvent + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7d6256f65d369fcbcfe1fffe7ac264788a38540b..ae60b233c4d56a4964b388f96e9cc66410774e8d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -611,9 +611,9 @@ public abstract class PlayerList { + + // return chatmessage; + if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Spigot // Paper - Adventure +- } else if (!this.isWhiteListed(gameprofile)) { +- chatmessage = new TranslatableComponent("multiplayer.disconnect.not_whitelisted"); +- event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure ++ } else if (!this.isWhitelisted(gameprofile, event)) { // Paper ++ //chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); // Paper ++ //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot // Paper - moved to isWhitelisted + } else if (getIpBans().isBanned(socketaddress) && !getIpBans().get(socketaddress).hasExpired()) { + IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); + +@@ -1005,9 +1005,25 @@ public abstract class PlayerList { + this.server.getCommands().sendCommands(player); + } + ++ // Paper start + public boolean isWhiteListed(GameProfile profile) { +- return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile); ++ return isWhitelisted(profile, null); + } ++ public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) { ++ boolean isOp = this.ops.contains(gameprofile); ++ boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile); ++ final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; ++ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); ++ event.callEvent(); ++ if (!event.isWhitelisted()) { ++ if (loginEvent != null) { ++ loginEvent.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getKickMessage() == null ? org.spigotmc.SpigotConfig.whitelistMessage : event.getKickMessage())); ++ } ++ return false; ++ } ++ return true; ++ } ++ // Paper end + + public boolean isOp(GameProfile profile) { + return this.ops.contains(profile) || this.server.isSingleplayerOwner(profile) && this.server.getWorldData().getAllowCommands() || this.allowCheatsForAllPlayers; diff --git a/Remapped-Spigot-Server-Patches/0161-Fix-this-stupid-bullshit.patch b/Remapped-Spigot-Server-Patches/0161-Fix-this-stupid-bullshit.patch new file mode 100644 index 000000000..8eacc81f8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0161-Fix-this-stupid-bullshit.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DemonWav +Date: Sun, 6 Aug 2017 17:17:53 -0500 +Subject: [PATCH] Fix this stupid bullshit + +Disable the 15 second sleep when the server jar hasn't been rebuilt within a period of time. + +modified in order to prevent merge conflicts when Spigot changes/disables the warning, +and to provide some level of hint without being disruptive. + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 60d9980ccca6f1ac55b70f7684b917ddceac380a..808a7688ed81bdfef623ee0a151ff8f94df2a3d7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -224,10 +224,12 @@ public class Main { + Calendar deadline = Calendar.getInstance(); + deadline.add(Calendar.DAY_OF_YEAR, -28); + if (buildDate.before(deadline.getTime())) { +- System.err.println("*** Error, this build is outdated ***"); ++ // Paper start - This is some stupid bullshit ++ System.err.println("*** Warning, you've not updated in a while! ***"); + System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper +- System.err.println("*** Server will start in 20 seconds ***"); +- Thread.sleep(TimeUnit.SECONDS.toMillis(20)); ++ //System.err.println("*** Server will start in 20 seconds ***"); ++ //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); ++ // Paper End + } + } + diff --git a/Remapped-Spigot-Server-Patches/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch b/Remapped-Spigot-Server-Patches/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch new file mode 100644 index 000000000..07571c80e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:54:40 -0500 +Subject: [PATCH] Ocelot despawns should honor nametags and leash + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java +index 1b820ca48845cbe4a668cc31582c2ddf6d50c96a..142025cfb75b24dc6df8160f4922086404efa6a7 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java +@@ -128,7 +128,7 @@ public class Ocelot extends Animal { + + @Override + public boolean removeWhenFarAway(double distanceSquared) { +- return !this.isTrusting() /*&& this.ticksLived > 2400*/; // CraftBukkit ++ return !this.isTrusting() && !this.hasCustomName() && !this.isLeashed() /*&& this.ticksLived > 2400*/; // CraftBukkit // Paper - honor name and leash + } + + public static AttributeSupplier.Builder createAttributes() { diff --git a/Remapped-Spigot-Server-Patches/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch b/Remapped-Spigot-Server-Patches/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch new file mode 100644 index 000000000..3476f2d83 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:45:19 -0500 +Subject: [PATCH] Reset spawner timer when spawner event is cancelled + + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 7541155048744a9af2017ec039cf99a246addb0b..4582fc1bb767214241568fbc22b0ee2cbf3322e0 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -161,6 +161,7 @@ public abstract class BaseSpawner { + // Spigot End + } + entity.spawnedViaMobSpawner = true; // Paper ++ flag = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { + Entity vehicle = entity.getVehicle(); +@@ -184,7 +185,7 @@ public abstract class BaseSpawner { + ((Mob) entity).spawnAnim(); + } + +- flag = true; ++ /*flag = true;*/ // Paper - moved up above cancellable event + } + } + } diff --git a/Remapped-Spigot-Server-Patches/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch b/Remapped-Spigot-Server-Patches/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch new file mode 100644 index 000000000..01e55f74f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mezz +Date: Wed, 9 Aug 2017 17:51:22 -0500 +Subject: [PATCH] Fix MC-117075: TE Unload Lag Spike + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index ecb6378a285dff4b34170410387ebb7a8510a6dc..81713c97f35263f4ab8d14f8b707aac3d6afea11 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -726,7 +726,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + gameprofilerfiller.push("blockEntities"); + timings.tileEntityTick.startTiming(); // Spigot + if (!this.tileEntityListUnload.isEmpty()) { +- this.tickableBlockEntities.removeAll(this.tileEntityListUnload); ++ // Paper start - Use alternate implementation with faster contains ++ java.util.Set toRemove = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); ++ toRemove.addAll(tileEntityListUnload); ++ this.tickableBlockEntities.removeAll(toRemove); ++ // Paper end + //this.tileEntityList.removeAll(this.tileEntityListUnload); // Paper - remove unused list + this.tileEntityListUnload.clear(); + } diff --git a/Remapped-Spigot-Server-Patches/0165-Allow-specifying-a-custom-authentication-servers-dow.patch b/Remapped-Spigot-Server-Patches/0165-Allow-specifying-a-custom-authentication-servers-dow.patch new file mode 100644 index 000000000..766b53651 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0165-Allow-specifying-a-custom-authentication-servers-dow.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 17 Aug 2017 16:08:20 -0700 +Subject: [PATCH] Allow specifying a custom "authentication servers down" kick + message + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 48319aaf1c525c6fb7bdee5c2f570a0d056d4eae..52954fc3bf932cfc9d5ce63e3d3cace351305790 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.google.common.base.Strings; + import com.google.common.base.Throwables; + + import java.io.File; +@@ -273,4 +274,9 @@ public class PaperConfig { + private static void suggestPlayersWhenNull() { + suggestPlayersWhenNullTabCompletions = getBoolean("settings.suggest-player-names-when-null-tab-completions", suggestPlayersWhenNullTabCompletions); + } ++ ++ public static String authenticationServersDownKickMessage = ""; // empty = use translatable message ++ private static void authenticationServersDownKickMessage() { ++ authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index fca291c34c67d552590320c6e6e9c08e21d19fa8..4b91699ddfa2ee298af5ba25447a85751facf4a4 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -275,6 +275,10 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + ServerLoginPacketListenerImpl.this.gameProfile = ServerLoginPacketListenerImpl.this.createFakeProfile(gameprofile); + ServerLoginPacketListenerImpl.this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; + } else { ++ // Paper start ++ if (com.destroystokyo.paper.PaperConfig.authenticationServersDownKickMessage != null) { ++ ServerLoginPacketListenerImpl.this.disconnect(new TextComponent(com.destroystokyo.paper.PaperConfig.authenticationServersDownKickMessage)); ++ } else // Paper end + ServerLoginPacketListenerImpl.this.disconnect(new TranslatableComponent("multiplayer.disconnect.authservers_down")); + ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable"); + } diff --git a/Remapped-Spigot-Server-Patches/0166-LivingEntity-setKiller.patch b/Remapped-Spigot-Server-Patches/0166-LivingEntity-setKiller.patch new file mode 100644 index 000000000..fdb6acae7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0166-LivingEntity-setKiller.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:49:48 -0500 +Subject: [PATCH] LivingEntity#setKiller + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index c1786fcabeaee5732e9197db04268c5c4e164339..d6b4fabd232958ae1fd5405c7129551951cd7765 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -177,7 +177,7 @@ public abstract class LivingEntity extends Entity { + public float flyingSpeed; + @Nullable + public net.minecraft.world.entity.player.Player lastHurtByPlayer; +- protected int lastHurtByPlayerTime; ++ public int lastHurtByPlayerTime; // Paper - protected -> public + protected boolean dead; + protected int noActionTime; + protected float oRun; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index d8cd88d62f9abfc7960c187dd74239f61267ca57..006f4c71bbcda61b55815e7ceab91731ab062da0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -8,6 +8,7 @@ import java.util.Iterator; + import java.util.List; + import java.util.Set; + import java.util.UUID; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.effect.MobEffect; +@@ -332,6 +333,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().lastHurtByPlayer == null ? null : (Player) getHandle().lastHurtByPlayer.getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public void setKiller(Player killer) { ++ ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); ++ getHandle().lastHurtByPlayer = entityPlayer; ++ getHandle().lastHurtByMob = entityPlayer; ++ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity ++ } ++ // Paper end ++ + @Override + public boolean addPotionEffect(PotionEffect effect) { + return addPotionEffect(effect, false); diff --git a/Remapped-Spigot-Server-Patches/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch b/Remapped-Spigot-Server-Patches/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch new file mode 100644 index 000000000..8c91986ef --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Thu, 21 Sep 2017 16:14:55 +0200 +Subject: [PATCH] Handle plugin prefixes using Log4J configuration + +Display logger name in the console for all loggers except the +root logger, Bukkit's logger ("Minecraft") and Minecraft loggers. +Since plugins now use the plugin name as logger name this will +restore the plugin prefixes without having to prepend them manually +to the log messages. + +Logger prefixes are shown by default for all loggers except for +the root logger, the Minecraft/Mojang loggers and the Bukkit loggers. +This may cause additional prefixes to be disabled for plugins bypassing +the plugin logger. + +diff --git a/pom.xml b/pom.xml +index 3841fe3630c090f8a468333d43caeb2b5841329d..f5429f2f1979542fd93956d2f436d20d0e3a66b8 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -76,7 +76,7 @@ + + org.apache.logging.log4j + log4j-core +- runtime ++ compile + + + org.apache.logging.log4j +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 0083f979933d4a9035efb992ab0a2f250a56a979..3981ba5957fdc2929d54515f2b98bb7a4611e938 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -290,7 +290,7 @@ public class SpigotConfig + private static void playerSample() + { + playerSample = getInt( "settings.sample-count", 12 ); +- System.out.println( "Server Ping Player Sample Count: " + playerSample ); ++ Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger + } + + public static int playerShuffle; +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 620b9490e5f159080e50289d127404a1b56adbef..a8bdaaeaa1a9316848416f0533739b9b083ca151 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -5,10 +5,22 @@ + + + +- ++ ++ ++ ++ ++ ++ + + +- ++ ++ ++ ++ ++ ++ + + + diff --git a/Remapped-Spigot-Server-Patches/0168-Improve-Log4J-Configuration-Plugin-Loggers.patch b/Remapped-Spigot-Server-Patches/0168-Improve-Log4J-Configuration-Plugin-Loggers.patch new file mode 100644 index 000000000..5afb9562c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0168-Improve-Log4J-Configuration-Plugin-Loggers.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Sat, 23 Sep 2017 21:07:20 +0200 +Subject: [PATCH] Improve Log4J Configuration / Plugin Loggers + +Add full exceptions to log4j to not truncate stack traces + +Disable logger prefix for various plugins bypassing the plugin logger + +Some plugins bypass the plugin logger and add the plugin prefix +manually to the log message. Since they use other logger names +(e.g. qualified class names) these would now also appear in the +log. Disable the logger prefix for these plugins so the messages +show up correctly. + +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index a8bdaaeaa1a9316848416f0533739b9b083ca151..476f4a5cbe664ddd05474cb88553018bd334a5b8 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -6,19 +6,21 @@ + + + +- ++ + +- ++ ++ + + + + + +- ++ + +- ++ ++ + + + diff --git a/Remapped-Spigot-Server-Patches/0169-Add-PlayerJumpEvent.patch b/Remapped-Spigot-Server-Patches/0169-Add-PlayerJumpEvent.patch new file mode 100644 index 000000000..9abed7291 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0169-Add-PlayerJumpEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 28 Sep 2017 17:21:44 -0400 +Subject: [PATCH] Add PlayerJumpEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6a922e3522ac99a8e317a5f5f51fbb597baaf63e..f35a976de39f16d100bcbe411b64de357832c5df 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1166,7 +1166,34 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + boolean flag = d8 > 0.0D; + + if (this.player.isOnGround() && !packet.isOnGround() && flag) { +- this.player.jumpFromGround(); ++ // Paper start - Add player jump event ++ Player player = this.getPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ if (packet.hasPos) { ++ to.setX(packet.x); ++ to.setY(packet.y); ++ to.setZ(packet.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packet.hasRot) { ++ to.setYaw(packet.yRot); ++ to.setPitch(packet.xRot); ++ } ++ ++ com.destroystokyo.paper.event.player.PlayerJumpEvent event = new com.destroystokyo.paper.event.player.PlayerJumpEvent(player, from, to); ++ ++ if (event.callEvent()) { ++ this.player.jumpFromGround(); ++ } else { ++ from = event.getFrom(); ++ this.internalTeleport(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()); ++ return; ++ } ++ // Paper end + } + + this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); diff --git a/Remapped-Spigot-Server-Patches/0170-handle-PacketPlayInKeepAlive-async.patch b/Remapped-Spigot-Server-Patches/0170-handle-PacketPlayInKeepAlive-async.patch new file mode 100644 index 000000000..1a0b7f308 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0170-handle-PacketPlayInKeepAlive-async.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 5 Oct 2017 01:54:07 +0100 +Subject: [PATCH] handle PacketPlayInKeepAlive async + +In 1.12.2, Mojang moved the processing of PacketPlayInKeepAlive off the main +thread, while entirely correct for the server, this causes issues with +plugins which are expecting the PlayerQuitEvent on the main thread. + +In order to counteract some bad behavior, we will post handling of the +disconnection to the main thread, but leave the actual processing of the packet +off the main thread. + +also adding some additional logging in order to help work out what is causing +random disconnections for clients. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f35a976de39f16d100bcbe411b64de357832c5df..f0aab8639c7d8440e4d70dd096200313d7958780 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2777,14 +2777,18 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { +- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // CraftBukkit ++ //PlayerConnectionUtils.ensureMainThread(packetplayinkeepalive, this, this.player.getWorldServer()); // CraftBukkit // Paper - This shouldn't be on the main thread + if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { + int i = (int) (Util.getMillis() - this.keepAliveTime); + + this.player.latency = (this.player.latency * 3 + i) / 4; + this.keepAlivePending = false; + } else if (!this.isSingleplayerOwner()) { ++ // Paper start - This needs to be handled on the main thread for plugins ++ server.scheduleOnMain(() -> { + this.disconnect(new TranslatableComponent("disconnect.timeout")); ++ }); ++ // Paper end + } + + } diff --git a/Remapped-Spigot-Server-Patches/0171-Expose-client-protocol-version-and-virtual-host.patch b/Remapped-Spigot-Server-Patches/0171-Expose-client-protocol-version-and-virtual-host.patch new file mode 100644 index 000000000..c18295ef0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0171-Expose-client-protocol-version-and-virtual-host.patch @@ -0,0 +1,128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Tue, 10 Oct 2017 18:45:20 +0200 +Subject: [PATCH] Expose client protocol version and virtual host + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5a7624f1f372a26b982836cd31cff15e2589e9b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java +@@ -0,0 +1,49 @@ ++package com.destroystokyo.paper.network; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++import net.minecraft.network.Connection; ++ ++public class PaperNetworkClient implements NetworkClient { ++ ++ private final Connection networkManager; ++ ++ PaperNetworkClient(Connection networkManager) { ++ this.networkManager = networkManager; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return (InetSocketAddress) this.networkManager.getRemoteAddress(); ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.networkManager.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.networkManager.virtualHost; ++ } ++ ++ public static InetSocketAddress prepareVirtualHost(String host, int port) { ++ int len = host.length(); ++ ++ // FML appends a marker to the host to recognize FML clients (\0FML\0) ++ int pos = host.indexOf('\0'); ++ if (pos >= 0) { ++ len = pos; ++ } ++ ++ // When clients connect with a SRV record, their host contains a trailing '.' ++ if (len > 0 && host.charAt(len - 1) == '.') { ++ len--; ++ } ++ ++ return InetSocketAddress.createUnresolved(host.substring(0, len), port); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 92c5c5bbcfe364475578b6a0eddfaa85858ace8a..3429c813a5b471cdfa561bd20849a303e5aacead 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -70,6 +70,10 @@ public class Connection extends SimpleChannelInboundHandler> { + private float averageSentPackets; + private int tickCount; + private boolean handlingFault; ++ // Paper start - NetworkClient implementation ++ public int protocolVersion; ++ public java.net.InetSocketAddress virtualHost; ++ // Paper end + + public Connection(PacketFlow side) { + this.receiving = side; +diff --git a/src/main/java/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java b/src/main/java/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java +index 1eae2999ecc57f68ac9cd1d745191cba617b0de2..9ad400b15a2eb2d80bc763de28d648e22432b8f2 100644 +--- a/src/main/java/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java +@@ -39,6 +39,7 @@ public class ClientIntentionPacket implements Packet +Date: Sun, 15 Oct 2017 00:29:07 +0100 +Subject: [PATCH] revert serverside behavior of keepalives + +This patch intends to bump up the time that a client has to reply to the +server back to 30 seconds as per pre 1.12.2, which allowed clients +more than enough time to reply potentially allowing them to be less +tempermental due to lag spikes on the network thread, e.g. that caused +by plugins that are interacting with netty. + +We also add a system property to allow people to tweak how long the server +will wait for a reply. There is a compromise here between lower and higher +values, lower values will mean that dead connections can be closed sooner, +whereas higher values will make this less sensitive to issues such as spikes +from networking or during connections flood of chunk packets on slower clients, + at the cost of dead connections being kept open for longer. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f0aab8639c7d8440e4d70dd096200313d7958780..525728268f56470fdc24c4fd2f19d66943447778 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -221,7 +221,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private final MinecraftServer server; + public ServerPlayer player; + private int tickCount; +- private long keepAliveTime; private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER ++ private long keepAliveTime = Util.getMillis(); private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER + private boolean keepAlivePending; private void setPendingPing(boolean isPending) { this.keepAlivePending = isPending;}; private boolean isPendingPing() { return this.keepAlivePending;}; // Paper - OBFHELPER + private long keepAliveChallenge; private void setKeepAliveID(long keepAliveID) { this.keepAliveChallenge = keepAliveID;}; private long getKeepAliveID() {return this.keepAliveChallenge; }; // Paper - OBFHELPER + // CraftBukkit start - multithreaded fields +@@ -252,6 +252,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private int aboveGroundVehicleTickCount; + private int receivedMovePacketCount; + private int knownMovePacketCount; ++ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { + this.server = server; +@@ -338,18 +339,25 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + this.server.getProfiler().push("keepAlive"); +- long i = Util.getMillis(); +- +- if (i - this.keepAliveTime >= 25000L) { // CraftBukkit +- if (this.keepAlivePending) { +- this.disconnect(new TranslatableComponent("disconnect.timeout")); +- } else { +- this.keepAlivePending = true; +- this.keepAliveTime = i; +- this.keepAliveChallenge = i; +- this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge)); ++ // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings ++ // This should effectively place the keepalive handling back to "as it was" before 1.12.2 ++ long currentTime = Util.getMillis(); ++ long elapsedTime = currentTime - this.getLastPing(); ++ ++ if (this.isPendingPing()) { ++ if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected ++ ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info ++ this.disconnect(new TranslatableComponent("disconnect.timeout", new Object[0])); ++ } ++ } else { ++ if (elapsedTime >= 15000L) { // 15 seconds ++ this.setPendingPing(true); ++ this.setLastPing(currentTime); ++ this.setKeepAliveID(currentTime); ++ this.send(new ClientboundKeepAlivePacket(this.getKeepAliveID())); + } + } ++ // Paper end + + this.server.getProfiler().pop(); + // CraftBukkit start diff --git a/Remapped-Spigot-Server-Patches/0173-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/Remapped-Spigot-Server-Patches/0173-Send-attack-SoundEffects-only-to-players-who-can-see.patch new file mode 100644 index 000000000..d30ac6d73 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0173-Send-attack-SoundEffects-only-to-players-who-can-see.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 31 Oct 2017 03:26:18 +0100 +Subject: [PATCH] Send attack SoundEffects only to players who can see the + attacker + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 3205bc2fb6c9031be68ff46dfca927e681163fa8..43868c1e2d2c858a4f02119c3238f615f9b1ee72 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -28,6 +28,7 @@ import net.minecraft.network.chat.MutableComponent; + import net.minecraft.network.chat.TextComponent; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; ++import net.minecraft.network.protocol.game.ClientboundSoundPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; +@@ -1123,7 +1124,7 @@ public abstract class Player extends LivingEntity { + int i = b0 + EnchantmentHelper.getKnockbackBonus((LivingEntity) this); + + if (this.isSprinting() && flag) { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + ++i; + flag1 = true; + } +@@ -1198,7 +1199,7 @@ public abstract class Player extends LivingEntity { + } + } + +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.sweepAttack(); + } + +@@ -1226,15 +1227,15 @@ public abstract class Player extends LivingEntity { + } + + if (flag2) { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.crit(target); + } + + if (!flag2 && !flag3) { + if (flag) { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } else { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } + } + +@@ -1286,7 +1287,7 @@ public abstract class Player extends LivingEntity { + + this.applyExhaustion(level.spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + if (flag4) { + target.clearFire(); + } +@@ -1721,6 +1722,14 @@ public abstract class Player extends LivingEntity { + public int getXpNeededForNextLevel() { + return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); + } ++ // Paper start - send SoundEffect to everyone who can see fromEntity ++ private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { ++ fromEntity.level.playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity himself ++ if (fromEntity instanceof ServerPlayer) { ++ ((ServerPlayer) fromEntity).connection.send(new ClientboundSoundPacket(soundEffect, soundCategory, x, y, z, volume, pitch)); ++ } ++ } ++ // Paper end + + // CraftBukkit start + public void causeFoodExhaustion(float exhaustion) { diff --git a/Remapped-Spigot-Server-Patches/0174-Option-for-maximum-exp-value-when-merging-orbs.patch b/Remapped-Spigot-Server-Patches/0174-Option-for-maximum-exp-value-when-merging-orbs.patch new file mode 100644 index 000000000..c2626fdee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0174-Option-for-maximum-exp-value-when-merging-orbs.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 10 Nov 2017 23:03:12 -0500 +Subject: [PATCH] Option for maximum exp value when merging orbs + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 90ca51dfdbb3045dd528450225cba96f5834166e..6c692e58cde22003ecbf6dc5695799147c39905a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -342,4 +342,10 @@ public class PaperWorldConfig { + disableCreeperLingeringEffect = getBoolean("disable-creeper-lingering-effect", false); + log("Creeper lingering effect: " + disableCreeperLingeringEffect); + } ++ ++ public int expMergeMaxValue; ++ private void expMergeMaxValue() { ++ expMergeMaxValue = getInt("experience-merge-max-value", -1); ++ log("Experience Merge Max Value: " + expMergeMaxValue); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 3f082b7fd50752728917a7da28cba4cb396a9fdf..7d6834796259e364196280ffa468b5bf999ec7b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -587,16 +587,32 @@ public class CraftEventFactory { + net.minecraft.world.entity.ExperienceOrb xp = (net.minecraft.world.entity.ExperienceOrb) entity; + double radius = world.spigotConfig.expMerge; + if (radius > 0) { ++ // Paper start - Maximum exp value when merging - Whole section has been tweaked, see comments for specifics ++ final int maxValue = world.paperConfig.expMergeMaxValue; ++ final boolean mergeUnconditionally = world.paperConfig.expMergeMaxValue <= 0; ++ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary ++ + List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof net.minecraft.world.entity.ExperienceOrb) { + net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; +- if (!loopItem.removed) { +- xp.value += loopItem.value; +- loopItem.remove(); ++ // Paper start ++ if (!loopItem.removed && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ long newTotal = (long)xp.value + (long)loopItem.value; ++ if ((int) newTotal < 0) continue; // Overflow ++ if (maxValue > 0 && newTotal > (long)maxValue) { ++ loopItem.value = (int) (newTotal - maxValue); ++ xp.value = maxValue; ++ } else { ++ xp.value += loopItem.value; ++ loopItem.remove(); ++ } ++ // Paper end + } + } + } ++ ++ } // Paper end - End iteration skip check - All tweaking ends here + } + // Spigot end + } else if (!(entity instanceof ServerPlayer)) { diff --git a/Remapped-Spigot-Server-Patches/0175-Add-PlayerArmorChangeEvent.patch b/Remapped-Spigot-Server-Patches/0175-Add-PlayerArmorChangeEvent.patch new file mode 100644 index 000000000..ae78b560a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0175-Add-PlayerArmorChangeEvent.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: pkt77 +Date: Fri, 10 Nov 2017 23:46:34 -0500 +Subject: [PATCH] Add PlayerArmorChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java +index b686b7a2faa4fbce37dcc3598b3c956661b91aaa..17cd5f525a45058ce34c66c87f9c133033bb8f4b 100644 +--- a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java ++++ b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java +@@ -16,6 +16,7 @@ public enum EquipmentSlot { + this.name = s; + } + ++ public EquipmentSlot.Type getType() { return this.getType(); } // Paper - OBFHELPER + public EquipmentSlot.Type getType() { + return this.type; + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d6b4fabd232958ae1fd5405c7129551951cd7765..8b0d1f4fbc43a6f37a5f9c453b5dd142a4f69745 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity; + ++import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; // Paper + import com.google.common.base.Objects; + import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; +@@ -2643,6 +2644,13 @@ public abstract class LivingEntity extends Entity { + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); + + if (!ItemStack.matches(itemstack1, itemstack)) { ++ // Paper start - PlayerArmorChangeEvent ++ if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.ARMOR) { ++ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack); ++ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemstack1); ++ new PlayerArmorChangeEvent((Player) this.getBukkitEntity(), PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent(); ++ } ++ // Paper end + if (map == null) { + map = Maps.newEnumMap(EquipmentSlot.class); + } diff --git a/Remapped-Spigot-Server-Patches/0176-Prevent-logins-from-being-processed-when-the-player-.patch b/Remapped-Spigot-Server-Patches/0176-Prevent-logins-from-being-processed-when-the-player-.patch new file mode 100644 index 000000000..c04d5e61c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0176-Prevent-logins-from-being-processed-when-the-player-.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: killme +Date: Sun, 12 Nov 2017 19:40:01 +0100 +Subject: [PATCH] Prevent logins from being processed when the player has + disconnected + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 4b91699ddfa2ee298af5ba25447a85751facf4a4..ff83fb15d0d0adb62c630fc7aafc134972bf15fc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -73,7 +73,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + } + // Paper end + if (this.state == ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT) { +- this.handleAcceptedLogin(); ++ // Paper start - prevent logins to be processed even though disconnect was called ++ if (connection.isConnected()) { ++ this.handleAcceptedLogin(); ++ } ++ // Paper end + } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { + ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); + diff --git a/Remapped-Spigot-Server-Patches/0177-use-CB-BlockState-implementations-for-captured-block.patch b/Remapped-Spigot-Server-Patches/0177-use-CB-BlockState-implementations-for-captured-block.patch new file mode 100644 index 000000000..939a2fed5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0177-use-CB-BlockState-implementations-for-captured-block.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 16 Nov 2017 12:12:41 +0000 +Subject: [PATCH] use CB BlockState implementations for captured blocks + +When modifying the world, CB will store a copy of the affected +blocks in order to restore their state in the case that the event +is cancelled. This change only modifies the collection of blocks +in the world by normal means, e.g. not during tree population, +as the potentially marginal overheads would serve no advantage. + +CB was using a CraftBlockState for all blocks, which causes issues +should any block that uses information beyond a data ID would suffer +from missing information, e.g. Skulls. + +By using CBs CraftBlock#getState(), we will maintain a proper copy of +the blockstate that will be valid for restoration, as opposed to dropping +information on restoration when the event is cancelled. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 81713c97f35263f4ab8d14f8b707aac3d6afea11..b4248d46ccb1a95e21601bca1198512287edcabf 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -124,7 +124,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; +- public Map capturedBlockStates = new java.util.LinkedHashMap<>(); ++ public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper + public Map capturedTileEntities = new HashMap<>(); + public List captureDrops; + public long ticksPerAnimalSpawns; +@@ -346,7 +346,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { +- CapturedBlockState blockstate = capturedBlockStates.get(pos); ++ CraftBlockState blockstate = capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); + this.capturedBlockStates.put(pos.immutable(), blockstate); +@@ -366,7 +366,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit start - capture blockstates + boolean captured = false; + if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) { +- CapturedBlockState blockstate = CapturedBlockState.getBlockState(this, pos, flags); ++ CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot ++ blockstate.setFlag(flags); // Paper - set flag + this.capturedBlockStates.put(pos.immutable(), blockstate); + captured = true; + } +@@ -624,7 +625,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public BlockState getBlockState(BlockPos pos) { + // CraftBukkit start - tree generation + if (captureTreeGeneration) { +- CapturedBlockState previous = capturedBlockStates.get(pos); ++ CraftBlockState previous = capturedBlockStates.get(pos); // Paper + if (previous != null) { + return previous.getHandle(); + } diff --git a/Remapped-Spigot-Server-Patches/0178-API-to-get-a-BlockState-without-a-snapshot.patch b/Remapped-Spigot-Server-Patches/0178-API-to-get-a-BlockState-without-a-snapshot.patch new file mode 100644 index 000000000..d3837145f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0178-API-to-get-a-BlockState-without-a-snapshot.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 6 Nov 2017 21:08:22 -0500 +Subject: [PATCH] API to get a BlockState without a snapshot + +This allows you to get a BlockState without creating a snapshot, operating +on the real tile entity. + +This is useful for where performance is needed + +also Avoid NPE during CraftBlockEntityState load if could not get TE + +If Tile Entity was null, correct Sign to return empty lines instead of null + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index d08ed44884726ca2ba4578226b8aa6244778f4c7..84012c2d12817e657b046bc168cc8eddebcd3831 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -47,6 +47,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + public BlockEntity(BlockEntityType type) { + this.worldPosition = BlockPos.ZERO; + this.type = type; ++ persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init + } + + // Paper start +@@ -95,7 +96,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + public void load(BlockState state, CompoundTag tag) { + this.worldPosition = new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + // CraftBukkit start - read container +- this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); ++ this.persistentDataContainer.clear(); // Paper - clear instead of reinit + + net.minecraft.nbt.Tag persistentDataTag = tag.get("PublicBukkitValues"); + if (persistentDataTag instanceof CompoundTag) { +@@ -245,7 +246,12 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + } + + // CraftBukkit start - add method ++ // Paper start + public InventoryHolder getOwner() { ++ return getOwner(true); ++ } ++ public InventoryHolder getOwner(boolean useSnapshot) { ++ // Paper end + if (level == null) return null; + // Spigot start + org.bukkit.block.Block block = level.getWorld().getBlockAt(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +@@ -254,7 +260,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + return null; + } + // Spigot end +- org.bukkit.block.BlockState state = block.getState(); ++ org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper + if (state instanceof InventoryHolder) return (InventoryHolder) state; + return null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 9d5b3801205e2800b0bcf238c5656321e3654f03..d73086970db19531db66c2e8af52da91d0b1ea28 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -313,6 +313,20 @@ public class CraftBlock implements Block { + + @Override + public BlockState getState() { ++ // Paper start - allow disabling the use of snapshots ++ return getState(true); ++ } ++ public BlockState getState(boolean useSnapshot) { ++ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; ++ CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; ++ try { ++ return getState0(); ++ } finally { ++ CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ } ++ } ++ public BlockState getState0() { ++ // Paper end + Material material = getType(); + + switch (material) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index e89a93082fe07fdb14df8ffef5beca5bd52d7866..730fda7f0bf02400d349959e9cc2aafaed000b21 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -26,20 +26,40 @@ public class CraftBlockEntityState extends CraftBlockStat + this.tileEntity = tileEntityClass.cast(world.getHandle().getBlockEntity(this.getPosition())); + Preconditions.checkState(this.tileEntity != null, "Tile is null, asynchronous access? %s", block); + ++ // Paper start ++ this.snapshotDisabled = DISABLE_SNAPSHOT; ++ if (DISABLE_SNAPSHOT) { ++ this.snapshot = this.tileEntity; ++ } else { ++ this.snapshot = this.createSnapshot(this.tileEntity); ++ } + // copy tile entity data: +- this.snapshot = this.createSnapshot(tileEntity); +- this.load(snapshot); ++ if(this.snapshot != null) { ++ this.load(this.snapshot); ++ } ++ // Paper end + } + ++ public final boolean snapshotDisabled; // Paper ++ public static boolean DISABLE_SNAPSHOT = false; // Paper ++ + public CraftBlockEntityState(Material material, T tileEntity) { + super(material); + + this.tileEntityClass = (Class) tileEntity.getClass(); + this.tileEntity = tileEntity; +- ++ // Paper start ++ this.snapshotDisabled = DISABLE_SNAPSHOT; ++ if (DISABLE_SNAPSHOT) { ++ this.snapshot = this.tileEntity; ++ } else { ++ this.snapshot = this.createSnapshot(this.tileEntity); ++ } + // copy tile entity data: +- this.snapshot = this.createSnapshot(tileEntity); +- this.load(snapshot); ++ if(this.snapshot != null) { ++ this.load(this.snapshot); ++ } ++ // Paper end + } + + private T createSnapshot(T tileEntity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +index e3664110bef9315cfde5b61dde98dce77016600e..10ba8b810c1759adc439f753d36108e30cf70140 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +@@ -155,4 +155,10 @@ public final class CraftPersistentDataContainer implements PersistentDataContain + public Map serialize() { + return (Map) CraftNBTTagConfigSerializer.serialize(toTagCompound()); + } ++ ++ // Paper start ++ public void clear() { ++ this.customDataTags.clear(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0179-AsyncTabCompleteEvent.patch b/Remapped-Spigot-Server-Patches/0179-AsyncTabCompleteEvent.patch new file mode 100644 index 000000000..1bd4fdfc0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0179-AsyncTabCompleteEvent.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 26 Nov 2017 13:19:58 -0500 +Subject: [PATCH] AsyncTabCompleteEvent + +Let plugins be able to control tab completion of commands and chat async. + +This will be useful for frameworks like ACF so we can define async safe completion handlers, +and avoid going to main for tab completions. + +Especially useful if you need to query a database in order to obtain the results for tab +completion, such as offline players. + +Also adds isCommand and getLocation to the sync TabCompleteEvent + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 525728268f56470fdc24c4fd2f19d66943447778..8d0c44b6c2c99d5161c5d4b79209b79ff6db75e4 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -711,10 +711,10 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + @Override + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { +- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); ++ // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start + if (chatSpamField.addAndGet(this, 1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { +- this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0])); ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper + return; + } + // CraftBukkit end +@@ -724,12 +724,35 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + stringreader.skip(); + } + +- ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ // Paper start - async tab completion ++ com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; ++ java.util.List completions = new java.util.ArrayList<>(); ++ String buffer = packet.getCommand(); ++ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(), completions, ++ buffer, true, null); ++ event.callEvent(); ++ completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server ++ if (!event.isHandled()) { ++ if (!event.isCancelled()) { + +- this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer +- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); +- }); ++ this.server.scheduleOnMain(() -> { // Paper - This needs to be on main ++ ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ ++ this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { ++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); ++ }); ++ }); ++ } ++ } else if (!completions.isEmpty()) { ++ com.mojang.brigadier.suggestion.SuggestionsBuilder builder = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); ++ ++ builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); ++ completions.forEach(builder::suggest); ++ player.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); ++ } ++ // Paper end - async tab completion + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index df68599520189e2699c8521d6c6ab7235612af33..10addb128a357e7719854bf4f9d75f5def32b27d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1850,7 +1850,7 @@ public final class CraftServer implements Server { + offers = tabCompleteChat(player, message); + } + +- TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); ++ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? net.minecraft.server.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), new BlockPos(pos)) : null); // Paper + getPluginManager().callEvent(tabEvent); + + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index a957695457cf3252848ce6ef37069692841b8e28..c5e00bd9e2790992202aadf8eec2002fc88c78f1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -28,6 +28,39 @@ public class ConsoleCommandCompleter implements Completer { + public void complete(LineReader reader, ParsedLine line, List candidates) { + final CraftServer server = this.server.server; + final String buffer = line.line(); ++ // Async Tab Complete ++ com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; ++ java.util.List completions = new java.util.ArrayList<>(); ++ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), completions, ++ buffer, true, null); ++ event.callEvent(); ++ completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ ++ if (event.isCancelled() || event.isHandled()) { ++ // Still fire sync event with the provided completions, if someone is listening ++ if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ List finalCompletions = completions; ++ Waitable> syncCompletions = new Waitable>() { ++ @Override ++ protected List evaluate() { ++ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, finalCompletions); ++ return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); ++ } ++ }; ++ server.getServer().processQueue.add(syncCompletions); ++ try { ++ completions = syncCompletions.get(); ++ } catch (InterruptedException | ExecutionException e1) { ++ e1.printStackTrace(); ++ } ++ } ++ ++ if (!completions.isEmpty()) { ++ candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList())); ++ } ++ return; ++ } ++ + // Paper end + Waitable> waitable = new Waitable>() { + @Override diff --git a/Remapped-Spigot-Server-Patches/0180-Avoid-NPE-in-PathfinderGoalTempt.patch b/Remapped-Spigot-Server-Patches/0180-Avoid-NPE-in-PathfinderGoalTempt.patch new file mode 100644 index 000000000..5d0147c51 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0180-Avoid-NPE-in-PathfinderGoalTempt.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 29 Nov 2017 22:18:54 -0500 +Subject: [PATCH] Avoid NPE in PathfinderGoalTempt + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java +index 186025458e923d153e9e47c2be147a9bb53db517..11ca6a752bac4ba4bc683bef844d204b739fab63 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -63,7 +63,7 @@ public class TemptGoal extends Goal { + } + this.target = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle(); + } +- return tempt; ++ return tempt && this.target != null; // Paper - must have target - plugin might of cancelled + // CraftBukkit end + } + } diff --git a/Remapped-Spigot-Server-Patches/0181-PlayerPickupExperienceEvent.patch b/Remapped-Spigot-Server-Patches/0181-PlayerPickupExperienceEvent.patch new file mode 100644 index 000000000..fe7129d33 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0181-PlayerPickupExperienceEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:02:53 -0500 +Subject: [PATCH] PlayerPickupExperienceEvent + +Allows plugins to cancel a player picking up an experience orb + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 3ddb0a9f15c920c9a2080f76edfda0504c1e287a..885c5a920204a31b24c7d360390eaf4177c30698 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -5,6 +5,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundAddExperienceOrbPacket; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.tags.FluidTags; + import net.minecraft.tags.Tag; +@@ -231,7 +232,7 @@ public class ExperienceOrb extends Entity { + @Override + public void playerTouch(Player player) { + if (!this.level.isClientSide) { +- if (this.throwTime == 0 && player.takeXpDelay == 0) { ++ if (this.throwTime == 0 && player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(((ServerPlayer) player).getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper + player.takeXpDelay = 2; + player.take(this, 1); + Entry entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, (LivingEntity) player, ItemStack::isDamaged); diff --git a/Remapped-Spigot-Server-Patches/0182-ExperienceOrbMergeEvent.patch b/Remapped-Spigot-Server-Patches/0182-ExperienceOrbMergeEvent.patch new file mode 100644 index 000000000..28b23c94b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0182-ExperienceOrbMergeEvent.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:57:26 -0500 +Subject: [PATCH] ExperienceOrbMergeEvent + +Fired when the server is about to merge 2 experience orbs +Plugins can cancel this if they want to ensure experience orbs do not lose important +metadata such as spawn reason, or conditionally move data from source to target. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7d6834796259e364196280ffa468b5bf999ec7b9..5e8ff18f98b03741ccbb927f87499ae36d775a86 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -597,7 +597,7 @@ public class CraftEventFactory { + if (e instanceof net.minecraft.world.entity.ExperienceOrb) { + net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; + // Paper start +- if (!loopItem.removed && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ if (!loopItem.removed && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper + long newTotal = (long)xp.value + (long)loopItem.value; + if ((int) newTotal < 0) continue; // Overflow + if (maxValue > 0 && newTotal > (long)maxValue) { diff --git a/Remapped-Spigot-Server-Patches/0183-Ability-to-apply-mending-to-XP-API.patch b/Remapped-Spigot-Server-Patches/0183-Ability-to-apply-mending-to-XP-API.patch new file mode 100644 index 000000000..24cf0a602 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0183-Ability-to-apply-mending-to-XP-API.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 20 Dec 2017 17:36:49 -0500 +Subject: [PATCH] Ability to apply mending to XP API + +This allows plugins that give players the ability to apply the experience +points to the Item Mending formula, which will repair an item instead +of giving the player experience points. + +Both an API To standalone mend, and apply mending logic to .giveExp has been added. + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 885c5a920204a31b24c7d360390eaf4177c30698..52b90ef3a145325209d3d903a2b7c9a44c332cbe 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -264,10 +264,12 @@ public class ExperienceOrb extends Entity { + } + } + ++ public final int durToXp(int i) { return durabilityToXp(i); } // Paper OBFHELPER + private int durabilityToXp(int repairAmount) { + return repairAmount / 2; + } + ++ public final int xpToDur(int i) { return xpToDurability(i); } // Paper OBFHELPER + private int xpToDurability(int experienceAmount) { + return experienceAmount * 2; + } +diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +index 045f937f0b7acd73469b65897bea2ca9036acf22..c82e1b6e3ec98530099fd6452fdaaefebfd99b33 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +@@ -269,8 +269,8 @@ public class EnchantmentHelper { + return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0; + } + +- @Nullable +- public static Entry getRandomItemWith(Enchantment enchantment, LivingEntity entity) { ++ public static @javax.annotation.Nonnull ItemStack getRandomEquippedItemWithEnchant(Enchantment enchantment, LivingEntity entityliving) { Entry entry = getRandomItemWith(enchantment, entityliving); return entry != null ? entry.getValue() : ItemStack.NULL_ITEM; } // Paper - OBFHELPER ++ @Nullable public static Entry getRandomItemWith(Enchantment enchantment, LivingEntity entity) { + return getRandomItemWith(enchantment, entity, (itemstack) -> { + return true; + }); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8a5bf0b83c1e65f07e14da0e053a64c34976b91a..efdcb8dac8db15c4bbaed84a7861ce98339e516a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -58,11 +58,14 @@ import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import net.minecraft.server.players.UserWhiteListEntry; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.attributes.AttributeInstance; + import net.minecraft.world.entity.ai.attributes.AttributeMap; + import net.minecraft.world.entity.ai.attributes.Attributes; + import net.minecraft.world.inventory.AbstractContainerMenu; ++import net.minecraft.world.item.enchantment.EnchantmentHelper; ++import net.minecraft.world.item.enchantment.Enchantments; + import net.minecraft.world.level.GameType; + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.saveddata.maps.MapDecoration; +@@ -1176,8 +1179,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return GameMode.getByValue(getHandle().gameMode.getGameModeForPlayer().getId()); + } + ++ // Paper start + @Override +- public void giveExp(int exp) { ++ public int applyMending(int amount) { ++ ServerPlayer handle = getHandle(); ++ // Logic copied from EntityExperienceOrb and remapped to unobfuscated methods/properties ++ net.minecraft.world.item.ItemStack itemstack = EnchantmentHelper.getRandomEquippedItemWithEnchant(Enchantments.MENDING, handle); ++ if (!itemstack.isEmpty() && itemstack.getItem().canBeDepleted()) { ++ ++ ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level); ++ orb.value = amount; ++ orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; ++ orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); ++ ++ int i = Math.min(orb.xpToDur(amount), itemstack.getDamageValue()); ++ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, i); ++ i = event.getRepairAmount(); ++ orb.removed = true; ++ if (!event.isCancelled()) { ++ amount -= orb.durToXp(i); ++ itemstack.setDamageValue(itemstack.getDamageValue() - i); ++ } ++ } ++ return amount; ++ } ++ ++ @Override ++ public void giveExp(int exp, boolean applyMending) { ++ if (applyMending) { ++ exp = this.applyMending(exp); ++ } ++ // Paper end + getHandle().giveExperiencePoints(exp); + } + diff --git a/Remapped-Spigot-Server-Patches/0184-Make-max-squid-spawn-height-configurable.patch b/Remapped-Spigot-Server-Patches/0184-Make-max-squid-spawn-height-configurable.patch new file mode 100644 index 000000000..94fb61436 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0184-Make-max-squid-spawn-height-configurable.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 11 Jan 2018 16:47:28 -0600 +Subject: [PATCH] Make max squid spawn height configurable + +I don't know why upstream made only the minimum height configurable but +whatever + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6c692e58cde22003ecbf6dc5695799147c39905a..3c39f1bb3d88baaaed4dd43c51faeef89bb5c6c2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -348,4 +348,9 @@ public class PaperWorldConfig { + expMergeMaxValue = getInt("experience-merge-max-value", -1); + log("Experience Merge Max Value: " + expMergeMaxValue); + } ++ ++ public double squidMaxSpawnHeight; ++ private void squidMaxSpawnHeight() { ++ squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java +index 0b782c77f6d93002c35b123044b5a3eb03e63672..5a7582fd4f8e883d2f08a0227932c17d7576b957 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Squid.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java +@@ -196,7 +196,8 @@ public class Squid extends WaterAnimal { + } + + public static boolean checkSquidSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { +- return pos.getY() > world.getLevel().spigotConfig.squidSpawnRangeMin && pos.getY() < world.getSeaLevel(); // Spigot ++ final double maxHeight = world.getLevel().paperConfig.squidMaxSpawnHeight > 0 ? world.getLevel().paperConfig.squidMaxSpawnHeight : world.getSeaLevel(); // Paper ++ return pos.getY() > world.getLevel().spigotConfig.squidSpawnRangeMin && pos.getY() < maxHeight; // Spigot // Paper + } + + public void setMovementVector(float x, float y, float z) { diff --git a/Remapped-Spigot-Server-Patches/0185-PreCreatureSpawnEvent.patch b/Remapped-Spigot-Server-Patches/0185-PreCreatureSpawnEvent.patch new file mode 100644 index 000000000..341927c26 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0185-PreCreatureSpawnEvent.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 14 Jan 2018 17:01:31 -0500 +Subject: [PATCH] PreCreatureSpawnEvent + +Adds an event to fire before an Entity is created, so that plugins that need to cancel +CreatureSpawnEvent can do so from this event instead. + +Cancelling CreatureSpawnEvent rapidly causes a lot of garbage collection and CPU waste +as it's done after the Entity object has been fully created. + +Mob Limiting plugins and blanket "ban this type of monster" plugins should use this event +instead and save a lot of server resources. + +See: https://github.com/PaperMC/Paper/issues/917 + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index e3d92d1d35911b2960a7ca82bd4f324d285d0533..e39d950783599b01271bdb7e67fe68b46af0c49c 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -17,6 +17,7 @@ import net.minecraft.nbt.ListTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.tags.BlockTags; +@@ -317,6 +318,20 @@ public class EntityType { + + @Nullable + public T spawnCreature(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - Call PreCreatureSpawnEvent ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityType.getKey(this).getPath()); ++ if (type != null) { ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(worldserver, blockposition), ++ type, ++ spawnReason ++ ); ++ if (!event.callEvent()) { ++ return null; ++ } ++ } ++ // Paper end + T t0 = this.create(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); + + if (t0 != null) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java +index f3a7807a20b279056d5640ab02aa77f7b1dabc2a..880d69bad933294a2cfdea9adb3e648e29eb42be 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java +@@ -33,8 +33,8 @@ public class GolemSensor extends Sensor { + Optional> optional = entityliving.getBrain().getMemory(MemoryModuleType.MOBS); + + if (optional.isPresent()) { +- boolean flag = ((List) optional.get()).stream().anyMatch((entityliving1) -> { +- return entityliving1.getEntityType().equals(EntityType.IRON_GOLEM); ++ boolean flag = optional.get().stream().anyMatch((entityliving1) -> { // Paper - decompile fixes ++ return entityliving1.getType().equals(EntityType.IRON_GOLEM); + }); + + if (flag) { +@@ -44,6 +44,7 @@ public class GolemSensor extends Sensor { + } + } + ++ public static void setDetectedRecently(LivingEntity entityLiving) { golemDetected(entityLiving); } // Paper - OBFHELPER + public static void golemDetected(LivingEntity entityliving) { + entityliving.getBrain().setMemoryWithExpiry(MemoryModuleType.GOLEM_DETECTED_RECENTLY, true, 600L); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index a66fab2e04a5d87ced139ed15d2434c5ffcec695..eed6265dc8275921a18fc5f4970ba131ba782132 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -30,6 +30,7 @@ import net.minecraft.network.protocol.game.DebugPackets; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvent; +@@ -942,6 +943,21 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + BlockPos blockposition1 = this.findSpawnPositionForGolemInColumn(blockposition, d0, d1); + + if (blockposition1 != null) { ++ // Paper start - Call PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(level, blockposition1), ++ org.bukkit.entity.EntityType.IRON_GOLEM, ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ GolemSensor.golemDetected(this); // Set Golem Last Seen to stop it from spawning another one ++ return null; ++ } ++ break; ++ } ++ // Paper end + IronGolem entityirongolem = (IronGolem) EntityType.IRON_GOLEM.create(world, (CompoundTag) null, (Component) null, (Player) null, blockposition1, MobSpawnType.MOB_SUMMONED, false, false); + + if (entityirongolem != null) { +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 4582fc1bb767214241568fbc22b0ee2cbf3322e0..ac572eba10a7239d71dfae060f623b076d4252ce 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -12,6 +12,7 @@ import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.StringUtil; + import net.minecraft.util.WeighedRandom; +@@ -125,6 +126,27 @@ public abstract class BaseSpawner { + ServerLevel worldserver = (ServerLevel) world; + + if (SpawnPlacements.checkSpawnRules((EntityType) optional.get(), worldserver, MobSpawnType.SPAWNER, new BlockPos(d3, d4, d5), world.getRandom())) { ++ // Paper start ++ EntityType entityType = optional.get(); ++ String key = EntityType.getKey(entityType).getPath(); ++ ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); ++ if (type != null) { ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(world, d3, d4, d5), ++ type, ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ ); ++ if (!event.callEvent()) { ++ flag = true; ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ } ++ // Paper end + Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, (entity1) -> { + entity1.moveTo(d3, d4, d5, entity1.yRot, entity1.xRot); + return entity1; +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index a19ac1cb7e4d8d478648a048b2bfa0daf85a80c9..8a71eaf2855be0d415d1f7b18dbec98353fe5b47 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -15,6 +15,7 @@ import net.minecraft.core.Direction; + import net.minecraft.core.Position; + import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.FluidTags; +@@ -214,9 +215,16 @@ public final class NaturalSpawner { + j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount); + } + +- if (isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ // Paper start ++ Boolean doSpawning = a(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ if (doSpawning == null) { ++ return; ++ } ++ if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ // Paper end + Mob entityinsentient = getMobForSpawn(world, biomesettingsmobs_c.type); + ++ + if (entityinsentient == null) { + return; + } +@@ -269,17 +277,33 @@ public final class NaturalSpawner { + } + } + +- private static boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureFeatureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { +- EntityType entitytypes = spawnEntry.type; ++ private static Boolean a(ServerLevel worldserver, MobCategory enumcreaturetype, StructureFeatureManager structuremanager, ChunkGenerator chunkgenerator, MobSpawnSettings.SpawnerData biomesettingsmobs_c, BlockPos.MutableBlockPos blockposition_mutableblockposition, double d0) { // Paper ++ EntityType entitytypes = biomesettingsmobs_c.type; ++ // Paper start ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityType.getKey(entitytypes).getPath()); ++ if (type != null) { ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(worldserver, blockposition_mutableblockposition), ++ type, SpawnReason.NATURAL ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ return null; ++ } ++ return false; ++ } ++ } ++ // Paper end + + if (entitytypes.getCategory() == MobCategory.MISC) { + return false; +- } else if (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance())) { ++ } else if (!entitytypes.canSpawnFarFromPlayer() && d0 > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance())) { + return false; +- } else if (entitytypes.canSummon() && canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, (BlockPos) pos)) { ++ } else if (entitytypes.canSummon() && canSpawnMobAt(worldserver, structuremanager, chunkgenerator, enumcreaturetype, biomesettingsmobs_c, (BlockPos) blockposition_mutableblockposition)) { + SpawnPlacements.Type entitypositiontypes_surface = SpawnPlacements.getPlacementType(entitytypes); + +- return !isSpawnPositionOk(entitypositiontypes_surface, (LevelReader) world, pos, entitytypes) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, MobSpawnType.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D))); ++ return !isSpawnPositionOk(entitypositiontypes_surface, (LevelReader) worldserver, blockposition_mutableblockposition, entitytypes) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, worldserver, MobSpawnType.NATURAL, blockposition_mutableblockposition, worldserver.random) ? false : worldserver.noCollision(entitytypes.getAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D))); + } else { + return false; + } diff --git a/Remapped-Spigot-Server-Patches/0186-PlayerNaturallySpawnCreaturesEvent.patch b/Remapped-Spigot-Server-Patches/0186-PlayerNaturallySpawnCreaturesEvent.patch new file mode 100644 index 000000000..0b2a988e3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0186-PlayerNaturallySpawnCreaturesEvent.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 14 Jan 2018 17:36:02 -0500 +Subject: [PATCH] PlayerNaturallySpawnCreaturesEvent + +This event can be used for when you want to exclude a certain player +from triggering monster spawns on a server. + +Also a highly more effecient way to blanket block spawns in a world + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 3b6f35b695117bd2b0c71b994efc55fa1084eddc..97d5437df10a6d0124e944404e88650547b7d8a8 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -964,12 +964,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; + chunkRange = (chunkRange > 8) ? 8 : chunkRange; + +- double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; ++ final int finalChunkRange = chunkRange; // Paper for lambda below ++ //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event + // Spigot end + long i = chunkcoordintpair.toLong(); + + return !this.distanceManager.hasPlayersNearby(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { +- return !entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange; // Spigot ++ // Paper start - ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ double blockRange = 16384.0D; ++ if (reducedRange) { ++ event = entityplayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) return false; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ ++ return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot ++ // Paper end + }); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index d0b0fdaf5451bcc7f7ac7dab28aa59ef77e6dd97..a7122a0411f4a8656efd4facde3403c8093bc8a6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -612,6 +612,15 @@ public class ServerChunkCache extends ChunkSource { + this.level.getProfiler().pop(); + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper ++ //Paper start - call player naturally spawn event ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; ++ chunkRange = Math.min(chunkRange, 8); ++ for (ServerPlayer entityPlayer : this.level.players()) { ++ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); ++ entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ }; ++ // Paper end + this.level.timings.chunkTicks.startTiming(); // Paper + this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no... + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 4ff66138fa43cf932b95d6d3dc050a9cd7b28ad4..0fa977a31cf945b74f3a046b6be302b10f494ad1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent; + import com.google.common.collect.Lists; + import com.mojang.authlib.GameProfile; + import com.mojang.datafixers.util.Either; +@@ -225,6 +226,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public boolean sentListPacket = false; + public Integer clientViewDistance; + // CraftBukkit end ++ public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + diff --git a/Remapped-Spigot-Server-Patches/0187-Add-setPlayerProfile-API-for-Skulls.patch b/Remapped-Spigot-Server-Patches/0187-Add-setPlayerProfile-API-for-Skulls.patch new file mode 100644 index 000000000..746bff56a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0187-Add-setPlayerProfile-API-for-Skulls.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 19 Jan 2018 00:36:25 -0500 +Subject: [PATCH] Add setPlayerProfile API for Skulls + +This allows you to create already filled textures on Skulls to avoid texture lookups +which commonly cause rate limit issues with Mojang API + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java +index 2047ea32489d03051783d18a0dbaf456bfdbb2a1..a06c51a56846750ce59a70e9698c2b57c3517aad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java +@@ -1,5 +1,7 @@ + package org.bukkit.craftbukkit.block; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.base.Preconditions; + import com.mojang.authlib.GameProfile; + import net.minecraft.server.MinecraftServer; +@@ -15,6 +17,7 @@ import org.bukkit.block.data.BlockData; + import org.bukkit.block.data.Directional; + import org.bukkit.block.data.Rotatable; + import org.bukkit.craftbukkit.entity.CraftPlayer; ++import javax.annotation.Nullable; + + public class CraftSkull extends CraftBlockEntityState implements Skull { + +@@ -105,6 +108,20 @@ public class CraftSkull extends CraftBlockEntityState implemen + } + } + ++ // Paper start ++ @Override ++ public void setPlayerProfile(PlayerProfile profile) { ++ Preconditions.checkNotNull(profile, "profile"); ++ this.profile = CraftPlayerProfile.asAuthlibCopy(profile); ++ } ++ ++ @Nullable ++ @Override ++ public PlayerProfile getPlayerProfile() { ++ return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null; ++ } ++ // Paper end ++ + @Override + public BlockFace getRotation() { + BlockData blockData = getBlockData(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 750661540f55d3c59119dcc909e706e571a2123b..aa64ffc23d8941ff05ea7791ddd628c3c6be90e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -4,10 +4,8 @@ import com.google.common.collect.ImmutableMap.Builder; + import com.mojang.authlib.GameProfile; + import java.util.Map; + import java.util.UUID; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.nbt.NbtUtils; +-import net.minecraft.nbt.Tag; +-import net.minecraft.world.level.block.entity.SkullBlockEntity; ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import org.bukkit.Bukkit; + import org.bukkit.Material; + import org.bukkit.OfflinePlayer; +@@ -18,6 +16,11 @@ import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; + import org.bukkit.inventory.meta.SkullMeta; + ++import javax.annotation.Nullable; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtUtils; ++import net.minecraft.nbt.Tag; ++import net.minecraft.world.level.block.entity.SkullBlockEntity; + @DelegateDeserialization(SerializableMeta.class) + class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + +@@ -149,6 +152,19 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + return hasOwner() ? profile.getName() : null; + } + ++ // Paper start ++ @Override ++ public void setPlayerProfile(@Nullable PlayerProfile profile) { ++ setProfile((profile == null) ? null : CraftPlayerProfile.asAuthlibCopy(profile)); ++ } ++ ++ @Nullable ++ @Override ++ public PlayerProfile getPlayerProfile() { ++ return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOwningPlayer() { + if (hasOwner()) { +@@ -175,8 +191,8 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + } else { + // Paper start - Use Online Players Skull + GameProfile newProfile = null; +- net.minecraft.server.EntityPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayerByName(name); +- if (player != null) newProfile = player.getProfile(); ++ net.minecraft.server.level.ServerPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayerByName(name); ++ if (player != null) newProfile = player.getGameProfile(); + if (newProfile == null) newProfile = new GameProfile(null, name); + setProfile(newProfile); + // Paper end diff --git a/Remapped-Spigot-Server-Patches/0188-Fill-Profile-Property-Events.patch b/Remapped-Spigot-Server-Patches/0188-Fill-Profile-Property-Events.patch new file mode 100644 index 000000000..30643d081 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0188-Fill-Profile-Property-Events.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 2 Jan 2018 00:31:26 -0500 +Subject: [PATCH] Fill Profile Property Events + +Allows plugins to populate profile properties from local sources to avoid calls out to Mojang API +to fill in textures for example. + +If Mojang API does need to be hit, event fire so you can get the results. + +This is useful for implementing a ProfileCache for Player Skulls + +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +index 93d73c27340645c7502acafdc0b2cfbc1a759dd8..5c7d2ee19243d0911a3a00af3ae42078a2ccba94 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -1,6 +1,8 @@ + package com.destroystokyo.paper.profile; + + import com.mojang.authlib.Environment; ++import com.destroystokyo.paper.event.profile.FillProfileEvent; ++import com.destroystokyo.paper.event.profile.PreFillProfileEvent; + import com.mojang.authlib.GameProfile; + import com.mojang.authlib.minecraft.MinecraftProfileTexture; + import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +@@ -20,7 +22,15 @@ public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionServi + + @Override + public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { +- return super.fillProfileProperties(profile, requireSecure); ++ CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile); ++ new PreFillProfileEvent(playerProfile).callEvent(); ++ profile = playerProfile.getGameProfile(); ++ if (profile.isComplete() && profile.getProperties().containsKey("textures")) { ++ return profile; ++ } ++ GameProfile gameProfile = super.fillProfileProperties(profile, requireSecure); ++ new FillProfileEvent(CraftPlayerProfile.asBukkitMirror(gameProfile)).callEvent(); ++ return gameProfile; + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0189-PlayerAdvancementCriterionGrantEvent.patch b/Remapped-Spigot-Server-Patches/0189-PlayerAdvancementCriterionGrantEvent.patch new file mode 100644 index 000000000..6190b30a2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0189-PlayerAdvancementCriterionGrantEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 19 Jan 2018 08:15:29 -0600 +Subject: [PATCH] PlayerAdvancementCriterionGrantEvent + + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 8df1803754817707a5ad292f65276871dacc4508..5ab62fc74085bbbb0c81b2f4d16a35c9345cd1f1 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -297,6 +297,12 @@ public class PlayerAdvancements { + boolean flag1 = advancementprogress.isDone(); + + if (advancementprogress.a(criterionName)) { ++ // Paper start ++ if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.bukkit, criterionName).callEvent()) { ++ advancementprogress.b(criterionName); ++ return false; ++ } ++ // Paper end + this.unregisterListeners(advancement); + this.progressChanged.add(advancement); + flag = true; diff --git a/Remapped-Spigot-Server-Patches/0190-Add-ArmorStand-Item-Meta.patch b/Remapped-Spigot-Server-Patches/0190-Add-ArmorStand-Item-Meta.patch new file mode 100644 index 000000000..14798cca5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0190-Add-ArmorStand-Item-Meta.patch @@ -0,0 +1,296 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 27 Jan 2018 17:04:14 -0500 +Subject: [PATCH] Add ArmorStand Item Meta + +This is adds basic item meta for armor stands. It does not add all +possible metadata however. + +There are armor, hand, and equipment types, as well as position data +that can also be added here. This initial addition should serve a +starting point for future additions in this area. + +Fixes GH-559 + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +index 783b9c7283492fb71776b61f9820c21a39868c47..6521f4f6f660885f59f024640239609fb67d691f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +@@ -9,9 +9,22 @@ import org.bukkit.configuration.serialization.DelegateDeserialization; + import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey; + + @DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +-public class CraftMetaArmorStand extends CraftMetaItem { ++public class CraftMetaArmorStand extends CraftMetaItem implements com.destroystokyo.paper.inventory.meta.ArmorStandMeta { // Paper + + static final ItemMetaKey ENTITY_TAG = new ItemMetaKey("EntityTag", "entity-tag"); ++ // Paper start ++ static final ItemMetaKey INVISIBLE = new ItemMetaKey("Invisible", "invisible"); ++ static final ItemMetaKey NO_BASE_PLATE = new ItemMetaKey("NoBasePlate", "no-base-plate"); ++ static final ItemMetaKey SHOW_ARMS = new ItemMetaKey("ShowArms", "show-arms"); ++ static final ItemMetaKey SMALL = new ItemMetaKey("Small", "small"); ++ static final ItemMetaKey MARKER = new ItemMetaKey("Marker", "marker"); ++ ++ private boolean invisible; ++ private boolean noBasePlate; ++ private boolean showArms; ++ private boolean small; ++ private boolean marker; ++ // Paper end + CompoundTag entityTag; + + CraftMetaArmorStand(CraftMetaItem meta) { +@@ -22,6 +35,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { + } + + CraftMetaArmorStand armorStand = (CraftMetaArmorStand) meta; ++ // Paper start ++ this.invisible = armorStand.invisible; ++ this.noBasePlate = armorStand.noBasePlate; ++ this.showArms = armorStand.showArms; ++ this.small = armorStand.small; ++ this.marker = armorStand.marker; ++ // Paper end + this.entityTag = armorStand.entityTag; + } + +@@ -30,11 +50,47 @@ public class CraftMetaArmorStand extends CraftMetaItem { + + if (tag.contains(ENTITY_TAG.NBT)) { + entityTag = tag.getCompound(ENTITY_TAG.NBT); ++ ++ // Paper start ++ if (entityTag.contains(INVISIBLE.NBT)) { ++ invisible = entityTag.getBoolean(INVISIBLE.NBT); ++ } ++ ++ if (entityTag.contains(NO_BASE_PLATE.NBT)) { ++ noBasePlate = entityTag.getBoolean(NO_BASE_PLATE.NBT); ++ } ++ ++ if (entityTag.contains(SHOW_ARMS.NBT)) { ++ showArms = entityTag.getBoolean(SHOW_ARMS.NBT); ++ } ++ ++ if (entityTag.contains(SMALL.NBT)) { ++ small = entityTag.getBoolean(SMALL.NBT); ++ } ++ ++ if (entityTag.contains(MARKER.NBT)) { ++ marker = entityTag.getBoolean(MARKER.NBT); ++ } ++ // Paper end + } + } + + CraftMetaArmorStand(Map map) { + super(map); ++ ++ // Paper start ++ boolean invis = SerializableMeta.getBoolean(map, INVISIBLE.BUKKIT); ++ boolean noBase = SerializableMeta.getBoolean(map, NO_BASE_PLATE.BUKKIT); ++ boolean showArms = SerializableMeta.getBoolean(map, SHOW_ARMS.BUKKIT); ++ boolean small = SerializableMeta.getBoolean(map, SMALL.BUKKIT); ++ boolean marker = SerializableMeta.getBoolean(map, MARKER.BUKKIT); ++ ++ this.invisible = invis; ++ this.noBasePlate = noBase; ++ this.showArms = showArms; ++ this.small = small; ++ this.marker = marker; ++ // Paper end + } + + @Override +@@ -57,6 +113,32 @@ public class CraftMetaArmorStand extends CraftMetaItem { + void applyToItem(CompoundTag tag) { + super.applyToItem(tag); + ++ // Paper start ++ if (!isArmorStandEmpty() && entityTag == null) { ++ entityTag = new CompoundTag(); ++ } ++ ++ if (isInvisible()) { ++ entityTag.putBoolean(INVISIBLE.NBT, invisible); ++ } ++ ++ if (hasNoBasePlate()) { ++ entityTag.putBoolean(NO_BASE_PLATE.NBT, noBasePlate); ++ } ++ ++ if (shouldShowArms()) { ++ entityTag.putBoolean(SHOW_ARMS.NBT, showArms); ++ } ++ ++ if (isSmall()) { ++ entityTag.putBoolean(SMALL.NBT, small); ++ } ++ ++ if (isMarker()) { ++ entityTag.putBoolean(MARKER.NBT, marker); ++ } ++ // Paper end ++ + if (entityTag != null) { + tag.put(ENTITY_TAG.NBT, entityTag); + } +@@ -78,7 +160,7 @@ public class CraftMetaArmorStand extends CraftMetaItem { + } + + boolean isArmorStandEmpty() { +- return !(entityTag != null); ++ return !(isInvisible() || hasNoBasePlate() || shouldShowArms() || isSmall() || isMarker() || entityTag != null); + } + + @Override +@@ -89,7 +171,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { + if (meta instanceof CraftMetaArmorStand) { + CraftMetaArmorStand that = (CraftMetaArmorStand) meta; + +- return entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : entityTag == null; ++ // Paper start ++ return invisible == that.invisible && ++ noBasePlate == that.noBasePlate && ++ showArms == that.showArms && ++ small == that.small && ++ marker == that.marker; ++ // Paper end + } + return true; + } +@@ -104,9 +192,14 @@ public class CraftMetaArmorStand extends CraftMetaItem { + final int original; + int hash = original = super.applyHash(); + +- if (entityTag != null) { +- hash = 73 * hash + entityTag.hashCode(); +- } ++ // Paper start ++ hash += entityTag != null ? 73 * hash + entityTag.hashCode() : 0; ++ hash += isInvisible() ? 61 * hash + 1231 : 0; ++ hash += hasNoBasePlate() ? 61 * hash + 1231 : 0; ++ hash += shouldShowArms() ? 61 * hash + 1231 : 0; ++ hash += isSmall() ? 61 * hash + 1231 : 0; ++ hash += isMarker() ? 61 * hash + 1231 : 0; ++ // Paper end + + return original != hash ? CraftMetaArmorStand.class.hashCode() ^ hash : hash; + } +@@ -115,6 +208,28 @@ public class CraftMetaArmorStand extends CraftMetaItem { + Builder serialize(Builder builder) { + super.serialize(builder); + ++ // Paper start ++ if (isInvisible()) { ++ builder.put(INVISIBLE.BUKKIT, invisible); ++ } ++ ++ if (hasNoBasePlate()) { ++ builder.put(NO_BASE_PLATE.BUKKIT, noBasePlate); ++ } ++ ++ if (shouldShowArms()) { ++ builder.put(SHOW_ARMS.BUKKIT, showArms); ++ } ++ ++ if (isSmall()) { ++ builder.put(SMALL.BUKKIT, small); ++ } ++ ++ if (isMarker()) { ++ builder.put(MARKER.BUKKIT, marker); ++ } ++ // Paper end ++ + return builder; + } + +@@ -128,4 +243,56 @@ public class CraftMetaArmorStand extends CraftMetaItem { + + return clone; + } ++ ++ // Paper start ++ @Override ++ public boolean isInvisible() { ++ return invisible; ++ } ++ ++ @Override ++ public boolean hasNoBasePlate() { ++ return noBasePlate; ++ } ++ ++ @Override ++ public boolean shouldShowArms() { ++ return showArms; ++ } ++ ++ @Override ++ public boolean isSmall() { ++ return small; ++ } ++ ++ @Override ++ public boolean isMarker() { ++ return marker; ++ } ++ ++ @Override ++ public void setInvisible(boolean invisible) { ++ this.invisible = invisible; ++ } ++ ++ @Override ++ public void setNoBasePlate(boolean noBasePlate) { ++ this.noBasePlate = noBasePlate; ++ } ++ ++ @Override ++ public void setShowArms(boolean showArms) { ++ this.showArms = showArms; ++ } ++ ++ @Override ++ public void setSmall(boolean small) { ++ this.small = small; ++ } ++ ++ @Override ++ public void setMarker(boolean marker) { ++ this.marker = marker; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 521699615778c4b724d10edfee1d3915e036eb2e..64f166fa93e998a58a895d785ff8c9e62dacb1bb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -1441,6 +1441,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaCrossbow.CHARGED.NBT, + CraftMetaCrossbow.CHARGED_PROJECTILES.NBT, + CraftMetaSuspiciousStew.EFFECTS.NBT, ++ // Paper start ++ CraftMetaArmorStand.ENTITY_TAG.NBT, ++ CraftMetaArmorStand.INVISIBLE.NBT, ++ CraftMetaArmorStand.NO_BASE_PLATE.NBT, ++ CraftMetaArmorStand.SHOW_ARMS.NBT, ++ CraftMetaArmorStand.SMALL.NBT, ++ CraftMetaArmorStand.MARKER.NBT, ++ // Paper end + CraftMetaCompass.LODESTONE_DIMENSION.NBT, + CraftMetaCompass.LODESTONE_POS.NBT, + CraftMetaCompass.LODESTONE_TRACKED.NBT +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index 9a351c137776ac622f4df7353bb353142b3a6ccc..42f577ed3508ba5a380648461e149f16ce97c9bd 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -313,6 +313,7 @@ public class ItemMetaTest extends AbstractTestingBase { + final CraftMetaArmorStand meta = (CraftMetaArmorStand) cleanStack.getItemMeta(); + meta.entityTag = new NBTTagCompound(); + meta.entityTag.setBoolean("Small", true); ++ meta.setInvisible(true); // Paper + cleanStack.setItemMeta(meta); + return cleanStack; + } diff --git a/Remapped-Spigot-Server-Patches/0191-Extend-Player-Interact-cancellation.patch b/Remapped-Spigot-Server-Patches/0191-Extend-Player-Interact-cancellation.patch new file mode 100644 index 000000000..826fede56 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0191-Extend-Player-Interact-cancellation.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Feb 2018 10:43:46 +0000 +Subject: [PATCH] Extend Player Interact cancellation + +GUIs are opened on the client, meaning that the server cannot block them from opening, +However, it is possible to close these GUIs from the server. + +Flower pots are also not updated on the client when interaction is cancelled, this patch +also resolves this. + +Update adjacent blocks of doors, double plants, pistons and beds +when cancelling interaction. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 26ce794cb8d089c03fab5dd0a0c910783d10b72e..b1a53093eb5b5f359eedb6c252cf6d0d1ae8e409 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -23,6 +23,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.network.protocol.game.ClientboundBlockBreakAckPacket; + import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; ++import net.minecraft.network.protocol.game.ClientboundContainerClosePacket; + import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket; + import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; + import net.minecraft.server.MinecraftServer; +@@ -180,6 +181,11 @@ public class ServerPlayerGameMode { + PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.inventory.getSelected(), InteractionHand.MAIN_HAND); + if (event.isCancelled()) { + // Let the client know the block still exists ++ // Paper start - brute force neighbor blocks for any attached blocks ++ for (Direction dir : Direction.values()) { ++ this.player.connection.send(new ClientboundBlockUpdatePacket(level, pos.relative(dir))); ++ } ++ // Paper end + this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + // Update any tile entity data for this block + BlockEntity tileentity = this.level.getBlockEntity(pos); +@@ -484,6 +490,7 @@ public class ServerPlayerGameMode { + interactItemStack = stack.copy(); + + if (event.useInteractedBlock() == Event.Result.DENY) { ++ + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. + if (iblockdata.getBlock() instanceof DoorBlock) { + boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; +@@ -496,7 +503,13 @@ public class ServerPlayerGameMode { + + // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) + player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); ++ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method ++ } else if (iblockdata.getBlock() instanceof StructureBlock) { ++ player.connection.send(new ClientboundContainerClosePacket()); ++ } else if (iblockdata.getBlock() instanceof CommandBlock) { ++ player.connection.send(new ClientboundContainerClosePacket()); + } ++ // Paper end - extend Player Interact cancellation + player.getBukkitEntity().updateInventory(); // SPIGOT-2867 + enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; + } else if (this.gameModeForPlayer == GameType.SPECTATOR) { diff --git a/Remapped-Spigot-Server-Patches/0192-Tameable-getOwnerUniqueId-API.patch b/Remapped-Spigot-Server-Patches/0192-Tameable-getOwnerUniqueId-API.patch new file mode 100644 index 000000000..643044c74 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0192-Tameable-getOwnerUniqueId-API.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 24 Feb 2018 01:14:55 -0500 +Subject: [PATCH] Tameable#getOwnerUniqueId API + +This is faster if all you need is the UUID, as .getOwner() will cause +an OfflinePlayer to be loaded from disk. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 6136b3322a340d506ce744bcd15f71a158e46ad1..04b0b2449f20220c74892788080d40e3693151c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -89,6 +89,9 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + } + } + ++ public UUID getOwnerUniqueId() { ++ return getOwnerUUID(); ++ } + public UUID getOwnerUUID() { + return getHandle().getOwnerUUID(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +index 35587fd6d90dfd1da2075c3268692eac7a2eaa25..85fe4f07d35c514f2a7c7920ec416fb651434c83 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +@@ -17,6 +17,9 @@ public class CraftTameableAnimal extends CraftAnimals implements Tameable, Creat + return (TamableAnimal) super.getHandle(); + } + ++ public UUID getOwnerUniqueId() { ++ return getOwnerUUID(); ++ } + public UUID getOwnerUUID() { + try { + return getHandle().getOwnerUUID(); diff --git a/Remapped-Spigot-Server-Patches/0193-Toggleable-player-crits-helps-mitigate-hacked-client.patch b/Remapped-Spigot-Server-Patches/0193-Toggleable-player-crits-helps-mitigate-hacked-client.patch new file mode 100644 index 000000000..671d7965c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0193-Toggleable-player-crits-helps-mitigate-hacked-client.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Sat, 10 Mar 2018 00:50:24 +0100 +Subject: [PATCH] Toggleable player crits, helps mitigate hacked clients. + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3c39f1bb3d88baaaed4dd43c51faeef89bb5c6c2..48f0385c7203c7955de5a015f3dc42be2ab7b681 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -192,6 +192,11 @@ public class PaperWorldConfig { + disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); + } + ++ public boolean disablePlayerCrits; ++ private void disablePlayerCrits() { ++ disablePlayerCrits = getBoolean("game-mechanics.disable-player-crits", false); ++ } ++ + public boolean allChunksAreSlimeChunks; + private void allChunksAreSlimeChunks() { + allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 43868c1e2d2c858a4f02119c3238f615f9b1ee72..63871a3a1981b2e8c7ad74214196e35684acb584 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1131,6 +1131,7 @@ public abstract class Player extends LivingEntity { + + boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; + ++ flag2 = flag2 && !level.paperConfig.disablePlayerCrits; // Paper + flag2 = flag2 && !this.isSprinting(); + if (flag2) { + f *= 1.5F; diff --git a/Remapped-Spigot-Server-Patches/0194-Prevent-Frosted-Ice-from-loading-holding-chunks.patch b/Remapped-Spigot-Server-Patches/0194-Prevent-Frosted-Ice-from-loading-holding-chunks.patch new file mode 100644 index 000000000..4b912daff --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0194-Prevent-Frosted-Ice-from-loading-holding-chunks.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 10 Mar 2018 16:33:15 -0500 +Subject: [PATCH] Prevent Frosted Ice from loading/holding chunks + + +diff --git a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java +index ae2f5acd008d5d7163b56cb4a2d29354299959ca..99843f1ca4737d40ae0626fce931c97bbf5ab81d 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java +@@ -40,7 +40,8 @@ public class FrostedIceBlock extends IceBlock { + Direction enumdirection = aenumdirection[j]; + + blockposition_mutableblockposition.setWithOffset((Vec3i) pos, enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition); ++ BlockState iblockdata1 = world.getTypeIfLoaded(blockposition_mutableblockposition); // Paper ++ if (iblockdata1 == null) { continue; } // Paper + + if (iblockdata1.is((Block) this) && !this.slightlyMelt(iblockdata1, (Level) world, blockposition_mutableblockposition)) { + world.getBlockTicks().scheduleTick(blockposition_mutableblockposition, this, Mth.nextInt(random, world.paperConfig.frostedIceDelayMin, world.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay +@@ -83,7 +84,9 @@ public class FrostedIceBlock extends IceBlock { + Direction enumdirection = aenumdirection[l]; + + blockposition_mutableblockposition.setWithOffset((Vec3i) pos, enumdirection); +- if (world.getBlockState(blockposition_mutableblockposition).is((Block) this)) { ++ // Paper start ++ BlockState type = world.getTypeIfLoaded(blockposition_mutableblockposition); ++ if (type != null && type.is((Block) this)) { // Paper end + ++j; + if (j >= maxNeighbors) { + return false; diff --git a/Remapped-Spigot-Server-Patches/0195-Disable-Explicit-Network-Manager-Flushing.patch b/Remapped-Spigot-Server-Patches/0195-Disable-Explicit-Network-Manager-Flushing.patch new file mode 100644 index 000000000..45eef88da --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0195-Disable-Explicit-Network-Manager-Flushing.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 11 Mar 2018 14:13:33 -0400 +Subject: [PATCH] Disable Explicit Network Manager Flushing + +This seems completely pointless, as packet dispatch uses .writeAndFlush. + +Things seem to work fine without explicit flushing, but incase issues arise, +provide a System property to re-enable it using improved logic of doing the +flushing on the netty event loop, so it won't do the flush on the main thread. + +Renable flushing by passing -Dpaper.explicit-flush=true + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 3429c813a5b471cdfa561bd20849a303e5aacead..7f4681910751047a26fdfc6b59bc460449c02001 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -73,6 +73,7 @@ public class Connection extends SimpleChannelInboundHandler> { + // Paper start - NetworkClient implementation + public int protocolVersion; + public java.net.InetSocketAddress virtualHost; ++ private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); + // Paper end + + public Connection(PacketFlow side) { +@@ -240,7 +241,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + if (this.channel != null) { +- this.channel.flush(); ++ if (enableExplicitFlush) this.channel.eventLoop().execute(() -> this.channel.flush()); // Paper - we don't need to explicit flush here, but allow opt in incase issues are found to a better version + } + + if (this.tickCount++ % 20 == 0) { diff --git a/Remapped-Spigot-Server-Patches/0196-Implement-extended-PaperServerListPingEvent.patch b/Remapped-Spigot-Server-Patches/0196-Implement-extended-PaperServerListPingEvent.patch new file mode 100644 index 000000000..762c35c3a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0196-Implement-extended-PaperServerListPingEvent.patch @@ -0,0 +1,378 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 15:56:26 +0200 +Subject: [PATCH] Implement extended PaperServerListPingEvent + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ecd0c5bbea55f68549c85aa27e80e2c7e6265d4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +@@ -0,0 +1,31 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.entity.Player; ++import org.bukkit.util.CachedServerIcon; ++ ++import javax.annotation.Nullable; ++ ++class PaperServerListPingEventImpl extends PaperServerListPingEvent { ++ ++ private final MinecraftServer server; ++ ++ PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) { ++ super(client, server.getMotd(), server.getPlayerCount(), server.getMaxPlayers(), ++ server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon); ++ this.server = server; ++ } ++ ++ @Override ++ protected final Object[] getOnlinePlayers() { ++ return this.server.getPlayerList().players.toArray(); ++ } ++ ++ @Override ++ protected final Player getBukkitPlayer(Object player) { ++ return ((ServerPlayer) player).getBukkitEntity(); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d926ad804355ee2fdc5910b2505e8671602acdab +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +@@ -0,0 +1,11 @@ ++package com.destroystokyo.paper.network; ++ ++import net.minecraft.network.Connection; ++ ++class PaperStatusClient extends PaperNetworkClient implements StatusClient { ++ ++ PaperStatusClient(Connection networkManager) { ++ super(networkManager); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4c2351b03b58511b80017b58ee9b20ab5193adc9 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +@@ -0,0 +1,110 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; ++import com.google.common.base.MoreObjects; ++import com.google.common.base.Strings; ++import com.mojang.authlib.GameProfile; ++import io.papermc.paper.adventure.AdventureComponent; ++import java.util.List; ++import java.util.UUID; ++import javax.annotation.Nonnull; ++import net.minecraft.network.Connection; ++import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket; ++import net.minecraft.network.protocol.status.ServerStatus; ++import net.minecraft.server.MinecraftServer; ++ ++public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { ++ ++ private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0]; ++ private static final UUID FAKE_UUID = new UUID(0, 0); ++ ++ private GameProfile[] originalSample; ++ ++ private StandardPaperServerListPingEventImpl(MinecraftServer server, Connection networkManager, ServerStatus ping) { ++ super(server, new PaperStatusClient(networkManager), ping.getVersion() != null ? ping.getVersion().getProtocol() : -1, server.server.getServerIcon()); ++ this.originalSample = ping.getPlayers() == null ? null : ping.getPlayers().getSample(); // GH-1473 - pre-tick race condition NPE ++ } ++ ++ @Nonnull ++ @Override ++ public List getPlayerSample() { ++ List sample = super.getPlayerSample(); ++ ++ if (this.originalSample != null) { ++ for (GameProfile profile : this.originalSample) { ++ sample.add(CraftPlayerProfile.asBukkitCopy(profile)); ++ } ++ this.originalSample = null; ++ } ++ ++ return sample; ++ } ++ ++ private GameProfile[] getPlayerSampleHandle() { ++ if (this.originalSample != null) { ++ return this.originalSample; ++ } ++ ++ List entries = super.getPlayerSample(); ++ if (entries.isEmpty()) { ++ return EMPTY_PROFILES; ++ } ++ ++ GameProfile[] profiles = new GameProfile[entries.size()]; ++ for (int i = 0; i < profiles.length; i++) { ++ /* ++ * Avoid null UUIDs/names since that will make the response invalid ++ * on the client. ++ * Instead, fall back to a fake/empty UUID and an empty string as name. ++ * This can be used to create custom lines in the player list that do not ++ * refer to a specific player. ++ */ ++ ++ PlayerProfile profile = entries.get(i); ++ if (profile.getId() != null && profile.getName() != null) { ++ profiles[i] = CraftPlayerProfile.asAuthlib(profile); ++ } else { ++ profiles[i] = new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName())); ++ } ++ } ++ ++ return profiles; ++ } ++ ++ @SuppressWarnings("deprecation") ++ public static void processRequest(MinecraftServer server, Connection networkManager) { ++ StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getStatus()); ++ server.server.getPluginManager().callEvent(event); ++ ++ // Close connection immediately if event is cancelled ++ if (event.isCancelled()) { ++ networkManager.disconnect(null); ++ return; ++ } ++ ++ // Setup response ++ ServerStatus ping = new ServerStatus(); ++ ++ // Description ++ ping.setDescription(new AdventureComponent(event.motd())); ++ ++ // Players ++ if (!event.shouldHidePlayers()) { ++ ping.setPlayers(new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers())); ++ ping.getPlayers().setSample(event.getPlayerSampleHandle()); ++ } ++ ++ // Version ++ ping.setVersion(new ServerStatus.Version(event.getVersion(), event.getProtocolVersion())); ++ ++ // Favicon ++ if (event.getServerIcon() != null) { ++ ping.setFavicon(event.getServerIcon().getData()); ++ } ++ ++ // Send response ++ networkManager.send(new ClientboundStatusResponsePacket(ping)); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/protocol/status/ClientboundStatusResponsePacket.java b/src/main/java/net/minecraft/network/protocol/status/ClientboundStatusResponsePacket.java +index b985d238eadf857602636ba5e5c277d4b1992d35..5b2081f920304244df96de78b2c66cf8a49a5b85 100644 +--- a/src/main/java/net/minecraft/network/protocol/status/ClientboundStatusResponsePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/status/ClientboundStatusResponsePacket.java +@@ -2,6 +2,7 @@ package net.minecraft.network.protocol.status; + + import com.google.gson.Gson; + import com.google.gson.GsonBuilder; ++import io.papermc.paper.adventure.AdventureComponent; // Paper + import java.io.IOException; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.chat.Component; +@@ -12,7 +13,9 @@ import net.minecraft.util.LowerCaseEnumTypeAdapterFactory; + + public class ClientboundStatusResponsePacket implements Packet { + +- private static final Gson GSON = (new GsonBuilder()).registerTypeAdapter(ServerStatus.Version.class, new ServerStatus.Version.Serializer()).registerTypeAdapter(ServerStatus.Players.class, new ServerStatus.Players.Serializer()).registerTypeAdapter(ServerStatus.class, new ServerStatus.Serializer()).registerTypeHierarchyAdapter(Component.class, new Component.Serializer()).registerTypeHierarchyAdapter(Style.class, new Style.Serializer()).registerTypeAdapterFactory(new LowerCaseEnumTypeAdapterFactory()).create(); ++ private static final Gson GSON = (new GsonBuilder()).registerTypeAdapter(ServerStatus.Version.class, new ServerStatus.Version.Serializer()).registerTypeAdapter(ServerStatus.Players.class, new ServerStatus.Players.Serializer()).registerTypeAdapter(ServerStatus.class, new ServerStatus.Serializer()).registerTypeHierarchyAdapter(Component.class, new Component.Serializer()).registerTypeHierarchyAdapter(Style.class, new Style.Serializer()).registerTypeAdapterFactory(new LowerCaseEnumTypeAdapterFactory()) ++ .registerTypeAdapter(AdventureComponent.class, new AdventureComponent.Serializer()) ++ .create(); + private ServerStatus status; + + public ClientboundStatusResponsePacket() {} +diff --git a/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java b/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java +index d6be3dd6cfed3f65325398fc33663cb251f87ac7..31d45cd635eae2ff406cb0441f2cd2aee833b945 100644 +--- a/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java ++++ b/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java +@@ -31,6 +31,7 @@ public class ServerStatus { + this.description = description; + } + ++ public Players getPlayers() { return getPlayers(); } // Paper - OBFHELPER + public ServerStatus.Players getPlayers() { + return this.players; + } +@@ -162,10 +163,12 @@ public class ServerStatus { + return this.numPlayers; + } + ++ public GameProfile[] getSample() { return getSample(); } // Paper - OBFHELPER + public GameProfile[] getSample() { + return this.sample; + } + ++ public void setSample(GameProfile[] sample) { setSample(sample); } // Paper - OBFHELPER + public void setSample(GameProfile[] sample) { + this.sample = sample; + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4b3341877629c7065496fb0f0b4d509f5a48db6d..d34da1eb172a7dcda564680afecf3dc145bf09f3 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2,6 +2,9 @@ package net.minecraft.server; + + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; ++import co.aikar.timings.Timings; ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import com.google.common.base.Stopwatch; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -1238,7 +1241,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= 5000000000L) { + this.lastServerStatus = i; + this.status.setPlayers(new ServerStatus.Players(this.getMaxPlayers(), this.getPlayerCount())); +- GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), 12)]; ++ GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), org.spigotmc.SpigotConfig.playerSample)]; // Paper + int j = Mth.nextInt(this.random, 0, this.getPlayerCount() - agameprofile.length); + + for (int k = 0; k < agameprofile.length; ++k) { +diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +index 223df8d27c2ff1cbff634bca3dbde5cc24de7f98..f74e3cbdff8c2d83809f04f42717501d7b1a1ed2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -1,7 +1,7 @@ + package net.minecraft.server.network; + + import net.minecraft.server.MinecraftServer; +-import net.minecraft.server.level.ServerPlayer; ++ + // CraftBukkit start + import com.mojang.authlib.GameProfile; + import java.net.InetSocketAddress; +@@ -11,8 +11,6 @@ import net.minecraft.network.Connection; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.status.ClientboundPongResponsePacket; +-import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket; +-import net.minecraft.network.protocol.status.ServerStatus; + import net.minecraft.network.protocol.status.ServerStatusPacketListener; + import net.minecraft.network.protocol.status.ServerboundPingRequestPacket; + import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; +@@ -47,15 +45,17 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON); + } else { + this.hasRequestedStatus = true; ++ // Paper start - Replace everything ++ /* + // CraftBukkit start + // this.networkManager.sendPacket(new PacketStatusOutServerInfo(this.minecraftServer.getServerPing())); +- final Object[] players = server.getPlayerList().players.toArray(); ++ final Object[] players = minecraftServer.getPlayerList().players.toArray(); + class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent { + +- CraftIconCache icon = server.server.getServerIcon(); ++ CraftIconCache icon = minecraftServer.server.getServerIcon(); + + ServerListPingEvent() { +- super(((InetSocketAddress) connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure ++ super(((InetSocketAddress) networkManager.getSocketAddress()).getAddress(), minecraftServer.server.motd(), minecraftServer.getPlayerList().getMaxPlayers()); // Paper - Adventure + } + + @Override +@@ -71,7 +71,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + return new Iterator() { + int i; + int ret = Integer.MIN_VALUE; +- ServerPlayer player; ++ EntityPlayer player; + + @Override + public boolean hasNext() { +@@ -80,7 +80,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + } + final Object[] currentPlayers = players; + for (int length = currentPlayers.length, i = this.i; i < length; i++) { +- final ServerPlayer player = (ServerPlayer) currentPlayers[i]; ++ final EntityPlayer player = (EntityPlayer) currentPlayers[i]; + if (player != null) { + this.i = i + 1; + this.player = player; +@@ -95,7 +95,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + if (!hasNext()) { + throw new java.util.NoSuchElementException(); + } +- final ServerPlayer player = this.player; ++ final EntityPlayer player = this.player; + this.player = null; + this.ret = this.i - 1; + return player.getBukkitEntity(); +@@ -115,16 +115,16 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + } + + ServerListPingEvent event = new ServerListPingEvent(); +- this.server.server.getPluginManager().callEvent(event); ++ this.minecraftServer.server.getPluginManager().callEvent(event); + + java.util.List profiles = new java.util.ArrayList(players.length); + for (Object player : players) { + if (player != null) { +- profiles.add(((ServerPlayer) player).getGameProfile()); ++ profiles.add(((EntityPlayer) player).getProfile()); + } + } + +- ServerStatus.Players playerSample = new ServerStatus.Players(event.getMaxPlayers(), profiles.size()); ++ ServerPing.ServerPingPlayerSample playerSample = new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), profiles.size()); + // Spigot Start + if ( !profiles.isEmpty() ) + { +@@ -132,16 +132,19 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour + } + // Spigot End +- playerSample.setSample(profiles.toArray(new GameProfile[profiles.size()])); ++ playerSample.a(profiles.toArray(new GameProfile[profiles.size()])); + +- ServerStatus ping = new ServerStatus(); ++ ServerPing ping = new ServerPing(); + ping.setFavicon(event.icon.value); +- ping.setDescription(CraftChatMessage.fromString(event.getMotd(), true)[0]); +- ping.setPlayers(playerSample); +- int version = SharedConstants.getCurrentVersion().getProtocolVersion(); +- ping.setVersion(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), version)); +- +- this.connection.send(new ClientboundStatusResponsePacket(ping)); ++ ping.setMOTD(CraftChatMessage.fromString(event.getMotd(), true)[0]); ++ ping.setPlayerSample(playerSample); ++ int version = SharedConstants.getGameVersion().getProtocolVersion(); ++ ping.setServerInfo(new ServerPing.ServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), version)); ++ ++ this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); ++ */ ++ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.server, this.connection); ++ // Paper end + } + // CraftBukkit end + } +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 3981ba5957fdc2929d54515f2b98bb7a4611e938..652b820a4c0bbf7b6bbb8200927a663665583606 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -289,7 +289,7 @@ public class SpigotConfig + public static int playerSample; + private static void playerSample() + { +- playerSample = getInt( "settings.sample-count", 12 ); ++ playerSample = Math.max( getInt( "settings.sample-count", 12 ), 0 ); // Paper - Avoid negative counts + Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger + } + diff --git a/Remapped-Spigot-Server-Patches/0197-Improved-Async-Task-Scheduler.patch b/Remapped-Spigot-Server-Patches/0197-Improved-Async-Task-Scheduler.patch new file mode 100644 index 000000000..b0ae82322 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0197-Improved-Async-Task-Scheduler.patch @@ -0,0 +1,370 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 16 Mar 2018 22:59:43 -0400 +Subject: [PATCH] Improved Async Task Scheduler + +The Craft Scheduler still uses the primary thread for task scheduling. +This results in the main thread still having to do work as part of the +dispatching of async tasks. + +If plugins make use of lots of async tasks, such as particle emitters +that want to keep the logic off the main thread, the main thread still +receives quite a bit of load from processing all of these queued tasks. + +Additionally, resizing and managing the pending entries for all of +these asynchronous tasks takes up time on the main thread too. + +This commit replaces the implementation of the scheduler when working +with asynchronous tasks, by forwarding calls to the new scheduler. + +The Async Scheduler uses a single thread executor for "management" tasks. +The Management Thread is responsible for all adding and dispatching of +scheduled tasks. + +The mainThreadHeartbeat will send a heartbeat task to the management thread +with the currentTick value, so that it can find which tasks to execute. + +Scheduling of an async tasks also dispatches a management task, ensuring +that any Queue resizing operation occurs off of the main thread. + +The async queue uses a complete separate PriorityQueue, ensuring that resize +operations are decoupled from the sync tasks queue. + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3c1992e212a6d6f1db4d5b807b38d71913619fc0 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -0,0 +1,122 @@ ++/* ++ * 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.destroystokyo.paper.ServerSchedulerReportingWrapper; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import org.bukkit.plugin.Plugin; ++ ++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(); ++ } ++ } ++ } ++ ++ /** ++ * Task is not cancelled ++ * @param runningTask ++ * @return ++ */ ++ static boolean isValid(CraftTask runningTask) { ++ return runningTask.getPeriod() >= CraftTask.NO_REPEATING; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 0e0f361c3af363539d5d1d865603114bdb84fd67..ca90237a53c9a026919d28adaedf483ca3c7c2a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -63,7 +63,7 @@ public class CraftScheduler implements BukkitScheduler { + /** + * Main thread logic only + */ +- private final PriorityQueue pending = new PriorityQueue(10, ++ final PriorityQueue pending = new PriorityQueue(10, // Paper + new Comparator() { + @Override + public int compare(final CraftTask o1, final CraftTask o2) { +@@ -80,12 +80,13 @@ public class CraftScheduler implements BukkitScheduler { + /** + * These are tasks that are currently active. It's provided for 'viewing' the current state. + */ +- private final ConcurrentHashMap runners = new ConcurrentHashMap(); ++ final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper + /** + * The sync task that is currently running on the main thread. + */ + private volatile CraftTask currentTask = null; +- private volatile int currentTick = -1; ++ // Paper start - Improved Async Task Scheduler ++ volatile int currentTick = -1;/* + private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); + private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) { + @Override +@@ -94,12 +95,31 @@ public class CraftScheduler implements BukkitScheduler { + } + }; + private CraftAsyncDebugger debugTail = debugHead; ++ ++ */ // Paper end + private static final int RECENT_TICKS; + + static { + 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 + @Override + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { + return this.scheduleSyncDelayedTask(plugin, task, 0L); +@@ -222,7 +242,7 @@ public class CraftScheduler implements BukkitScheduler { + } else if (period < CraftTask.NO_REPEATING) { + period = CraftTask.NO_REPEATING; + } +- return handle(new CraftAsyncTask(runners, plugin, runnable, nextId(), period), delay); ++ return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper + } + + @Override +@@ -238,6 +258,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(); +@@ -280,6 +305,11 @@ public class CraftScheduler implements BukkitScheduler { + @Override + 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() { + @Override +@@ -319,6 +349,13 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + 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) { + return false; +@@ -337,6 +374,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() >= CraftTask.NO_REPEATING; // The task will run +@@ -348,6 +390,12 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public List getActiveWorkers() { ++ // 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 +@@ -385,6 +433,11 @@ public class CraftScheduler implements BukkitScheduler { + pending.add(task); + } + } ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ pending.addAll(this.asyncScheduler.getPendingTasks()); ++ } ++ // Paper end + return pending; + } + +@@ -392,6 +445,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(); +@@ -431,7 +489,7 @@ public class CraftScheduler implements BukkitScheduler { + parsePending(); + } else { + //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper +- executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper ++ 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) + } +@@ -450,7 +508,7 @@ public class CraftScheduler implements BukkitScheduler { + //debugHead = debugHead.getNextHead(currentTick); // Paper + } + +- 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)) { +@@ -459,7 +517,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; +@@ -478,8 +542,8 @@ public class CraftScheduler implements BukkitScheduler { + return ids.incrementAndGet(); + } + +- private void parsePending() { +- MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); ++ void parsePending() { // Paper ++ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; +@@ -498,7 +562,7 @@ public class CraftScheduler implements BukkitScheduler { + task.setNext(null); + } + this.head = lastTask; +- MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); ++ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper + } + + private boolean isReady(final int currentTick) { diff --git a/Remapped-Spigot-Server-Patches/0198-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch b/Remapped-Spigot-Server-Patches/0198-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch new file mode 100644 index 000000000..f40063713 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0198-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 18 Mar 2018 11:45:57 -0400 +Subject: [PATCH] Ability to change PlayerProfile in AsyncPreLoginEvent + +This will allow you to change the players name or skin on login. + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index ff83fb15d0d0adb62c630fc7aafc134972bf15fc..e5be45ac86907c1f8cc154bd38fd624a2320180f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.mojang.authlib.GameProfile; + import com.mojang.authlib.exceptions.AuthenticationUnavailableException; + import java.math.BigInteger; +@@ -36,6 +38,7 @@ import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import io.papermc.paper.adventure.PaperAdventure; // Paper ++import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; +@@ -314,8 +317,16 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + java.util.UUID uniqueId = gameProfile.getId(); + final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; + +- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId); ++ // Paper start ++ PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); ++ profile = asyncEvent.getPlayerProfile(); ++ profile.complete(); ++ gameProfile = CraftPlayerProfile.asAuthlibCopy(profile); ++ playerName = gameProfile.getName(); ++ uniqueId = gameProfile.getId(); ++ // Paper end + + if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { + final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); diff --git a/Remapped-Spigot-Server-Patches/0199-Player.setPlayerProfile-API.patch b/Remapped-Spigot-Server-Patches/0199-Player.setPlayerProfile-API.patch new file mode 100644 index 000000000..f35f170d5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0199-Player.setPlayerProfile-API.patch @@ -0,0 +1,142 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 18 Mar 2018 12:29:48 -0400 +Subject: [PATCH] Player.setPlayerProfile API + +This can be useful for changing name or skins after a player has logged in. + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index e5be45ac86907c1f8cc154bd38fd624a2320180f..0aa3a154d68f00edcc09b947a24b2b59b1e135e6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -54,7 +54,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + public final Connection connection; + private ServerLoginPacketListenerImpl.State state; + private int tick; +- private GameProfile gameProfile; ++ private GameProfile gameProfile; private void setGameProfile(final GameProfile profile) { this.gameProfile = profile; } private GameProfile getGameProfile() { return this.gameProfile; } // Paper - OBFHELPER + private final String serverId; + private SecretKey secretKey; + private ServerPlayer delayedAcceptPlayer; +@@ -318,12 +318,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; + + // Paper start +- PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ PlayerProfile profile = CraftPlayerProfile.asBukkitMirror(getGameProfile()); + AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); +- profile.complete(); +- gameProfile = CraftPlayerProfile.asAuthlibCopy(profile); ++ profile.complete(true); ++ setGameProfile(CraftPlayerProfile.asAuthlib(profile)); + playerName = gameProfile.getName(); + uniqueId = gameProfile.getId(); + // Paper end +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 63871a3a1981b2e8c7ad74214196e35684acb584..c4aa824d03de952fe6b306e539baa47af979add1 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -159,7 +159,7 @@ public abstract class Player extends LivingEntity { + protected int enchantmentSeed; + protected final float defaultFlySpeed = 0.02F; + private int lastLevelUpTime; +- private final GameProfile gameProfile; ++ private GameProfile gameProfile; public final void setProfile(final GameProfile profile) { this.gameProfile = profile; } // Paper - OBFHELPER + private ItemStack lastItemInMainHand; + private final ItemCooldowns cooldowns; + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index efdcb8dac8db15c4bbaed84a7861ce98339e516a..9d853733ff9054cc48925e22c8bb3c8d9b898808 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -67,6 +67,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.item.enchantment.Enchantments; + import net.minecraft.world.level.GameType; ++import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.saveddata.maps.MapDecoration; + import net.minecraft.world.phys.Vec3; +@@ -1307,8 +1308,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + hiddenPlayers.put(player.getUniqueId(), hidingPlugins); + + // Remove this player from the hidden player's EntityTrackerEntry +- ChunkMap tracker = ((ServerLevel) entity.level).getChunkSource().chunkMap; ++ // Paper start + ServerPlayer other = ((CraftPlayer) player).getHandle(); ++ unregisterPlayer(other); ++ } ++ private void unregisterPlayer(ServerPlayer other) { ++ ChunkMap tracker = ((ServerLevel) entity.level).getChunkSource().chunkMap; ++ // Paper end + ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); + if (entry != null) { + entry.removePlayer(getHandle()); +@@ -1349,8 +1355,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + hiddenPlayers.remove(player.getUniqueId()); + +- ChunkMap tracker = ((ServerLevel) entity.level).getChunkSource().chunkMap; ++ // Paper start + ServerPlayer other = ((CraftPlayer) player).getHandle(); ++ registerPlayer(other); ++ } ++ private void registerPlayer(ServerPlayer other) { ++ ChunkMap tracker = ((ServerLevel) entity.level).getChunkSource().chunkMap; ++ // Paper end + + getHandle().connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, other)); + +@@ -1359,6 +1370,50 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + entry.updatePlayer(getHandle()); + } + } ++ // Paper start ++ private void reregisterPlayer(ServerPlayer player) { ++ if (!hiddenPlayers.containsKey(player.getUUID())) { ++ unregisterPlayer(player); ++ registerPlayer(player); ++ } ++ } ++ public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) { ++ ServerPlayer self = getHandle(); ++ self.setProfile(com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile)); ++ if (!self.sentListPacket) { ++ return; ++ } ++ List players = server.getServer().getPlayerList().players; ++ for (ServerPlayer player : players) { ++ player.getBukkitEntity().reregisterPlayer(self); ++ } ++ refreshPlayer(); ++ } ++ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); ++ } ++ ++ private void refreshPlayer() { ++ ServerPlayer handle = getHandle(); ++ ++ Location loc = getLocation(); ++ ++ ServerGamePacketListenerImpl connection = handle.connection; ++ reregisterPlayer(handle); ++ ++ //Respawn the player then update their position and selected slot ++ ServerLevel worldserver = handle.getLevel(); ++ connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(worldserver.dimensionType(), worldserver.dimension(), BiomeManager.obfuscateSeed(worldserver.getSeed()), handle.gameMode.getGameModeForPlayer(), handle.gameMode.getPreviousGameModeForPlayer(), worldserver.isDebug(), worldserver.isFlat(), true)); ++ handle.onUpdateAbilities(); ++ connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), new HashSet<>(), 0)); ++ net.minecraft.server.MinecraftServer.getServer().getPlayerList().sendAllPlayerInfo(handle); ++ ++ if (this.isOp()) { ++ this.setOp(false); ++ this.setOp(true); ++ } ++ } ++ // Paper end + + public void removeDisconnectingPlayer(Player player) { + hiddenPlayers.remove(player.getUniqueId()); diff --git a/Remapped-Spigot-Server-Patches/0200-Fix-Dragon-Server-Crashes.patch b/Remapped-Spigot-Server-Patches/0200-Fix-Dragon-Server-Crashes.patch new file mode 100644 index 000000000..334aa02f7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0200-Fix-Dragon-Server-Crashes.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Mar 2018 20:52:07 -0400 +Subject: [PATCH] Fix Dragon Server Crashes + +If the dragon tries to find "ground" and hits a hole, or off edge, +it will infinitely keep looking for non air and eventually crash. + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +index 5e701b02e464889fe433b08018d13e63b24506eb..0c2a5f5c4d7d7516793eba20205b5703fe1450d5 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +@@ -63,7 +63,7 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { + double d3 = d2; + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(d0, d2, d1); + +- while (this.dragon.level.isEmptyBlock(blockposition_mutableblockposition)) { ++ while (this.dragon.level.isEmptyBlock(blockposition_mutableblockposition ) && d2 > 0) { // Paper + --d3; + if (d3 < 0.0D) { + d3 = d2; diff --git a/Remapped-Spigot-Server-Patches/0201-getPlayerUniqueId-API.patch b/Remapped-Spigot-Server-Patches/0201-getPlayerUniqueId-API.patch new file mode 100644 index 000000000..64d63b916 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0201-getPlayerUniqueId-API.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 22 Mar 2018 01:40:24 -0400 +Subject: [PATCH] getPlayerUniqueId API + +Gets the unique ID of the player currently known as the specified player name +In Offline Mode, will return an Offline UUID + +This is a more performant way to obtain a UUID for a name than loading an OfflinePlayer + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 10addb128a357e7719854bf4f9d75f5def32b27d..20915e40fbcf28faed603d449a99bf2157fcf972 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1517,6 +1517,25 @@ public final class CraftServer implements Server { + return recipients.size(); + } + ++ // Paper start ++ @Nullable ++ public UUID getPlayerUniqueId(String name) { ++ Player player = Bukkit.getPlayerExact(name); ++ if (player != null) { ++ return player.getUniqueId(); ++ } ++ GameProfile profile; ++ // Only fetch an online UUID in online mode ++ if (com.destroystokyo.paper.PaperConfig.isProxyOnlineMode()) { ++ profile = console.getProfileCache().get( name ); ++ } else { ++ // Make an OfflinePlayer using an offline mode UUID since the name has no profile ++ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); ++ } ++ return profile != null ? profile.getId() : null; ++ } ++ // Paper end ++ + @Override + @Deprecated + public OfflinePlayer getOfflinePlayer(String name) { diff --git a/Remapped-Spigot-Server-Patches/0202-Make-player-data-saving-configurable.patch b/Remapped-Spigot-Server-Patches/0202-Make-player-data-saving-configurable.patch new file mode 100644 index 000000000..7a7b4d199 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0202-Make-player-data-saving-configurable.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Mon, 26 Mar 2018 18:30:53 +0300 +Subject: [PATCH] Make player data saving configurable + +Upstream has added a patch which negates the need for this patch, +however, we should still migrate our configuration back upstream, +to prevent unexpected situations + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 52954fc3bf932cfc9d5ce63e3d3cace351305790..05a5abb951abe37f30a719cb75376d2d43c0d252 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -279,4 +279,13 @@ public class PaperConfig { + private static void authenticationServersDownKickMessage() { + authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); + } ++ ++ private static void savePlayerData() { ++ Object val = config.get("settings.save-player-data"); ++ if (val instanceof Boolean) { ++ SpigotConfig.disablePlayerDataSaving = !(Boolean) val; ++ SpigotConfig.config.set("players.disable-saving", SpigotConfig.disableAdvancementSaving); ++ SpigotConfig.save(); ++ } ++ } + } diff --git a/Remapped-Spigot-Server-Patches/0203-Make-legacy-ping-handler-more-reliable.patch b/Remapped-Spigot-Server-Patches/0203-Make-legacy-ping-handler-more-reliable.patch new file mode 100644 index 000000000..72dfa9fd8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0203-Make-legacy-ping-handler-more-reliable.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 18:22:50 +0200 +Subject: [PATCH] Make legacy ping handler more reliable + +The Minecraft server often fails to respond to old ("legacy") pings +from old Minecraft versions using the protocol used before the switch +to Netty in Minecraft 1.7. + +Due to packet fragmentation[1], we might not have all needed bytes +available when the LegacyPingHandler is called. In this case, it will +run into an error, remove the handler and continue using the modern +protocol. + +This is unlikely to happen for the first two revisions of the legacy +ping protocol (used in Minecraft 1.5.x and older) since the request +consists of only one or two bytes, but happens frequently for the +last/third revision introduced in Minecraft 1.6. + +It has much larger, variable packet sizes due to the inclusion of +the virtual host (the hostname/port used to connect to the server). + +The solution[2] is simple: If we find more than two matching bytes, +we buffer the remaining bytes until we have enough to fully read and +respond to the request. + +[1]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11 +[2]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h4-13 + +diff --git a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +index e2867a3de87a778a897b4963212fa4aab566a643..1d11802876c1a94ecf991cd8249bd6a911c0ac20 100644 +--- a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java ++++ b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +@@ -15,6 +15,7 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LogManager.getLogger(); + private final ServerConnectionListener serverConnectionListener; ++ private ByteBuf buf; // Paper + + public LegacyQueryHandler(ServerConnectionListener networkIo) { + this.serverConnectionListener = networkIo; +@@ -23,6 +24,16 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) throws Exception { + ByteBuf bytebuf = (ByteBuf) object; + ++ // Paper start - Make legacy ping handler more reliable ++ if (this.buf != null) { ++ try { ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ } finally { ++ bytebuf.release(); ++ } ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); + boolean flag = true; + +@@ -53,6 +64,10 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + this.sendFlushAndClose(channelhandlercontext, this.createReply(s)); + break; + default: ++ // Paper start - Replace with improved version below ++ if (bytebuf.readUnsignedByte() != 0x01 || bytebuf.readUnsignedByte() != 0xFA) return; ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ /* + boolean flag1 = bytebuf.readUnsignedByte() == 1; + + flag1 &= bytebuf.readUnsignedByte() == 250; +@@ -67,15 +82,16 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + return; + } + +- LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); +- String s1 = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, minecraftserver.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit +- ByteBuf bytebuf1 = this.createReply(s1); ++ LegacyPingHandler.LOGGER.debug("Ping: (1.6) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); ++ String s1 = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, minecraftserver.getVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ ByteBuf bytebuf1 = this.a(s1); + + try { +- this.sendFlushAndClose(channelhandlercontext, bytebuf1); ++ this.a(channelhandlercontext, bytebuf1); + } finally { + bytebuf1.release(); + } ++ */ // Paper end - Replace with improved version below + } + + bytebuf.release(); +@@ -93,6 +109,90 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + + } + ++ // Paper start ++ private static String readLegacyString(ByteBuf buf) { ++ int size = buf.readShort() * Character.BYTES; ++ if (!buf.isReadable(size)) { ++ return null; ++ } ++ ++ String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE); ++ buf.skipBytes(size); // toString doesn't increase readerIndex automatically ++ return result; ++ } ++ ++ private void readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) { ++ ByteBuf buf = this.buf; ++ ++ if (buf == null) { ++ this.buf = buf = ctx.alloc().buffer(); ++ buf.markReaderIndex(); ++ } else { ++ buf.resetReaderIndex(); ++ } ++ ++ buf.writeBytes(part); ++ ++ if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) { ++ return; ++ } ++ ++ String s = readLegacyString(buf); ++ if (s == null) { ++ return; ++ } ++ ++ if (!s.equals("MC|PingHost")) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) { ++ return; ++ } ++ ++ MinecraftServer server = this.serverConnectionListener.getServer(); ++ int protocolVersion = buf.readByte(); ++ String host = readLegacyString(buf); ++ if (host == null) { ++ removeHandler(ctx); ++ return; ++ } ++ int port = buf.readInt(); ++ ++ if (buf.isReadable()) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ buf.release(); ++ this.buf = null; ++ ++ LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", ++ Byte.MAX_VALUE, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ this.sendFlushAndClose(ctx, this.createReply(response)); ++ } ++ ++ private void removeHandler(ChannelHandlerContext ctx) { ++ ByteBuf buf = this.buf; ++ this.buf = null; ++ ++ buf.resetReaderIndex(); ++ ctx.pipeline().remove(this); ++ ctx.fireChannelRead(buf); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) { ++ if (this.buf != null) { ++ this.buf.release(); ++ this.buf = null; ++ } ++ } ++ // Paper end ++ + private void sendFlushAndClose(ChannelHandlerContext ctx, ByteBuf buf) { + ctx.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + } diff --git a/Remapped-Spigot-Server-Patches/0204-Call-PaperServerListPingEvent-for-legacy-pings.patch b/Remapped-Spigot-Server-Patches/0204-Call-PaperServerListPingEvent-for-legacy-pings.patch new file mode 100644 index 000000000..90d915afd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0204-Call-PaperServerListPingEvent-for-legacy-pings.patch @@ -0,0 +1,154 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:51 +0200 +Subject: [PATCH] Call PaperServerListPingEvent for legacy pings + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..74c012fd40491f1d870fbc1aa8c318a2197eb106 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang3.StringUtils; ++import org.bukkit.ChatColor; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++ ++public final class PaperLegacyStatusClient implements StatusClient { ++ ++ private final InetSocketAddress address; ++ private final int protocolVersion; ++ @Nullable private final InetSocketAddress virtualHost; ++ ++ private PaperLegacyStatusClient(InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ this.address = address; ++ this.protocolVersion = protocolVersion; ++ this.virtualHost = virtualHost; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return this.address; ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.virtualHost; ++ } ++ ++ @Override ++ public boolean isLegacy() { ++ return true; ++ } ++ ++ public static PaperServerListPingEvent processRequest(MinecraftServer server, ++ InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ ++ PaperServerListPingEvent event = new PaperServerListPingEventImpl(server, ++ new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null); ++ server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ return event; ++ } ++ ++ public static String getMotd(PaperServerListPingEvent event) { ++ return getFirstLine(event.getMotd()); ++ } ++ ++ public static String getUnformattedMotd(PaperServerListPingEvent event) { ++ // Strip color codes and all other occurrences of the color char (because it's used as delimiter) ++ return getFirstLine(StringUtils.remove(ChatColor.stripColor(event.getMotd()), ChatColor.COLOR_CHAR)); ++ } ++ ++ private static String getFirstLine(String s) { ++ int pos = s.indexOf('\n'); ++ return pos >= 0 ? s.substring(0, pos) : s; ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +index 1d11802876c1a94ecf991cd8249bd6a911c0ac20..dfe2cd46f2432dca2028b7436c4108e3f190787f 100644 +--- a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java ++++ b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.network.PaperLegacyStatusClient; ++ + import io.netty.buffer.ByteBuf; + import io.netty.buffer.Unpooled; + import io.netty.channel.ChannelFutureListener; +@@ -46,12 +48,19 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + MinecraftServer minecraftserver = this.serverConnectionListener.getServer(); + int i = bytebuf.readableBytes(); + String s; +- org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(minecraftserver.server, inetsocketaddress.getAddress(), minecraftserver.getMotd(), minecraftserver.getPlayerCount(), minecraftserver.getMaxPlayers()); // CraftBukkit ++ //org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(minecraftserver.server, inetsocketaddress.getAddress(), minecraftserver.getMotd(), minecraftserver.getPlayerCount(), minecraftserver.getMaxPlayers()); // CraftBukkit // Paper ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper + + switch (i) { + case 0: + LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); +- s = String.format("%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 39, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("%s\u00a7%d\u00a7%d", PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.sendFlushAndClose(channelhandlercontext, this.createReply(s)); + break; + case 1: +@@ -60,7 +69,14 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + } + + LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); +- s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, minecraftserver.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 127, null); // Paper ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), minecraftserver.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit ++ // Paper end + this.sendFlushAndClose(channelhandlercontext, this.createReply(s)); + break; + default: +@@ -170,8 +186,16 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + + LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); + +- String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", +- Byte.MAX_VALUE, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = PaperLegacyStatusClient.processRequest( ++ server, (InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ if (event == null) { ++ ctx.close(); ++ return; ++ } ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.sendFlushAndClose(ctx, this.createReply(response)); + } + diff --git a/Remapped-Spigot-Server-Patches/0205-Flag-to-disable-the-channel-limit.patch b/Remapped-Spigot-Server-Patches/0205-Flag-to-disable-the-channel-limit.patch new file mode 100644 index 000000000..467dda42a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0205-Flag-to-disable-the-channel-limit.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 31 Mar 2018 17:04:26 +0100 +Subject: [PATCH] Flag to disable the channel limit + +In some enviroments, the channel limit set by spigot can cause issues, +e.g. servers which allow and support the usage of mod packs. + +provide an optional flag to disable this check, at your own risk. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 9d853733ff9054cc48925e22c8bb3c8d9b898808..46338fe5693003698de9c7b37a860c3481e06233 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -143,6 +143,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper start + private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; + private String resourcePackHash; ++ private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit + // Paper end + + public CraftPlayer(CraftServer server, ServerPlayer entity) { +@@ -1576,7 +1577,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void addChannel(String channel) { +- Preconditions.checkState(channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); ++ Preconditions.checkState(DISABLE_CHANNEL_LIMIT || channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); // Paper - flag to disable channel limit + channel = StandardMessenger.validateAndCorrectChannel(channel); + if (channels.add(channel)) { + server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); diff --git a/Remapped-Spigot-Server-Patches/0206-Add-method-to-open-already-placed-sign.patch b/Remapped-Spigot-Server-Patches/0206-Add-method-to-open-already-placed-sign.patch new file mode 100644 index 000000000..9a923369d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0206-Add-method-to-open-already-placed-sign.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 1 Apr 2018 02:29:37 +0300 +Subject: [PATCH] Add method to open already placed sign + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index c62d01719f21762aa10294815ab88e450e4dce3f..4aec1c2b26d48cb5bea3dfb9e183526763bdb98f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -28,6 +28,7 @@ import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.CraftingTableBlock; + import net.minecraft.world.level.block.EnchantmentTableBlock; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import org.bukkit.GameMode; + import org.bukkit.Location; +@@ -604,6 +605,17 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start - Add method to open already placed sign ++ @Override ++ public void openSign(org.bukkit.block.Sign sign) { ++ org.apache.commons.lang.Validate.isTrue(sign.getWorld().equals(this.getWorld()), "Sign must be in the same world as player is in"); ++ org.bukkit.craftbukkit.block.CraftSign craftSign = (org.bukkit.craftbukkit.block.CraftSign) sign; ++ SignBlockEntity teSign = craftSign.getTileEntity(); ++ // Make sign editable temporarily, will be set back to false in PlayerConnection later ++ teSign.isEditable = true; ++ getHandle().openTextEdit(teSign); ++ } ++ // Paper end + @Override + public boolean dropItem(boolean dropAll) { + return getHandle().drop(dropAll); diff --git a/Remapped-Spigot-Server-Patches/0207-Configurable-sprint-interruption-on-attack.patch b/Remapped-Spigot-Server-Patches/0207-Configurable-sprint-interruption-on-attack.patch new file mode 100644 index 000000000..dfe307fc2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0207-Configurable-sprint-interruption-on-attack.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Sat, 14 Apr 2018 20:20:46 +0200 +Subject: [PATCH] Configurable sprint interruption on attack + +If the sprint interruption is disabled players continue sprinting when they attack entities. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 48f0385c7203c7955de5a015f3dc42be2ab7b681..cebf1a623a9bec72d60fdd23dda01868ef6431d4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -358,4 +358,9 @@ public class PaperWorldConfig { + private void squidMaxSpawnHeight() { + squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D); + } ++ ++ public boolean disableSprintInterruptionOnAttack; ++ private void disableSprintInterruptionOnAttack() { ++ disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index c4aa824d03de952fe6b306e539baa47af979add1..552920f59aae9de2cad3edcdc8c48a91dff49093 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1180,7 +1180,11 @@ public abstract class Player extends LivingEntity { + } + + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); +- this.setSprinting(false); ++ // Paper start - Configuration option to disable automatic sprint interruption ++ if (!level.paperConfig.disableSprintInterruptionOnAttack) { ++ this.setSprinting(false); ++ } ++ // Paper end + } + + if (flag3) { diff --git a/Remapped-Spigot-Server-Patches/0208-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/Remapped-Spigot-Server-Patches/0208-Fix-exploit-that-allowed-colored-signs-to-be-created.patch new file mode 100644 index 000000000..68cdac63b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0208-Fix-exploit-that-allowed-colored-signs-to-be-created.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 0x22 <0x22@futureclient.net> +Date: Thu, 26 Apr 2018 04:41:11 -0400 +Subject: [PATCH] Fix exploit that allowed colored signs to be created + + +diff --git a/src/main/java/net/minecraft/SharedConstants.java b/src/main/java/net/minecraft/SharedConstants.java +index a70c3d25930e7414fc9e897de8d2e0c12f11c0e4..04b8783417bbcd826d6d1c302551fbad9c48bd01 100644 +--- a/src/main/java/net/minecraft/SharedConstants.java ++++ b/src/main/java/net/minecraft/SharedConstants.java +@@ -20,6 +20,7 @@ public class SharedConstants { + return chr != 167 && chr >= ' ' && chr != 127; + } + ++ public static String filterAllowedChatCharacters(String input) { return filterText(input); } // Paper - OBFHELPER + public static String filterText(String s) { + StringBuilder stringbuilder = new StringBuilder(); + char[] achar = s.toCharArray(); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8d0c44b6c2c99d5161c5d4b79209b79ff6db75e4..fb36aa08cd2fbe3f7262dccb8cf0f7cae55aea9c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2786,7 +2786,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines.add(net.kyori.adventure.text.Component.text(list.get(i))); ++ lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(list.get(i)))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. + } + SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.craftServer.getPluginManager().callEvent(event); diff --git a/Remapped-Spigot-Server-Patches/0209-EndermanEscapeEvent.patch b/Remapped-Spigot-Server-Patches/0209-EndermanEscapeEvent.patch new file mode 100644 index 000000000..038bdea2d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0209-EndermanEscapeEvent.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 13:15:55 -0400 +Subject: [PATCH] EndermanEscapeEvent + +Fires an event anytime an enderman intends to teleport away from the player + +You may cancel this, enabling ranged attacks to damage the enderman for example. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 1c2998c89fd660d6b26b7ff48cddd1862b9b1828..1b9c77666204765a3ed5648b0f8eaa820f578e58 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -2,6 +2,7 @@ package net.minecraft.world.entity.monster; + + import java.util.EnumSet; + import java.util.Optional; ++import com.destroystokyo.paper.event.entity.EndermanEscapeEvent; // Paper + import java.util.Random; + import java.util.UUID; + import java.util.function.Predicate; +@@ -109,6 +110,12 @@ public class EnderMan extends Monster implements NeutralMob { + setGoalTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, true); + } + ++ // Paper start ++ private boolean tryEscape(EndermanEscapeEvent.Reason reason) { ++ return new EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent(); ++ } ++ // Paper end ++ + @Override + public boolean setGoalTarget(LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { + if (!super.setGoalTarget(entityliving, reason, fireEvent)) { +@@ -262,7 +269,7 @@ public class EnderMan extends Monster implements NeutralMob { + if (this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) { + float f = this.getBrightness(); + +- if (f > 0.5F && this.level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) { ++ if (f > 0.5F && this.level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper + this.setTarget((LivingEntity) null); + this.teleport(); + } +@@ -360,17 +367,19 @@ public class EnderMan extends Monster implements NeutralMob { + if (this.isInvulnerableTo(source)) { + return false; + } else if (source instanceof IndirectEntityDamageSource) { ++ if (this.tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start + for (int i = 0; i < 64; ++i) { + if (this.teleport()) { + return true; + } + } ++ } // Paper end + + return false; + } else { + boolean flag = super.hurt(source, amount); + +- if (!this.level.isClientSide() && !(source.getEntity() instanceof LivingEntity) && this.random.nextInt(10) != 0) { ++ if (!this.level.isClientSide() && !(source.getEntity() instanceof LivingEntity) && this.random.nextInt(10) != 0 && this.tryEscape(source == DamageSource.DROWN ? EndermanEscapeEvent.Reason.DROWN : EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - use to be critical hits as else, but mojang removed critical hits in 1.16.2 due to MC-185684 + this.teleport(); + } + +@@ -515,7 +524,7 @@ public class EnderMan extends Monster implements NeutralMob { + + static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal { + +- private final EnderMan enderman; ++ private final EnderMan enderman; public final EnderMan getEnderman() { return this.enderman; } // Paper - OBFHELPER + private Player pendingTarget; + private int aggroTime; + private int teleportTime; +@@ -578,7 +587,7 @@ public class EnderMan extends Monster implements NeutralMob { + } else { + if (this.target != null && !this.enderman.isPassenger()) { + if (this.enderman.isLookingAtMe((Player) this.target)) { +- if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D) { ++ if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D && this.getEnderman().tryEscape(EndermanEscapeEvent.Reason.STARE)) { // Paper + this.enderman.teleport(); + } + diff --git a/Remapped-Spigot-Server-Patches/0210-Enderman.teleportRandomly.patch b/Remapped-Spigot-Server-Patches/0210-Enderman.teleportRandomly.patch new file mode 100644 index 000000000..f76f4023f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0210-Enderman.teleportRandomly.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 13:29:44 -0400 +Subject: [PATCH] Enderman.teleportRandomly() + +Ability to trigger the vanilla "teleport randomly" mechanic of an enderman. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 1b9c77666204765a3ed5648b0f8eaa820f578e58..1981c08af85b16d45531ffae4fe790bb31edec04 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -278,6 +278,7 @@ public class EnderMan extends Monster implements NeutralMob { + super.customServerAiStep(); + } + ++ public final boolean teleportRandomly() { return this.teleport(); } // Paper - OBFHELPER + protected boolean teleport() { + if (!this.level.isClientSide() && this.isAlive()) { + double d0 = this.getX() + (this.random.nextDouble() - 0.5D) * 64.0D; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index d17ed71e800ebcd12b69745f239fa7dbc8a0c808..1edb45490b35b6517201acc8551da8d3c5a489de 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -16,6 +16,7 @@ public class CraftEnderman extends CraftMonster implements Enderman { + super(server, entity); + } + ++ @Override public boolean teleportRandomly() { return getHandle().teleportRandomly(); } // Paper + @Override + public MaterialData getCarriedMaterial() { + BlockState blockData = getHandle().getCarriedBlock(); diff --git a/Remapped-Spigot-Server-Patches/0211-Block-Enderpearl-Travel-Exploit.patch b/Remapped-Spigot-Server-Patches/0211-Block-Enderpearl-Travel-Exploit.patch new file mode 100644 index 000000000..47a535288 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0211-Block-Enderpearl-Travel-Exploit.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 17:15:26 -0400 +Subject: [PATCH] Block Enderpearl Travel Exploit + +Players are able to use alt accounts and enderpearls to travel +long distances utilizing the pearls in unloaded chunks and loading +the chunk later when convenient. + +This disables that by not saving the thrower when the chunk is unloaded. + +This is mainly useful for survival servers that do not allow freeform teleporting. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cebf1a623a9bec72d60fdd23dda01868ef6431d4..e8e1e7dafaf1c105b2f58cf3e118e3d665dc50ec 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -363,4 +363,10 @@ public class PaperWorldConfig { + private void disableSprintInterruptionOnAttack() { + disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false); + } ++ ++ public boolean disableEnderpearlExploit = true; ++ private void disableEnderpearlExploit() { ++ disableEnderpearlExploit = getBoolean("game-mechanics.disable-unloaded-chunk-enderpearl-exploit", disableEnderpearlExploit); ++ log("Disable Unloaded Chunk Enderpearl Exploit: " + (disableEnderpearlExploit ? "enabled" : "disabled")); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 14ed4f212a9c9c3128c4ddbef7b2e243c925b509..16b554675a276471851846d4f2bea06fdcc166d9 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -62,6 +62,7 @@ public abstract class Projectile extends Entity { + protected void readAdditionalSaveData(CompoundTag tag) { + if (tag.hasUUID("Owner")) { + this.ownerUUID = tag.getUUID("Owner"); ++ if (this instanceof ThrownEnderpearl && this.level != null && this.level.paperConfig.disableEnderpearlExploit) { this.ownerUUID = null; } // Paper - Don't store shooter name for pearls to block enderpearl travel exploit + } + + this.leftOwner = tag.getBoolean("LeftOwner"); diff --git a/Remapped-Spigot-Server-Patches/0212-Expand-World.spawnParticle-API-and-add-Builder.patch b/Remapped-Spigot-Server-Patches/0212-Expand-World.spawnParticle-API-and-add-Builder.patch new file mode 100644 index 000000000..e2c16b564 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0212-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 15 Aug 2017 22:29:12 -0400 +Subject: [PATCH] Expand World.spawnParticle API and add Builder + +Adds ability to control who receives it and who is the source/sender (vanish API) +the standard API is to send the packet to everyone in the world, which is ineffecient. +Adds an option to control the force mode of the particle. + +This adds a new Builder API which is much friendlier to use. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 20650bfd10abfa010e71cfeede06c461d50d19a3..5110f2c70d96284e8e7592b3d89266b867b9a466 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -164,7 +164,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); + private final Map entitiesByUuid = Maps.newHashMap(); + private final Queue toAddAfterTick = Queues.newArrayDeque(); +- private final List players = Lists.newArrayList(); ++ public final List players = Lists.newArrayList(); // Paper - private -> public + public final ServerChunkCache chunkSource; // Paper - public + boolean tickingEntities; + private final MinecraftServer server; +@@ -1472,12 +1472,17 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper start - Particle API Expansion ++ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); ++ } ++ public int sendParticles(List receivers, ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper end + ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); + // CraftBukkit end + int j = 0; + +- for (int k = 0; k < this.players.size(); ++k) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); ++ for (Player entityhuman : receivers) { // Paper - Particle API Expansion ++ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API Expansion + if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit + + if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 88658d4deacc29128c537e2e02fdc8f684090a2c..beb7219312be5143a50b0841c25efea5dbcc1267 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2334,11 +2334,17 @@ public class CraftWorld implements World { + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper start - Particle API Expansion ++ spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); ++ } ++ public void spawnParticle(Particle particle, List receivers, Player sender, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper end + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } + getHandle().sendParticles( +- null, // Sender ++ receivers == null ? getHandle().players : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API Expansion ++ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API Expansion + CraftParticle.toNMS(particle, data), // Particle + x, y, z, // Position + count, // Count diff --git a/Remapped-Spigot-Server-Patches/0213-EndermanAttackPlayerEvent.patch b/Remapped-Spigot-Server-Patches/0213-EndermanAttackPlayerEvent.patch new file mode 100644 index 000000000..3341055b4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0213-EndermanAttackPlayerEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 May 2018 20:18:54 -0400 +Subject: [PATCH] EndermanAttackPlayerEvent + +Allow control over whether or not an enderman aggros a player. + +This allows you to override/extend the pumpkin/stare logic. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 1981c08af85b16d45531ffae4fe790bb31edec04..d190b58bea310f4006ea3deaf0d42c502d441284 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -221,20 +221,28 @@ public class EnderMan extends Monster implements NeutralMob { + this.readPersistentAngerSaveData((ServerLevel) this.level, tag); + } + ++ // Paper start - OBFHELPER - ok not really, but verify this on updates + private boolean isLookingAtMe(Player player) { +- ItemStack itemstack = (ItemStack) player.inventory.armor.get(3); ++ boolean shouldAttack = g_real(player); ++ com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); ++ event.setCancelled(!shouldAttack); ++ return event.callEvent(); ++ } ++ private boolean g_real(Player entityhuman) { ++ // Paper end ++ ItemStack itemstack = (ItemStack) entityhuman.inventory.armor.get(3); + + if (itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) { + return false; + } else { +- Vec3 vec3d = player.getViewVector(1.0F).normalize(); +- Vec3 vec3d1 = new Vec3(this.getX() - player.getX(), this.getEyeY() - player.getEyeY(), this.getZ() - player.getZ()); ++ Vec3 vec3d = entityhuman.getViewVector(1.0F).normalize(); ++ Vec3 vec3d1 = new Vec3(this.getX() - entityhuman.getX(), this.getEyeY() - entityhuman.getEyeY(), this.getZ() - entityhuman.getZ()); + double d0 = vec3d1.length(); + + vec3d1 = vec3d1.normalize(); + double d1 = vec3d.dot(vec3d1); + +- return d1 > 1.0D - 0.025D / d0 ? player.canSee(this) : false; ++ return d1 > 1.0D - 0.025D / d0 ? entityhuman.canSee(this) : false; + } + } + diff --git a/Remapped-Spigot-Server-Patches/0214-WitchConsumePotionEvent.patch b/Remapped-Spigot-Server-Patches/0214-WitchConsumePotionEvent.patch new file mode 100644 index 000000000..b17791b35 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0214-WitchConsumePotionEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:35:16 -0400 +Subject: [PATCH] WitchConsumePotionEvent + +Fires when a witch consumes the potion in their hand + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 9b0269bdd25123f5c0662187de49a869ead3ee81..dd5976d81ff57e8691ba60f425af37572edd26e7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -124,7 +124,11 @@ public class Witch extends Raider implements RangedAttackMob { + + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + if (itemstack.getItem() == Items.POTION) { +- List list = PotionUtils.getMobEffects(itemstack); ++ // Paper start ++ com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ ++ List list = event.callEvent() ? PotionUtils.getMobEffects(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion())) : null; ++ // Paper end + + if (list != null) { + Iterator iterator = list.iterator(); diff --git a/Remapped-Spigot-Server-Patches/0215-WitchThrowPotionEvent.patch b/Remapped-Spigot-Server-Patches/0215-WitchThrowPotionEvent.patch new file mode 100644 index 000000000..0630f7d92 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0215-WitchThrowPotionEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:44:58 -0400 +Subject: [PATCH] WitchThrowPotionEvent + +Fired when a witch throws a potion at a player + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index dd5976d81ff57e8691ba60f425af37572edd26e7..7cefabfb0d8a264cae24f23c06f1c5f552ff0158 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -224,9 +224,16 @@ public class Witch extends Raider implements RangedAttackMob { + potionregistry = Potions.WEAKNESS; + } + ++ // Paper start ++ ItemStack potion = PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), potionregistry); ++ com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ if (!event.callEvent()) { ++ return; ++ } ++ potion = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); + ThrownPotion entitypotion = new ThrownPotion(this.level, this); +- +- entitypotion.setItem(PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), potionregistry)); ++ entitypotion.setItem(potion); ++ // Paper end + entitypotion.xRot -= -20.0F; + entitypotion.shoot(d0, d1 + (double) (f1 * 0.2F), d2, 0.75F, 8.0F); + if (!this.isSilent()) { diff --git a/Remapped-Spigot-Server-Patches/0216-Allow-spawning-Item-entities-with-World.spawnEntity.patch b/Remapped-Spigot-Server-Patches/0216-Allow-spawning-Item-entities-with-World.spawnEntity.patch new file mode 100644 index 000000000..7e80925cf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0216-Allow-spawning-Item-entities-with-World.spawnEntity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 Jun 2018 20:39:20 -0400 +Subject: [PATCH] Allow spawning Item entities with World.spawnEntity + +This API has more capabilities than .dropItem with the Consumer function + +Item can be set inside of the Consumer pre spawn function. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index beb7219312be5143a50b0841c25efea5dbcc1267..2dc9daaeea600fff1f2efddf74b6849fd745a28c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1497,6 +1497,10 @@ public class CraftWorld implements World { + if (Boat.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.vehicle.Boat(world, x, y, z); + entity.moveTo(x, y, z, yaw, pitch); ++ // Paper start ++ } else if (org.bukkit.entity.Item.class.isAssignableFrom(clazz)) { ++ entity = new ItemEntity(world, x, y, z, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Item.byBlock(net.minecraft.world.level.block.Blocks.DIRT))); ++ // Paper end + } else if (FallingBlock.class.isAssignableFrom(clazz)) { + entity = new FallingBlockEntity(world, x, y, z, world.getBlockState(new BlockPos(x, y, z))); + } else if (Projectile.class.isAssignableFrom(clazz)) { diff --git a/Remapped-Spigot-Server-Patches/0217-WitchReadyPotionEvent.patch b/Remapped-Spigot-Server-Patches/0217-WitchReadyPotionEvent.patch new file mode 100644 index 000000000..2177cecc8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0217-WitchReadyPotionEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 22:47:26 -0400 +Subject: [PATCH] WitchReadyPotionEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 7cefabfb0d8a264cae24f23c06f1c5f552ff0158..a37ee32b46aa87be6e3eeca2892b4e7294fd1aef 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -157,7 +157,11 @@ public class Witch extends Raider implements RangedAttackMob { + } + + if (potionregistry != null) { +- this.setItemSlot(EquipmentSlot.MAINHAND, PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry)); ++ // Paper start ++ ItemStack potion = PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry); ++ org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ this.setItemSlot(EquipmentSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper end + this.usingTime = this.getMainHandItem().getUseDuration(); + this.setUsingItem(true); + if (!this.isSilent()) { diff --git a/Remapped-Spigot-Server-Patches/0218-ItemStack-getMaxItemUseDuration.patch b/Remapped-Spigot-Server-Patches/0218-ItemStack-getMaxItemUseDuration.patch new file mode 100644 index 000000000..2b415bf92 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0218-ItemStack-getMaxItemUseDuration.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 23:00:29 -0400 +Subject: [PATCH] ItemStack#getMaxItemUseDuration + +Allows you to determine how long it takes to use a usable/consumable item + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index a0815c0d7f68f345dc48c73b8253de637c7a3e0f..34187197efd5ceff0503682dc6ce313220ca916f 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -604,6 +604,7 @@ public final class ItemStack { + this.getItem().onCraftedBy(this, world, player); + } + ++ public int getItemUseMaxDuration() { return getUseDuration(); } // Paper - OBFHELPER + public int getUseDuration() { + return this.getItem().getUseDuration(this); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 5f0ccdeb8565505278caa591f7390047eab49cf4..44caf00330e4f4f74745973dbe709980f0b61269 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -173,6 +173,13 @@ public final class CraftItemStack extends ItemStack { + return (handle == null) ? Material.AIR.getMaxStackSize() : handle.getItem().getMaxStackSize(); + } + ++ // Paper start ++ @Override ++ public int getMaxItemUseDuration() { ++ return handle == null ? 0 : handle.getItemUseMaxDuration(); ++ } ++ // Paper end ++ + @Override + public void addUnsafeEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Cannot add null enchantment"); diff --git a/Remapped-Spigot-Server-Patches/0219-Implement-EntityTeleportEndGatewayEvent.patch b/Remapped-Spigot-Server-Patches/0219-Implement-EntityTeleportEndGatewayEvent.patch new file mode 100644 index 000000000..007bf52a2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0219-Implement-EntityTeleportEndGatewayEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jun 2018 14:08:39 +0200 +Subject: [PATCH] Implement EntityTeleportEndGatewayEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index 2c974f9801d209907733bed8e6c4c9ef46e2b610..b70e0633435a272ae1e9fbd12d7f18862de0b951 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -191,9 +191,20 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity implements + + } + // CraftBukkit end ++ // Paper start - EntityTeleportEndGatewayEvent - replicated from above ++ org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = entity.getBukkitEntity(); ++ org.bukkit.Location location = new Location(level.getWorld(), (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D); ++ location.setPitch(bukkitEntity.getLocation().getPitch()); ++ location.setYaw(bukkitEntity.getLocation().getYaw()); ++ ++ com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent event = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(bukkitEntity, bukkitEntity.getLocation(), location, new org.bukkit.craftbukkit.block.CraftEndGateway(MCUtil.toLocation(level, this.getBlockPos()).getBlock())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end + + entity1.setPortalCooldown(); +- entity1.teleportToWithTicket((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D); ++ entity1.teleportToWithTicket(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()); // Paper + } + + this.triggerCooldown(); diff --git a/Remapped-Spigot-Server-Patches/0220-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/Remapped-Spigot-Server-Patches/0220-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch new file mode 100644 index 000000000..1bda08648 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0220-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 01:18:49 -0400 +Subject: [PATCH] Unset Ignited flag on cancel of Explosion Event + +Otherwise the creeper infinite explodes + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index 8f8d0a23d011936150854a0606be3d63b18c57af..d9b5cf8ac01289801ded01d928fa7ead96551be5 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -47,7 +47,7 @@ public class Creeper extends Monster { + + private static final EntityDataAccessor DATA_SWELL_DIR = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_IS_POWERED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN); +- private static final EntityDataAccessor DATA_IS_IGNITED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN); ++ private static final EntityDataAccessor DATA_IS_IGNITED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor isIgnitedDW = DATA_IS_IGNITED; // Paper OBFHELPER + private int oldSwell; + public int swell; // PAIL + public int maxSwell = 30; +@@ -252,6 +252,7 @@ public class Creeper extends Monster { + this.spawnLingeringCloud(); + } else { + swell = 0; ++ this.entityData.set(isIgnitedDW, Boolean.valueOf(false)); // Paper + } + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0221-Fix-CraftEntity-hashCode.patch b/Remapped-Spigot-Server-Patches/0221-Fix-CraftEntity-hashCode.patch new file mode 100644 index 000000000..dbc094219 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0221-Fix-CraftEntity-hashCode.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 20:20:15 -0400 +Subject: [PATCH] Fix CraftEntity hashCode + +hashCodes are not allowed to change, however bukkit used a value +that does change, the entityId. + +When an entity is teleported dimensions, the entity reference is +replaced with a new one with a new entity ID. + +For hashCode, we can simply use the UUID's hashCode to keep +the hashCode from changing. + +equals() is ok to use getEntityId() because equals() should only +be true if both the left and right are the same reference. + +Since entity ids can not duplicate during runtime, this +check is essentially the same as this.getHandle() == other.getHandle() + +However, replaced it too to make it clearer of intent. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index ecb5f5ca547930f91602d539e541964cd9f10287..e1bbaf620f3ed2a6cb9ce8007a78c4cee47b653e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -745,14 +745,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return false; + } + final CraftEntity other = (CraftEntity) obj; +- return (this.getEntityId() == other.getEntityId()); ++ return (this.getHandle() == other.getHandle()); // Paper - while logically the same, this is clearer + } + ++ // Paper - Fix hashCode. entity ID's are not static. ++ // A CraftEntity can change reference to a new entity with a new ID, and hash codes should never change + @Override + public int hashCode() { +- int hash = 7; +- hash = 29 * hash + this.getEntityId(); +- return hash; ++ return getUniqueId().hashCode(); ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0222-Configurable-Alternative-LootPool-Luck-Formula.patch b/Remapped-Spigot-Server-Patches/0222-Configurable-Alternative-LootPool-Luck-Formula.patch new file mode 100644 index 000000000..5a10f8c4d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0222-Configurable-Alternative-LootPool-Luck-Formula.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Jun 2018 00:30:32 -0400 +Subject: [PATCH] Configurable Alternative LootPool Luck Formula + +Rewrites the Vanilla luck application formula so that luck can be +applied to items that do not have any quality defined. + +See: https://luckformula.emc.gs for data and details +----------- + +The rough summary is: +My goal was that in a pool, when luck was applied, the pool +rebalances so the percentages for bigger items is +lowered and smaller items is boosted. + +Do this by boosting and then reducing the weight value, +so that larger numbers are penalized more than smaller numbers. +resulting in a larger reduction of entries for more common +items than the reduction on small weights, +giving smaller weights more of a chance + +----------- + +This work kind of obsoletes quality, but quality would be useful +for 2 items with same weight that you want luck to impact +in varying directions. + +Fishing still falls into that as the weights are closer, so luck +will invalidate junk more. + +This change will result in some major changes to fishing formulas. + +----------- + +I would love to see this change in Vanilla, so Mojang please pull :) + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 05a5abb951abe37f30a719cb75376d2d43c0d252..77a03abd59db4a43f6f2d59d4c7ef176e782f205 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -288,4 +288,12 @@ public class PaperConfig { + SpigotConfig.save(); + } + } ++ ++ public static boolean useAlternativeLuckFormula = false; ++ private static void useAlternativeLuckFormula() { ++ useAlternativeLuckFormula = getBoolean("settings.use-alternative-luck-formula", false); ++ if (useAlternativeLuckFormula) { ++ Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java +index ceb5e5405ed20c8de954847bbb269109107a43fc..d99bc0b8a1e9c4749b176a823b879ced9efdd7d6 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java +@@ -8,7 +8,6 @@ import java.util.List; + import java.util.function.BiFunction; + import java.util.function.Consumer; + import net.minecraft.util.GsonHelper; +-import net.minecraft.util.Mth; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.storage.loot.LootContext; + import net.minecraft.world.level.storage.loot.ValidationContext; +@@ -20,8 +19,8 @@ import org.apache.commons.lang3.ArrayUtils; + + public abstract class LootPoolSingletonContainer extends LootPoolEntryContainer { + +- protected final int weight; +- protected final int quality; ++ protected final int weight; public int getWeight() { return weight; } // Paper - OBFHELPER ++ protected final int quality; public int getQuality() { return quality; } // Paper - OBFHELPER + protected final LootItemFunction[] functions; + private final BiFunction compositeFunction; + private final LootPoolEntry entry = new LootPoolSingletonContainer.EntryBase() { +@@ -152,11 +151,38 @@ public abstract class LootPoolSingletonContainer extends LootPoolEntryContainer + + public abstract class EntryBase implements LootPoolEntry { + +- protected EntryBase() {} ++ protected EntryBase() { ++ } + + @Override + public int getWeight(float luck) { +- return Math.max(Mth.floor((float) LootPoolSingletonContainer.this.weight + (float) LootPoolSingletonContainer.this.quality * luck), 0); ++ // Paper start - Offer an alternative loot formula to refactor how luck bonus applies ++ // SEE: https://luckformula.emc.gs for details and data ++ if (lastLuck != null && lastLuck == luck) { ++ return lastWeight; ++ } ++ // This is vanilla ++ float qualityModifer = (float) getQuality() * luck; ++ double baseWeight = (getWeight() + qualityModifer); ++ if (com.destroystokyo.paper.PaperConfig.useAlternativeLuckFormula) { ++ // Random boost to avoid losing precision in the final int cast on return ++ final int weightBoost = 100; ++ baseWeight *= weightBoost; ++ // If we have vanilla 1, bump that down to 0 so nothing is is impacted ++ // vanilla 3 = 300, 200 basis = impact 2% ++ // =($B2*(($B2-100)/100/100)) ++ double impacted = baseWeight * ((baseWeight - weightBoost) / weightBoost / 100); ++ // =($B$7/100) ++ float luckModifier = Math.min(100, luck * 10) / 100; ++ // =B2 - (C2 *($B$7/100)) ++ baseWeight = Math.ceil(baseWeight - (impacted * luckModifier)); ++ } ++ lastLuck = luck; ++ lastWeight = (int) Math.max(0, Math.floor(baseWeight)); ++ return lastWeight; + } + } ++ private Float lastLuck = null; ++ private int lastWeight = 0; ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0223-Print-Error-details-when-failing-to-save-player-data.patch b/Remapped-Spigot-Server-Patches/0223-Print-Error-details-when-failing-to-save-player-data.patch new file mode 100644 index 000000000..368773ef8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0223-Print-Error-details-when-failing-to-save-player-data.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Jun 2018 20:37:03 -0400 +Subject: [PATCH] Print Error details when failing to save player data + + +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index 60fe01e824e4657d2601797d7858d5de339ab255..5b2a558d4d357d0de033ec2d7a9b4686f202c903 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -43,7 +43,7 @@ public class PlayerDataStorage { + + Util.safeReplaceFile(file1, file, file2); + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", entityhuman.getName().getString()); ++ PlayerDataStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getScoreboardName(), exception); // Paper + } + + } diff --git a/Remapped-Spigot-Server-Patches/0224-Make-shield-blocking-delay-configurable.patch b/Remapped-Spigot-Server-Patches/0224-Make-shield-blocking-delay-configurable.patch new file mode 100644 index 000000000..9472536ee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0224-Make-shield-blocking-delay-configurable.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 16 Jun 2018 01:18:16 -0500 +Subject: [PATCH] Make shield blocking delay configurable + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e8e1e7dafaf1c105b2f58cf3e118e3d665dc50ec..3e4bd1d6718d3ad2498fe9bd72eaac45044ecb77 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -369,4 +369,9 @@ public class PaperWorldConfig { + disableEnderpearlExploit = getBoolean("game-mechanics.disable-unloaded-chunk-enderpearl-exploit", disableEnderpearlExploit); + log("Disable Unloaded Chunk Enderpearl Exploit: " + (disableEnderpearlExploit ? "enabled" : "disabled")); + } ++ ++ public int shieldBlockingDelay = 5; ++ private void shieldBlockingDelay() { ++ shieldBlockingDelay = getInt("game-mechanics.shield-blocking-delay", 5); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8b0d1f4fbc43a6f37a5f9c453b5dd142a4f69745..af2e81137d9c686f8d94a1d0d7241619fa1f352c 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3308,7 +3308,7 @@ public abstract class LivingEntity extends Entity { + if (this.isUsingItem() && !this.useItem.isEmpty()) { + Item item = this.useItem.getItem(); + +- return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5; ++ return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= getShieldBlockingDelay(); // Paper - shieldBlockingDelay + } else { + return false; + } +@@ -3587,4 +3587,15 @@ public abstract class LivingEntity extends Entity { + public void broadcastBreakEvent(InteractionHand hand) { + this.broadcastBreakEvent(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND); + } ++ // Paper start ++ public int shieldBlockingDelay = level.paperConfig.shieldBlockingDelay; ++ ++ public int getShieldBlockingDelay() { ++ return shieldBlockingDelay; ++ } ++ ++ public void setShieldBlockingDelay(int shieldBlockingDelay) { ++ this.shieldBlockingDelay = shieldBlockingDelay; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 006f4c71bbcda61b55815e7ceab91731ab062da0..663887d9aecc2823fe7d02a9b108a291cd27102c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -699,5 +699,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setArrowsStuck(int arrows) { + getHandle().setArrowCount(arrows); + } ++ ++ @Override ++ public int getShieldBlockingDelay() { ++ return getHandle().getShieldBlockingDelay(); ++ } ++ ++ @Override ++ public void setShieldBlockingDelay(int delay) { ++ getHandle().setShieldBlockingDelay(delay); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0225-Improve-EntityShootBowEvent.patch b/Remapped-Spigot-Server-Patches/0225-Improve-EntityShootBowEvent.patch new file mode 100644 index 000000000..4fc5ab999 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0225-Improve-EntityShootBowEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 15 Jun 2013 19:51:17 -0400 +Subject: [PATCH] Improve EntityShootBowEvent + +Adds missing call to Illagers and also adds Arrow ItemStack to skeltons + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index e8ad820b11dd1b89e442bb057d5761c90c4b1923..76027a7c9615495af64102744e264d7ba7c9b87e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -197,7 +197,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level.getDifficulty().getId() * 4)); + // CraftBukkit start +- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), null, entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getOriginalItemStack(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper + if (event.isCancelled()) { + event.getProjectile().remove(); + return; +diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +index ab8c41e72c15ee9e5256eba2ba2681a33ce8a8d9..2d07e9cf4c84bc32a7624f65173c4e8a6dc07165 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +@@ -171,8 +171,18 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { + double d3 = (double) Mth.sqrt(d0 * d0 + d2 * d2); + + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level.getDifficulty().getId() * 4)); ++ // Paper start ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getOriginalItemStack(), entityarrow, target.getUsedItemHand(), 0.8F, true); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { ++ this.level.addFreshEntity(entityarrow); ++ } + this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +- this.level.addFreshEntity(entityarrow); ++ // Paper end + } + + class IllusionerBlindnessSpellGoal extends SpellcasterIllager.SpellcasterUseSpellGoal { diff --git a/Remapped-Spigot-Server-Patches/0226-PlayerReadyArrowEvent.patch b/Remapped-Spigot-Server-Patches/0226-PlayerReadyArrowEvent.patch new file mode 100644 index 000000000..1250005dc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0226-PlayerReadyArrowEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 18 Jun 2018 01:12:53 -0400 +Subject: [PATCH] PlayerReadyArrowEvent + +Called when a player is firing a bow and the server is choosing an arrow to use. +Plugins can skip selection of certain arrows and control which is used. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 552920f59aae9de2cad3edcdc8c48a91dff49093..b88d8f2c377322290002e84e217f3f2f6e4e71e8 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2139,6 +2139,17 @@ public abstract class Player extends LivingEntity { + return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); + } + ++ // Paper start ++ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { ++ return !(this instanceof ServerPlayer) || ++ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( ++ ((ServerPlayer) this).getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) ++ ).callEvent(); ++ // Paper end ++ } ++ + @Override + public ItemStack getProjectile(ItemStack stack) { + if (!(stack.getItem() instanceof ProjectileWeaponItem)) { +@@ -2155,7 +2166,7 @@ public abstract class Player extends LivingEntity { + for (int i = 0; i < this.inventory.getContainerSize(); ++i) { + ItemStack itemstack2 = this.inventory.getItem(i); + +- if (predicate.test(itemstack2)) { ++ if (predicate.test(itemstack2) && tryReadyArrow(stack, itemstack2)) { // Paper + return itemstack2; + } + } diff --git a/Remapped-Spigot-Server-Patches/0227-Implement-EntityKnockbackByEntityEvent.patch b/Remapped-Spigot-Server-Patches/0227-Implement-EntityKnockbackByEntityEvent.patch new file mode 100644 index 000000000..79e858a27 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0227-Implement-EntityKnockbackByEntityEvent.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Mon, 18 Jun 2018 15:46:23 +0200 +Subject: [PATCH] Implement EntityKnockbackByEntityEvent + +This event is called when an entity receives knockback by another entity. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index af2e81137d9c686f8d94a1d0d7241619fa1f352c..04489a915d11ba970a5188a5a913432ab4ef9faa 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1336,7 +1336,7 @@ public abstract class LivingEntity extends Entity { + } + + this.hurtDir = (float) (Mth.atan2(d1, d0) * 57.2957763671875D - (double) this.yRot); +- this.knockback(0.4F, d0, d1); ++ this.doKnockback(0.4F, d0, d1, entity1); // Paper + } else { + this.hurtDir = (float) ((int) (Math.random() * 2.0D) * 180); + } +@@ -1384,7 +1384,7 @@ public abstract class LivingEntity extends Entity { + } + + protected void blockedByShield(LivingEntity target) { +- target.knockback(0.5F, target.getX() - this.getX(), target.getZ() - this.getZ()); ++ target.doKnockback(0.5F, target.getX() - this.getX(), target.getZ() - this.getZ(), this); // Paper + } + + private boolean checkTotemDeathProtection(DamageSource source) { +@@ -1627,6 +1627,11 @@ public abstract class LivingEntity extends Entity { + } + + public void knockback(float f, double d0, double d1) { ++ // Paper start - add knockbacking entity parameter ++ this.doKnockback(f, d0, d1, null); ++ } ++ public void doKnockback(float f, double d0, double d1, Entity knockingBackEntity) { ++ // Paper end - add knockbacking entity parameter + f = (float) ((double) f * (1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE))); + if (f > 0.0F) { + this.hasImpulse = true; +@@ -1634,6 +1639,16 @@ public abstract class LivingEntity extends Entity { + Vec3 vec3d1 = (new Vec3(d0, 0.0D, d1)).normalize().scale((double) f); + + this.setDeltaMovement(vec3d.x / 2.0D - vec3d1.x, this.onGround ? Math.min(0.4D, vec3d.y / 2.0D + (double) f) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ ++ // Paper start - call EntityKnockbackByEntityEvent ++ Vec3 currentMot = this.getDeltaMovement(); ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(currentMot.x - vec3d.x, currentMot.y - vec3d.y, currentMot.z - vec3d.z); ++ // Restore old velocity to be able to access it in the event ++ this.setDeltaMovement(vec3d); ++ if (knockingBackEntity == null || new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent((org.bukkit.entity.LivingEntity) getBukkitEntity(), knockingBackEntity.getBukkitEntity(), f, delta).callEvent()) { ++ this.setDeltaMovement(vec3d.x + delta.getX(), vec3d.y + delta.getY(), vec3d.z + delta.getZ()); ++ } ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index eb35c69bb777ba8d83b2304ff9f862512643e745..f3690ea49cf90c816b8b3554b47d6f2d9dfbe016 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1566,7 +1566,7 @@ public abstract class Mob extends LivingEntity { + + if (flag) { + if (f1 > 0.0F && target instanceof LivingEntity) { +- ((LivingEntity) target).knockback(f1 * 0.5F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F))); ++ ((LivingEntity) target).doKnockback(f1 * 0.5F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F)), this); // Paper + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index b88d8f2c377322290002e84e217f3f2f6e4e71e8..5e6a86b0b8999a5c47d2f9bdd9857ab5f0772033 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1174,7 +1174,7 @@ public abstract class Player extends LivingEntity { + if (flag5) { + if (i > 0) { + if (target instanceof LivingEntity) { +- ((LivingEntity) target).knockback((float) i * 0.5F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F))); ++ ((LivingEntity) target).doKnockback((float) i * 0.5F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F)), this); // Paper + } else { + target.push((double) (-Mth.sin(this.yRot * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (Mth.cos(this.yRot * 0.017453292F) * (float) i * 0.5F)); + } +@@ -1198,7 +1198,7 @@ public abstract class Player extends LivingEntity { + if (entityliving != this && entityliving != target && !this.isAlliedTo(entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { + // CraftBukkit start - Only apply knockback if the damage hits + if (entityliving.hurt(DamageSource.playerAttack(this).sweep(), f4)) { +- entityliving.knockback(0.4F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F))); ++ entityliving.doKnockback(0.4F, (double) Mth.sin(this.yRot * 0.017453292F), (double) (-Mth.cos(this.yRot * 0.017453292F)), this); // Paper + } + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0228-Expand-Explosions-API.patch b/Remapped-Spigot-Server-Patches/0228-Expand-Explosions-API.patch new file mode 100644 index 000000000..01be1df09 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0228-Expand-Explosions-API.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 20 Jun 2018 23:17:24 -0400 +Subject: [PATCH] Expand Explosions API + +Add Entity as a Source capability, and add more API choices, and on Location. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 2dc9daaeea600fff1f2efddf74b6849fd745a28c..9b5a1c3ab8ffde524e194fdeaa8eaef6b286b57b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -871,6 +871,11 @@ public class CraftWorld implements World { + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { + return !world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? Explosion.BlockInteraction.BREAK : Explosion.BlockInteraction.NONE).wasCanceled; + } ++ // Paper start ++ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { ++ return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? Explosion.BlockInteraction.BREAK : Explosion.BlockInteraction.NONE).wasCanceled; ++ } ++ // Paper end + + @Override + public boolean createExplosion(Location loc, float power) { diff --git a/Remapped-Spigot-Server-Patches/0229-LivingEntity-Hand-Raised-Item-Use-API.patch b/Remapped-Spigot-Server-Patches/0229-LivingEntity-Hand-Raised-Item-Use-API.patch new file mode 100644 index 000000000..181103a79 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0229-LivingEntity-Hand-Raised-Item-Use-API.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 Jun 2018 00:21:28 -0400 +Subject: [PATCH] LivingEntity Hand Raised/Item Use API + +How long an entity has raised hands to charge an attack or use an item + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 04489a915d11ba970a5188a5a913432ab4ef9faa..205c639d26652befebae925fc6e40976c370710f 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -209,7 +209,7 @@ public abstract class LivingEntity extends Entity { + private float speed; + private int noJumpDelay; + private float absorptionAmount; +- protected ItemStack useItem; ++ public ItemStack useItem; // Paper - public + protected int useItemRemaining; + protected int fallFlyTicks; + private BlockPos lastPos; +@@ -3291,10 +3291,12 @@ public abstract class LivingEntity extends Entity { + return this.useItem; + } + ++ public int getItemUseRemainingTime() { return this.getUseItemRemainingTicks(); } // Paper - OBFHELPER + public int getUseItemRemainingTicks() { + return this.useItemRemaining; + } + ++ public int getHandRaisedTime() { return this.getTicksUsingItem(); } // Paper - OBFHELPER + public int getTicksUsingItem() { + return this.isUsingItem() ? this.useItem.getUseDuration() - this.getUseItemRemainingTicks() : 0; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 663887d9aecc2823fe7d02a9b108a291cd27102c..6dd7a722e10a2727f68318b880f2726bb816f198 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -709,5 +709,30 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setShieldBlockingDelay(int delay) { + getHandle().setShieldBlockingDelay(delay); + } ++ ++ @Override ++ public ItemStack getActiveItem() { ++ return getHandle().useItem.asBukkitMirror(); ++ } ++ ++ @Override ++ public int getItemUseRemainingTime() { ++ return getHandle().getItemUseRemainingTime(); ++ } ++ ++ @Override ++ public int getHandRaisedTime() { ++ return getHandle().getHandRaisedTime(); ++ } ++ ++ @Override ++ public boolean isHandRaised() { ++ return getHandle().isUsingItem(); ++ } ++ ++ @Override ++ public org.bukkit.inventory.EquipmentSlot getHandRaised() { ++ return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0230-RangedEntity-API.patch b/Remapped-Spigot-Server-Patches/0230-RangedEntity-API.patch new file mode 100644 index 000000000..40db6f959 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0230-RangedEntity-API.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 26 Jun 2018 22:00:49 -0400 +Subject: [PATCH] RangedEntity API + +Allows you to determine if an entity is capable of ranged attacks, +and to perform an attack. + +diff --git a/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java b/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e75e1d0d833c96af139fd955b2585ec24281b294 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java +@@ -0,0 +1,19 @@ ++package com.destroystokyo.paper.entity; ++ ++import net.minecraft.world.entity.monster.RangedAttackMob; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++ ++public interface CraftRangedEntity extends RangedEntity { ++ T getHandle(); ++ ++ @Override ++ default void rangedAttack(LivingEntity target, float charge) { ++ getHandle().rangedAttack(((CraftLivingEntity) target).getHandle(), charge); ++ } ++ ++ @Override ++ default void setChargingAttack(boolean raiseHands) { ++ getHandle().setChargingAttack(raiseHands); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/RangedAttackMob.java b/src/main/java/net/minecraft/world/entity/monster/RangedAttackMob.java +index b3ad4d54eeb1b894c24a5a76e8b12b8d9568cd56..ae10f3933fe78f74995952a6a8acf09ef4e99823 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/RangedAttackMob.java ++++ b/src/main/java/net/minecraft/world/entity/monster/RangedAttackMob.java +@@ -4,5 +4,8 @@ import net.minecraft.world.entity.LivingEntity; + + public interface RangedAttackMob { + +- void performRangedAttack(LivingEntity target, float pullProgress); ++ void performRangedAttack(LivingEntity target, float pullProgress); default void rangedAttack(LivingEntity entityliving, float f) { performRangedAttack(entityliving, f); } // Paper - OBFHELPER ++ ++ // - see EntitySkeletonAbstract melee goal ++ void setAggressive(boolean flag); default void setChargingAttack(boolean charging) { setAggressive(charging); }; // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +index 34cb8062168258bfd168826ceeb2fde669f6d1a8..03e2acd4829da449a471b0fa1a311e74aee114d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +@@ -4,7 +4,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Drowned; + import org.bukkit.entity.EntityType; + +-public class CraftDrowned extends CraftZombie implements Drowned { ++public class CraftDrowned extends CraftZombie implements Drowned, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftDrowned(CraftServer server, net.minecraft.world.entity.monster.Drowned entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +index 59b866e54e0d7e1dd8815ffa85275e36271113da..bbf7189a0fc9921e7a6007494f91229d9fba0846 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +@@ -4,7 +4,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Illusioner; + +-public class CraftIllusioner extends CraftSpellcaster implements Illusioner { ++public class CraftIllusioner extends CraftSpellcaster implements Illusioner, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftIllusioner(CraftServer server, net.minecraft.world.entity.monster.Illusioner entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 04f716af67939b2dc34875f722816dd1ffdc4966..ed3b8fcc221d6c0789eb92eb4716d640ba0fec9f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.entity.CraftRangedEntity; + import com.google.common.base.Preconditions; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.inventory.CraftInventoryLlama; +@@ -9,7 +10,7 @@ import org.bukkit.entity.Llama; + import org.bukkit.entity.Llama.Color; + import org.bukkit.inventory.LlamaInventory; + +-public class CraftLlama extends CraftChestedHorse implements Llama { ++public class CraftLlama extends CraftChestedHorse implements Llama, CraftRangedEntity { // Paper + + public CraftLlama(CraftServer server, net.minecraft.world.entity.animal.horse.Llama entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index 12bce49fcb164b6311a81024d1749a582ab1be25..c06fea6e0eaf58b8e7441ccf8595d6ca8417526a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -13,7 +13,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Piglin; + import org.bukkit.inventory.Inventory; + +-public class CraftPiglin extends CraftPiglinAbstract implements Piglin { ++public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftPiglin(CraftServer server, net.minecraft.world.entity.monster.piglin.Piglin entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +index fc86b8ecc349ed59c9eb6de03086d4027cb4e08d..949260d6750e71f148229955c94bcbaad9d54a2d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +@@ -6,7 +6,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Pillager; + import org.bukkit.inventory.Inventory; + +-public class CraftPillager extends CraftIllager implements Pillager { ++public class CraftPillager extends CraftIllager implements Pillager, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftPillager(CraftServer server, net.minecraft.world.entity.monster.Pillager entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index 4cd3dfd3466f384aab06dacd388e8053b045b046..b2d3244cca4d9d108159f3537d8a9aace3f8e77f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -6,7 +6,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Skeleton; + import org.bukkit.entity.Skeleton.SkeletonType; + +-public class CraftSkeleton extends CraftMonster implements Skeleton { ++public class CraftSkeleton extends CraftMonster implements Skeleton, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftSkeleton(CraftServer server, AbstractSkeleton entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +index fcce4aa391d0c448531815e99e0e32c84203c1b8..a7164a921f479c928049d4e812eab50567e96fc2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Snowman; + +-public class CraftSnowman extends CraftGolem implements Snowman { ++public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftSnowman(CraftServer server, SnowGolem entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index 60e00e539d214eb8854a53364c92c3cf55ca1062..d4eeb071dbbfca3ecea256228853bcb5c11f49ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -4,7 +4,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Witch; + +-public class CraftWitch extends CraftRaider implements Witch { ++public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftWitch(CraftServer server, net.minecraft.world.entity.monster.Witch entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 728a8c0f5781f33bdb096aefb569e9509dda8c89..fdcd680b972da54f9cdb41dff5563e42bd12d8e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.boss.CraftBossBar; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Wither; + +-public class CraftWither extends CraftMonster implements Wither { ++public class CraftWither extends CraftMonster implements Wither, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + private BossBar bossBar; + diff --git a/Remapped-Spigot-Server-Patches/0231-Add-config-to-disable-ender-dragon-legacy-check.patch b/Remapped-Spigot-Server-Patches/0231-Add-config-to-disable-ender-dragon-legacy-check.patch new file mode 100644 index 000000000..1ba869587 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0231-Add-config-to-disable-ender-dragon-legacy-check.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 22 Jun 2018 10:38:31 -0500 +Subject: [PATCH] Add config to disable ender dragon legacy check + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3e4bd1d6718d3ad2498fe9bd72eaac45044ecb77..4813f62d1e382d5ac6971b2244df3f13c80d1950 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -374,4 +374,9 @@ public class PaperWorldConfig { + private void shieldBlockingDelay() { + shieldBlockingDelay = getInt("game-mechanics.shield-blocking-delay", 5); + } ++ ++ public boolean scanForLegacyEnderDragon = true; ++ private void scanForLegacyEnderDragon() { ++ scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 2386ffeec60851ba192b89bc6fd7ffff9c56aff5..4b18931225ef60dbcffd7fcc20d0e9ce62348a07 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -73,7 +73,7 @@ public class EndDragonFight { + private boolean dragonKilled; + private boolean previouslyKilled; + public UUID dragonUUID; +- private boolean needsStateScanning; ++ private boolean needsStateScanning; private void setScanForLegacyFight(boolean scanForLegacyFight) { this.needsStateScanning = scanForLegacyFight; } private boolean scanForLegacyFight() { return this.needsStateScanning; } // Paper - OBFHELPER + public BlockPos portalLocation; + public DragonRespawnAnimation respawnStage; + private int respawnTime; diff --git a/Remapped-Spigot-Server-Patches/0232-Implement-World.getEntity-UUID-API.patch b/Remapped-Spigot-Server-Patches/0232-Implement-World.getEntity-UUID-API.patch new file mode 100644 index 000000000..6bcd99431 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0232-Implement-World.getEntity-UUID-API.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 3 Jul 2018 16:08:14 +0200 +Subject: [PATCH] Implement World.getEntity(UUID) API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9b5a1c3ab8ffde524e194fdeaa8eaef6b286b57b..3a3466cd9bbd34dbc0b79567f5579e84a81d6009 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1286,6 +1286,15 @@ public class CraftWorld implements World { + return list; + } + ++ // Paper start - getEntity by UUID API ++ @Override ++ public Entity getEntity(UUID uuid) { ++ Validate.notNull(uuid, "UUID cannot be null"); ++ net.minecraft.world.entity.Entity entity = world.getEntity(uuid); ++ return entity == null ? null : entity.getBukkitEntity(); ++ } ++ // Paper end ++ + @Override + public void save() { + org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot diff --git a/Remapped-Spigot-Server-Patches/0233-InventoryCloseEvent-Reason-API.patch b/Remapped-Spigot-Server-Patches/0233-InventoryCloseEvent-Reason-API.patch new file mode 100644 index 000000000..c1daa66bf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0233-InventoryCloseEvent-Reason-API.patch @@ -0,0 +1,228 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 3 Jul 2018 21:56:23 -0400 +Subject: [PATCH] InventoryCloseEvent Reason API + +Allows you to determine why an inventory was closed, enabling plugin developers +to "confirm" things based on if it was player triggered close or not. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5110f2c70d96284e8e7592b3d89266b867b9a466..ea1b15495481157912140bf5de9bf4a949c16910 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1119,7 +1119,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + for (BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper + } + } + } +@@ -1177,7 +1177,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder) { + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 0fa977a31cf945b74f3a046b6be302b10f494ad1..1441a461e749dbfa303095f6b51d655c45f68ce0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -544,7 +544,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + // Paper end + if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.containerMenu = this.inventoryMenu; + } + +@@ -717,7 +717,7 @@ public class ServerPlayer extends Player implements ContainerListener { + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper + } + + net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure +@@ -1290,7 +1290,7 @@ public class ServerPlayer extends Player implements ContainerListener { + return OptionalInt.empty(); + } else { + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + this.nextContainerCounter(); +@@ -1350,7 +1350,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + // CraftBukkit end + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + // this.nextContainerCounter(); // CraftBukkit - moved up +@@ -1414,7 +1414,12 @@ public class ServerPlayer extends Player implements ContainerListener { + + @Override + public void closeContainer() { +- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit ++ // Paper start ++ closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index fb36aa08cd2fbe3f7262dccb8cf0f7cae55aea9c..d322e99170b3cb6594efc824a879ab9a538ea1eb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -188,6 +188,7 @@ import org.bukkit.event.inventory.ClickType; + import org.bukkit.event.inventory.CraftItemEvent; + import org.bukkit.event.inventory.InventoryAction; + import org.bukkit.event.inventory.InventoryClickEvent; ++import org.bukkit.event.inventory.InventoryCloseEvent; // Paper + import org.bukkit.event.inventory.InventoryCreativeEvent; + import org.bukkit.event.inventory.InventoryType.SlotType; + import org.bukkit.event.inventory.SmithItemEvent; +@@ -2309,10 +2310,15 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { +- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); ++ // Paper start ++ handleContainerClose(packet, InventoryCloseEvent.Reason.PLAYER); ++ } ++ public void handleContainerClose(ServerboundContainerClosePacket packetplayinclosewindow, InventoryCloseEvent.Reason reason) { ++ // Paper end ++ PacketUtils.ensureRunningOnSameThread(packetplayinclosewindow, this, this.player.getLevel()); + + if (this.player.isImmobile()) return; // CraftBukkit +- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper + + this.player.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ae60b233c4d56a4964b388f96e9cc66410774e8d..51b1ce465d23b971f7e08a3175319a33183d0398 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -490,7 +490,7 @@ public abstract class PlayerList { + // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it + // See SPIGOT-5799, SPIGOT-6145 + if (entityplayer.containerMenu != entityplayer.inventoryMenu) { +- entityplayer.closeContainer(); ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 5e6a86b0b8999a5c47d2f9bdd9857ab5f0772033..709e930eef7bae5694238ed8c4d0ef59316bb715 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -249,7 +249,7 @@ public abstract class Player extends LivingEntity { + this.updateIsUnderwater(); + super.tick(); + if (!this.level.isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.containerMenu = this.inventoryMenu; + } + +@@ -444,6 +444,13 @@ public abstract class Player extends LivingEntity { + return 20; + } + ++ // Paper start - unused code, but to keep signatures aligned ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ closeContainer(); ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end ++ + public void closeContainer() { + this.containerMenu = this.inventoryMenu; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 4aec1c2b26d48cb5bea3dfb9e183526763bdb98f..a73c6ddd6bf66cc21ae5b25daacdece8cbfeeeac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -374,7 +374,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (((ServerPlayer) getHandle()).connection == null) return; + if (getHandle().containerMenu != getHandle().inventoryMenu) { + // fire INVENTORY_CLOSE if one already open +- ((ServerPlayer) getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(getHandle().containerMenu.containerId)); ++ ((ServerPlayer) getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + ServerPlayer player = (ServerPlayer) getHandle(); + AbstractContainerMenu container; +@@ -444,8 +444,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + @Override + public void closeInventory() { +- getHandle().closeContainer(); ++ // Paper start ++ getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); + } ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ getHandle().closeContainer(reason); ++ } ++ // Paper end + + @Override + public boolean isBlocking() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 46338fe5693003698de9c7b37a860c3481e06233..c7f66dddf0a0850ca4048dd47cd2ded114caa07e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -895,7 +895,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Close any foreign inventory + if (getHandle().containerMenu != getHandle().inventoryMenu) { +- getHandle().closeContainer(); ++ getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper + } + + // Check if the fromWorld and toWorld are the same. +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5e8ff18f98b03741ccbb927f87499ae36d775a86..c2f58b95db41b2205fbf2728c6a99419c6a63dfa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1175,7 +1175,7 @@ public class CraftEventFactory { + + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { + if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open +- player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId)); ++ player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + CraftServer server = player.level.getCraftServer(); +@@ -1341,8 +1341,18 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start ++ /** ++ * Incase plugins hooked into this or Spigot adds a new inventory close event. Prefer to pass a reason ++ * @param human ++ */ ++ @Deprecated + public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human) { +- InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView()); ++ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end ++ InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper + human.level.getCraftServer().getPluginManager().callEvent(event); + human.containerMenu.transferTo(human.inventoryMenu, human.getBukkitEntity()); + } diff --git a/Remapped-Spigot-Server-Patches/0234-Vex-getSummoner-API.patch b/Remapped-Spigot-Server-Patches/0234-Vex-getSummoner-API.patch new file mode 100644 index 000000000..0689023cf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0234-Vex-getSummoner-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 15:30:22 -0400 +Subject: [PATCH] Vex#getSummoner API + +Get's the NPC that summoned this Vex + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java +index 3204ae9a9243818727bedfa060c07bc34d3b4c66..ec4f6d96360e759ffc19de838fdbf3027164a424 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vex.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java +@@ -124,6 +124,7 @@ public class Vex extends Monster { + + } + ++ public Mob getOwner() { return getOwner(); } // Paper - OBFHELPER + public Mob getOwner() { + return this.owner; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 2b4e9ddca5c6429c01d780d65a64ef6a59446c69..56bec4350f36a94d4dfa71a234872a795c2dcb3f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -15,6 +15,13 @@ public class CraftVex extends CraftMonster implements Vex { + return (net.minecraft.world.entity.monster.Vex) super.getHandle(); + } + ++ // Paper start ++ public org.bukkit.entity.Mob getSummoner() { ++ net.minecraft.world.entity.Mob owner = getHandle().getOwner(); ++ return owner != null ? (org.bukkit.entity.Mob) owner.getBukkitEntity() : null; ++ } ++ // Paper end ++ + @Override + public String toString() { + return "CraftVex"; diff --git a/Remapped-Spigot-Server-Patches/0235-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/Remapped-Spigot-Server-Patches/0235-Refresh-player-inventory-when-cancelling-PlayerInter.patch new file mode 100644 index 000000000..5cab7fe14 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0235-Refresh-player-inventory-when-cancelling-PlayerInter.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Fri, 13 Jul 2018 14:54:43 +0200 +Subject: [PATCH] Refresh player inventory when cancelling + PlayerInteractEntityEvent + +When interacting with entities with an item, the client will assume +the interaction is successful, and update the held item on the +client. However, if the interaction is cancelled on the server side, +the client will still mistakenly remove/replace the item in hand. + +Examples for this are milking cows with a bucket or dyeing sheep. +The bucket is replaced with milk and the dye removed from inventory. + +Refresh the player inventory when PlayerInteractEntityEvent is +cancelled to avoid this problem. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index d322e99170b3cb6594efc824a879ab9a538ea1eb..645f7b7c862acf77d70ca0b05308945424bc4d32 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2222,6 +2222,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + if (event.isCancelled()) { ++ this.player.refreshContainer(this.player.containerMenu); // Paper - Refresh player inventory + return; + } + // CraftBukkit end diff --git a/Remapped-Spigot-Server-Patches/0236-Don-t-change-the-Entity-Random-seed-for-squids.patch b/Remapped-Spigot-Server-Patches/0236-Don-t-change-the-Entity-Random-seed-for-squids.patch new file mode 100644 index 000000000..4887c3dd7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0236-Don-t-change-the-Entity-Random-seed-for-squids.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:05:00 -0400 +Subject: [PATCH] Don't change the Entity Random seed for squids + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java +index 5a7582fd4f8e883d2f08a0227932c17d7576b957..2e5a35565b6b7c4d3f7fdab45095f789c33f8937 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Squid.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java +@@ -48,7 +48,7 @@ public class Squid extends WaterAnimal { + + public Squid(EntityType type, Level world) { + super(type, world); +- this.random.setSeed((long) this.getId()); ++ //this.random.setSeed((long) this.getId()); // Paper + this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; + } + diff --git a/Remapped-Spigot-Server-Patches/0237-Re-add-vanilla-entity-warnings-for-duplicates.patch b/Remapped-Spigot-Server-Patches/0237-Re-add-vanilla-entity-warnings-for-duplicates.patch new file mode 100644 index 000000000..4deb8f2bc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0237-Re-add-vanilla-entity-warnings-for-duplicates.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:08:05 -0400 +Subject: [PATCH] Re-add vanilla entity warnings for duplicates + +These are a critical sign that somethin went wrong, and you've lost some data.... + +We should kind of know about these things you know. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ea1b15495481157912140bf5de9bf4a949c16910..914241a57c304fde220bc546261d6e959445772a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1071,7 +1071,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (entity1 == null) { + return false; + } else { +- // WorldServer.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityTypes.getName(entity1.getEntityType()), entity1.getId(), EntityTypes.getName(entity.getEntityType()), entity.getId()); // CraftBukkit ++ ServerLevel.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityType.getKey(entity1.getType()), entity1.getId(), EntityType.getKey(entity.getType()), entity.getId()); // CraftBukkit // Paper + return true; + } + } diff --git a/Remapped-Spigot-Server-Patches/0238-Avoid-item-merge-if-stack-size-above-max-stack-size.patch b/Remapped-Spigot-Server-Patches/0238-Avoid-item-merge-if-stack-size-above-max-stack-size.patch new file mode 100644 index 000000000..b47c9efed --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0238-Avoid-item-merge-if-stack-size-above-max-stack-size.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 16 Jul 2018 12:42:20 +0200 +Subject: [PATCH] Avoid item merge if stack size above max stack size + + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index de69f7c3c0ee1e74682b0db91bdaae09175690e9..70f719ba3c68c8e9414e6b4bc68002f7c962e2b9 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -208,6 +208,10 @@ public class ItemEntity extends Entity { + + private void mergeWithNeighbours() { + if (this.isMergable()) { ++ // Paper start - avoid item merge if stack size above max stack size ++ ItemStack stack = getItem(); ++ if (stack.getCount() >= stack.getMaxStackSize()) return; ++ // Paper end + // Spigot start + double radius = level.spigotConfig.itemMerge; + List list = this.level.getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(radius, radius, radius), (entityitem) -> { diff --git a/Remapped-Spigot-Server-Patches/0239-Use-asynchronous-Log4j-2-loggers.patch b/Remapped-Spigot-Server-Patches/0239-Use-asynchronous-Log4j-2-loggers.patch new file mode 100644 index 000000000..fd4a4b705 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0239-Use-asynchronous-Log4j-2-loggers.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Tue, 17 Jul 2018 16:42:17 +0200 +Subject: [PATCH] Use asynchronous Log4j 2 loggers + + +diff --git a/pom.xml b/pom.xml +index f5429f2f1979542fd93956d2f436d20d0e3a66b8..4c8a057e790c96b0ab5123549d0566371acacb46 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -88,6 +88,13 @@ + log4j-iostreams + compile + ++ ++ ++ com.lmax ++ disruptor ++ 3.4.2 ++ runtime ++ + + org.ow2.asm + asm +diff --git a/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java b/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..db652a1f7abc80bc751fd94925abaec58ab1a563 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.log; ++ ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.core.async.AsyncQueueFullPolicy; ++import org.apache.logging.log4j.core.async.EventRoute; ++ ++public final class LogFullPolicy implements AsyncQueueFullPolicy { ++ ++ /* ++ * Prevents log calls being logged out of order when the log queue is full. ++ */ ++ ++ @Override ++ public EventRoute getRoute(final long backgroundThreadId, final Level level) { ++ return EventRoute.ENQUEUE; ++ } ++} +diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties +index 0694b21465fb9e4164e71862ff24b62241b191f2..30efeb5faf8e7faccf1b252fa0ed6a9fc31c40a7 100644 +--- a/src/main/resources/log4j2.component.properties ++++ b/src/main/resources/log4j2.component.properties +@@ -1 +1,3 @@ ++Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector ++log4j2.AsyncQueueFullPolicy="com.destroystokyo.paper.log.LogFullPolicy" + log4j.skipJansi=true diff --git a/Remapped-Spigot-Server-Patches/0240-add-more-information-to-Entity.toString.patch b/Remapped-Spigot-Server-Patches/0240-add-more-information-to-Entity.toString.patch new file mode 100644 index 000000000..b0de783de --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0240-add-more-information-to-Entity.toString.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:13:28 -0400 +Subject: [PATCH] add more information to Entity.toString() + +UUID, ticks lived, valid, dead + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7e70bae5bc54ad17980789fa965fd539a7f193ea..e49fe2de6a53bdd16f0cd09b691f01f1866ffb4f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2526,7 +2526,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public String toString() { +- return String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.level == null ? "~NULL~" : this.level.toString(), this.getX(), this.getY(), this.getZ()); ++ return String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cx=%d, cz=%d, tl=%d, v=%b, d=%b]", new Object[] { this.getClass().getSimpleName(), this.getName().getString(), Integer.valueOf(this.id), this.uuid.toString(), this.level == null ? "~NULL~" : this.level.toString(), Double.valueOf(this.getX()), Double.valueOf(this.getY()), Double.valueOf(this.getZ()), xChunk, zChunk, this.tickCount, this.valid, this.removed}); // Paper - add more information + } + + public boolean isInvulnerableTo(DamageSource damageSource) { diff --git a/Remapped-Spigot-Server-Patches/0241-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/Remapped-Spigot-Server-Patches/0241-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch new file mode 100644 index 000000000..be88eeb2d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0241-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 08:25:40 -0400 +Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues + +Add -Ddebug.entities=true to your JVM flags to gain more information + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 97d5437df10a6d0124e944404e88650547b7d8a8..083db6c1899b5391231b6d5d5044a334212f148c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1145,6 +1145,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas()); + ++ entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); + playerchunkmap_entitytracker.updatePlayers(this.level.players()); + if (entity instanceof ServerPlayer) { +@@ -1186,7 +1187,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (playerchunkmap_entitytracker1 != null) { + playerchunkmap_entitytracker1.broadcastRemoved(); + } +- ++ entity.tracker = null; // Paper - We're no longer tracked + } + + protected void tick() { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 914241a57c304fde220bc546261d6e959445772a..a5d7781b13a6d61238d026f064512f7162e1e868 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -191,6 +191,9 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper ++ private static Throwable getAddToWorldStackTrace(Entity entity) { ++ return new Throwable(entity + " Added to world at " + new java.util.Date()); ++ } + + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkSource.getChunk(x, z, false); +@@ -1032,8 +1035,28 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot +- if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper ++ // Paper start ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); ++ ++ if (DEBUG_ENTITIES) { ++ Throwable thr = entity.addedToWorldStack; ++ if (thr == null) { ++ MinecraftServer.LOGGER.error("Double add entity has no add stacktrace"); ++ } else { ++ MinecraftServer.LOGGER.error("Double add stacktrace: ", thr); ++ } ++ } ++ return true; ++ } ++ // Paper end + if (entity.removed) { ++ // Paper start ++ if (DEBUG_ENTITIES) { ++ new Throwable("Tried to add entity " + entity + " but it was marked as removed already").printStackTrace(); // CraftBukkit ++ getAddToWorldStackTrace(entity).printStackTrace(); ++ } ++ // Paper end + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit + return false; + } else if (this.isUUIDUsed(entity)) { +@@ -1231,7 +1254,24 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + +- this.entitiesByUuid.put(entity.getUUID(), entity); ++ if (DEBUG_ENTITIES) { ++ entity.addedToWorldStack = getAddToWorldStackTrace(entity); ++ } ++ ++ Entity old = this.entitiesByUuid.put(entity.getUUID(), entity); ++ if (old != null && old.getId() != entity.getId() && old.valid) { ++ Logger logger = LogManager.getLogger(); ++ logger.error("Overwrote an existing entity " + old + " with " + entity); ++ if (DEBUG_ENTITIES) { ++ if (old.addedToWorldStack != null) { ++ old.addedToWorldStack.printStackTrace(); ++ } else { ++ logger.error("Oddly, the old entity was not added to the world in the normal way. Plugins?"); ++ } ++ entity.addedToWorldStack.printStackTrace(); ++ } ++ } ++ + this.getChunkSource().addEntity(entity); + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof Drowned) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e49fe2de6a53bdd16f0cd09b691f01f1866ffb4f..9d8682d367522bd85894947ad2f2a53cf0aa123a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -47,6 +47,7 @@ import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.TicketType; +@@ -160,6 +161,8 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + ++ ChunkMap.TrackedEntity tracker; // Paper ++ public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { + bukkitEntity = CraftEntity.getEntity(level.getCraftServer(), this); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b4248d46ccb1a95e21601bca1198512287edcabf..0c6c3b211b05eda8f9ab47ef0a01cc520ae28201 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -121,6 +121,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean pvpMode; + public boolean keepSpawnInMemory = true; + public org.bukkit.generator.ChunkGenerator generator; ++ public static final boolean DEBUG_ENTITIES = Boolean.getBoolean("debug.entities"); // Paper + + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; diff --git a/Remapped-Spigot-Server-Patches/0242-EnderDragon-Events.patch b/Remapped-Spigot-Server-Patches/0242-EnderDragon-Events.patch new file mode 100644 index 000000000..14fc4f84c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0242-EnderDragon-Events.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:51:27 -0500 +Subject: [PATCH] EnderDragon Events + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +index 0c2a5f5c4d7d7516793eba20205b5703fe1450d5..6b28cfb1e79903c43702f6e78e226dc78e3ccec2 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +@@ -80,7 +80,11 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { + this.flame.setDuration(200); + this.flame.setParticle(ParticleTypes.DRAGON_BREATH); + this.flame.addEffect(new MobEffectInstance(MobEffects.HARM)); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper + this.dragon.level.addFreshEntity(this.flame); ++ } else { ++ this.removeAreaEffect(); ++ } + } + + } +@@ -91,8 +95,8 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { + ++this.flameCount; + } + +- @Override +- public void end() { ++ public final void removeAreaEffect() { this.end(); } // Paper - OBFHELPER ++ @Override public void end() { + if (this.flame != null) { + this.flame.remove(); + this.flame = null; +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +index 01442af7b0c7340f0ece2fa0faa0f783b1b81c48..a43ef94f8a5c7a9d1581667beb4516cc26f30a0d 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +@@ -81,7 +81,9 @@ public class DragonStrafePlayerPhase extends AbstractDragonPhaseInstance { + DragonFireball entitydragonfireball = new DragonFireball(this.dragon.level, this.dragon, d9, d10, d11); + + entitydragonfireball.moveTo(d6, d7, d8, 0.0F, 0.0F); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) entitydragonfireball.getBukkitEntity()).callEvent()) // Paper + this.dragon.level.addFreshEntity(entitydragonfireball); ++ else entitydragonfireball.remove(); // Paper + this.fireballCharge = 0; + if (this.currentPath != null) { + while (!this.currentPath.isDone()) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +index 76dc15e07ce9ef01ad7908e289a0d695b65b2fc9..1629e409a83b11b76e1627247a838c9ebd11a648 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +@@ -58,8 +58,10 @@ public class DragonFireball extends AbstractHurtingProjectile { + } + } + ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper + this.level.levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); + this.level.addFreshEntity(entityareaeffectcloud); ++ } else entityareaeffectcloud.remove(); // Paper + this.remove(); + } + diff --git a/Remapped-Spigot-Server-Patches/0243-PlayerElytraBoostEvent.patch b/Remapped-Spigot-Server-Patches/0243-PlayerElytraBoostEvent.patch new file mode 100644 index 000000000..be2454e01 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0243-PlayerElytraBoostEvent.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:59:59 -0500 +Subject: [PATCH] PlayerElytraBoostEvent + + +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index dba52063d402eb2371441fa244b730a3313444fc..28cffbe299acccc59c34d5dbd2cf458704be5ee5 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -45,11 +45,16 @@ public class FireworkRocketItem extends Item { + // Paper start + final FireworkRocketEntity entityfireworks = new FireworkRocketEntity(world, itemstack, user); + entityfireworks.spawningEntity = user.getUUID(); +- world.addFreshEntity(entityfireworks); +- // Paper end +- if (!user.abilities.instabuild) { +- itemstack.shrink(1); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Firework) entityfireworks.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entityfireworks)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ itemstack.shrink(1); ++ } else ((EntityPlayer) user).getBukkitEntity().updateInventory(); ++ } else if (user instanceof EntityPlayer) { ++ ((EntityPlayer) user).getBukkitEntity().updateInventory(); + } ++ // Paper end + } + + return InteractionResultHolder.sidedSuccess(user.getItemInHand(hand), world.isClientSide()); diff --git a/Remapped-Spigot-Server-Patches/0244-Improve-BlockPosition-inlining.patch b/Remapped-Spigot-Server-Patches/0244-Improve-BlockPosition-inlining.patch new file mode 100644 index 000000000..675f0c036 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0244-Improve-BlockPosition-inlining.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Wed, 30 Nov 2016 20:56:58 -0600 +Subject: [PATCH] Improve BlockPosition inlining + +Normally the JVM can inline virtual getters by having two sets of code, one is the 'optimized' code and the other is the 'deoptimized' code. +If a single type is used 99% of the time, then its worth it to inline, and to revert to 'deoptimized' the 1% of the time we encounter other types. +But if two types are encountered commonly, then the JVM can't inline them both, and the call overhead remains. + +This scenario also occurs with BlockPos and MutableBlockPos. +The variables in BlockPos are final, so MutableBlockPos can't modify them. +MutableBlockPos fixes this by adding custom mutable variables, and overriding the getters to access them. + +This approach with utility methods that operate on MutableBlockPos and BlockPos. +Specific examples are BlockPosition.up(), and World.isValidLocation(). +It makes these simple methods much slower than they need to be. + +This should result in an across the board speedup in anything that accesses blocks or does logic with positions. + +This is based upon conclusions drawn from inspecting the assenmbly generated bythe JIT compiler on my microbenchmarks. +They had 'callq' (invoke) instead of 'mov' (get from memory) instructions. + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 6a58059a05e16d96894b67a544c2f595d9546c78..c5089b0da33a68e7cadbc4841b07f9d772d224a0 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -88,6 +88,7 @@ public class BlockPos extends Vec3i { + return asLong(this.getX(), this.getY(), this.getZ()); + } + ++ public static long asLong(int x, int y, int z) { return asLong(x, y, z); } // Paper - OBFHELPER + public static long asLong(int x, int y, int z) { + long l = 0L; + +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index c22de593be404c4e921724bba6a69c13759a95fd..fc4e652ada7b228cb99a3c8fb372ae9af5ddfba4 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -41,7 +41,7 @@ public class Vec3i implements Comparable { + this(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + } + +- public boolean equals(Object object) { ++ public final boolean equals(Object object) { // Paper + if (this == object) { + return true; + } else if (!(object instanceof Vec3i)) { +@@ -53,7 +53,7 @@ public class Vec3i implements Comparable { + } + } + +- public int hashCode() { ++ public final int hashCode() { // Paper + return (this.getY() + this.getZ() * 31) * 31 + this.getX(); + } + +@@ -61,15 +61,15 @@ public class Vec3i implements Comparable { + return this.getY() == baseblockposition.getY() ? (this.getZ() == baseblockposition.getZ() ? this.getX() - baseblockposition.getX() : this.getZ() - baseblockposition.getZ()) : this.getY() - baseblockposition.getY(); + } + +- public int getX() { ++ public final int getX() { // Paper + return this.x; + } + +- public int getY() { ++ public final int getY() { // Paper + return this.y; + } + +- public int getZ() { ++ public final int getZ() { // Paper + return this.z; + } + diff --git a/Remapped-Spigot-Server-Patches/0245-Optimize-RegistryID.c.patch b/Remapped-Spigot-Server-Patches/0245-Optimize-RegistryID.c.patch new file mode 100644 index 000000000..e8d895a80 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0245-Optimize-RegistryID.c.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 23 Jul 2018 13:08:19 -0400 +Subject: [PATCH] Optimize RegistryID.c() + +This is a frequent hotspot for world loading/saving. + +diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +index 66ad412e4368a8615cc66a97ac442c572813a3dd..fa4a2d29b3b4c20d7396e973801d69c4acadddda 100644 +--- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java ++++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +@@ -15,12 +15,14 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + private K[] byId; + private int nextId; + private int size; ++ private java.util.BitSet usedIds; // Paper + + public CrudeIncrementalIntIdentityHashBiMap(int size) { + size = (int) ((float) size / 0.8F); + this.keys = (K[]) (new Object[size]); // Paper - decompile fix + this.values = new int[size]; + this.byId = (K[]) (new Object[size]); // Paper - decompile fix ++ this.usedIds = new java.util.BitSet(); // Paper + } + + // Paper start - decompile fix +@@ -52,9 +54,14 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + } + + private int nextId() { +- while (this.nextId < this.byId.length && this.byId[this.nextId] != null) { +- ++this.nextId; ++ // Paper start ++ /* ++ while (this.e < this.d.length && this.d[this.e] != null) { ++ ++this.e; + } ++ */ ++ this.nextId = this.usedIds.nextClearBit(0); ++ // Paper end + + return this.nextId; + } +@@ -68,6 +75,7 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + this.byId = (K[]) (new Object[newSize]); // Paper - decompile fix + this.nextId = 0; + this.size = 0; ++ this.usedIds.clear(); // Paper + + for (int j = 0; j < ak.length; ++j) { + if (ak[j] != null) { +@@ -93,6 +101,7 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + this.keys[k] = value; + this.values[k] = id; + this.byId[id] = value; ++ this.usedIds.set(id); // Paper + ++this.size; + if (id == this.nextId) { + ++this.nextId; +@@ -157,6 +166,7 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + Arrays.fill(this.byId, (Object) null); + this.nextId = 0; + this.size = 0; ++ this.usedIds.clear(); // Paper + } + + public int size() { diff --git a/Remapped-Spigot-Server-Patches/0246-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/Remapped-Spigot-Server-Patches/0246-Option-to-prevent-armor-stands-from-doing-entity-loo.patch new file mode 100644 index 000000000..f39334e39 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0246-Option-to-prevent-armor-stands-from-doing-entity-loo.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 12:57:39 +0200 +Subject: [PATCH] Option to prevent armor stands from doing entity lookups + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4813f62d1e382d5ac6971b2244df3f13c80d1950..3562950df4868b1393790b1a1ff1fe0dc589c155 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -379,4 +379,9 @@ public class PaperWorldConfig { + private void scanForLegacyEnderDragon() { + scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); + } ++ ++ public boolean armorStandEntityLookups = true; ++ private void armorStandEntityLookups() { ++ armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 59239e202e8e99870ce3be515d2f040ad9786892..7fc69adc8afa971ee3cf815c6002628ae2149a5b 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -352,6 +352,7 @@ public class ArmorStand extends LivingEntity { + + @Override + protected void pushEntities() { ++ if (!level.paperConfig.armorStandEntityLookups) return; // Paper + List list = this.level.getEntities(this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); + + for (int i = 0; i < list.size(); ++i) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 0c6c3b211b05eda8f9ab47ef0a01cc520ae28201..7b135a3951bc168ccebdbb4e3acdc07397a820d3 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -39,6 +39,7 @@ import net.minecraft.world.DifficultyInstance; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.entity.decoration.ArmorStand; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; +@@ -854,6 +855,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // Paper end + } + } ++ // Paper start - Prevent armor stands from doing entity lookups ++ @Override ++ public boolean noCollision(@Nullable Entity entity, AABB box) { ++ if (entity instanceof ArmorStand && !entity.level.paperConfig.armorStandEntityLookups) return false; ++ return LevelAccessor.super.noCollision(entity, box); ++ } ++ // Paper end + + public Explosion explode(@Nullable Entity entity, double x, double y, double z, float power, Explosion.BlockInteraction destructionType) { + return this.explode(entity, (DamageSource) null, (ExplosionDamageCalculator) null, x, y, z, power, false, destructionType); diff --git a/Remapped-Spigot-Server-Patches/0247-Vanished-players-don-t-have-rights.patch b/Remapped-Spigot-Server-Patches/0247-Vanished-players-don-t-have-rights.patch new file mode 100644 index 000000000..f0b596c50 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0247-Vanished-players-don-t-have-rights.patch @@ -0,0 +1,194 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 14:22:26 +0200 +Subject: [PATCH] Vanished players don't have rights + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9d8682d367522bd85894947ad2f2a53cf0aa123a..a2cc3e58d59ed3d9f443b77c44d8200cc09b4da9 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -183,7 +183,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + private static double viewScale = 1.0D; + private final EntityType type; + private int id; +- public boolean blocksBuilding; ++ public boolean blocksBuilding; public final boolean blocksEntitySpawning() { return this.blocksBuilding; } // Paper - OBFHELPER + public final List passengers; + protected int boardingCooldown; + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 16b554675a276471851846d4f2bea06fdcc166d9..d1dd173c11d751b15c3afd4309e386931fd9cf8d 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -5,6 +5,7 @@ import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +@@ -157,8 +158,14 @@ public abstract class Projectile extends Entity { + protected boolean canHitEntity(Entity entity) { + if (!entity.isSpectator() && entity.isAlive() && entity.isPickable()) { + Entity entity1 = this.getOwner(); +- ++ // Paper start - Cancel hit for vanished players ++ if (entity1 instanceof ServerPlayer && entity instanceof ServerPlayer) { ++ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); ++ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); ++ if (!shooter.canSee(collided)) return false; ++ } + return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); ++ // Paper end + } else { + return false; + } +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 1ba89fa106dc24abf68d5d13c39a8d80f5582f1f..59522e367327224e5fe0fe2307858077ed812ba6 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -174,7 +174,8 @@ public class BlockItem extends Item { + Player entityhuman = context.getPlayer(); + CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of((Entity) entityhuman); + // CraftBukkit start - store default return +- boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); ++ Level world = context.getLevel(); // Paper ++ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper + org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + + BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7b135a3951bc168ccebdbb4e3acdc07397a820d3..7a9ccd203885b9b369767d1fb8c53783201d0f0f 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -30,6 +30,7 @@ import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundSource; + import net.minecraft.tags.TagContainer; +@@ -69,6 +70,10 @@ import net.minecraft.world.level.saveddata.maps.MapItemSavedData; + import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.level.storage.WritableLevelData; + import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.shapes.BooleanOp; ++import net.minecraft.world.phys.shapes.CollisionContext; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.scores.Scoreboard; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -232,6 +237,46 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } + ++ // Paper start ++ // ret true if no collision ++ public final boolean checkEntityCollision(BlockState data, Entity source, CollisionContext voxelshapedcollision, ++ BlockPos position, boolean checkCanSee) { ++ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) ++ VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ voxelshape = voxelshape.offset((double) position.getX(), (double) position.getY(), (double) position.getZ()); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ List entities = this.getEntities(null, voxelshape.bounds()); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity = entities.get(i); ++ ++ if (checkCanSee && source instanceof ServerPlayer && entity instanceof ServerPlayer ++ && !((ServerPlayer) source).getBukkitEntity().canSee(((ServerPlayer) entity).getBukkitEntity())) { ++ continue; ++ } ++ ++ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); ++ // elide the last check since vanilla calls with entity = null ++ // only we care about the source for the canSee check ++ if (entity.removed || !entity.blocksEntitySpawning()) { ++ continue; ++ } ++ ++ if (Shapes.applyOperation(voxelshape, Shapes.of(entity.getBoundingBox()), BooleanOp.AND)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end ++ + @Override + public boolean isClientSide() { + return this.isClientSide; +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index e6928557a79f51302975f2832ec911c2692eaaeb..5d7794c9533bd37193f196bda616adaaace2bbde 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -504,6 +504,7 @@ public abstract class BlockBehaviour { + return this.cache != null ? this.cache.collisionShape : this.getCollisionShape(world, pos, CollisionContext.empty()); + } + ++ public final VoxelShape getCollisionShape(BlockGetter iblockaccess, BlockPos blockposition, CollisionContext voxelshapecollision) { return this.getCollisionShape(iblockaccess, blockposition, voxelshapecollision); } // Paper - OBFHELPER + public VoxelShape getCollisionShape(BlockGetter world, BlockPos pos, CollisionContext context) { + return this.getBlock().getCollisionShape(this.asState(), world, pos, context); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 2371b52b450e2b43fa9b9549a91f853c702a9dc0..fa2942d0b0424390daee2121f8959034c5352e0b 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -45,6 +45,7 @@ public final class Shapes { + return create(new AABB(xMin, yMin, zMin, xMax, yMax, zMax)); + } + ++ public static final VoxelShape of(AABB axisAlignedbb) { return Shapes.create(axisAlignedbb); } // Paper - OBFHELPER + public static VoxelShape create(AABB box) { + int i = findBits(box.minX, box.maxX); + int j = findBits(box.minY, box.maxY); +@@ -139,6 +140,7 @@ public final class Shapes { + } + } + ++ public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, BooleanOp operatorboolean) { return Shapes.joinIsNotEmpty(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER + public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { + if (predicate.apply(false, false)) { + throw (IllegalArgumentException) Util.pauseInIde((Throwable) (new IllegalArgumentException())); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +index 026759567c58b5e7687ed1eec26fb4ffc60f9fa7..67ecdaada9f87304a9ae83d7917c7aca4676d195 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -54,6 +54,7 @@ public abstract class VoxelShape { + return this.shape.isEmpty(); + } + ++ public final VoxelShape offset(double x, double y, double z) { return this.move(x, y, z); } // Paper - OBFHELPER + public VoxelShape move(double x, double y, double z) { + return (VoxelShape) (this.isEmpty() ? Shapes.empty() : new ArrayVoxelShape(this.shape, new OffsetDoubleList(this.getCoords(Direction.Axis.X), x), new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y), new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z))); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c2f58b95db41b2205fbf2728c6a99419c6a63dfa..4cd08821305590e21a01cc4dda05370c2b721ac2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1211,6 +1211,14 @@ public class CraftEventFactory { + Projectile projectile = (Projectile) entity.getBukkitEntity(); + org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); + com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); ++ ++ if (projectile.getShooter() instanceof Player && collided instanceof Player) { ++ if (!((Player) projectile.getShooter()).canSee((Player) collided)) { ++ event.setCancelled(true); ++ return event; ++ } ++ } ++ + Bukkit.getPluginManager().callEvent(event); + return event; + } diff --git a/Remapped-Spigot-Server-Patches/0248-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch b/Remapped-Spigot-Server-Patches/0248-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch new file mode 100644 index 000000000..a8a2ab743 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0248-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 23 Jul 2018 22:18:31 -0400 +Subject: [PATCH] Mark chunk dirty anytime entities change to guarantee it + saves + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index d69ccb1f31f31ebeee477df20ce1410f9e485eb7..bd9b19d988ecf72e099efeff6ec3483a352174ec 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -559,6 +559,7 @@ public class LevelChunk implements ChunkAccess { + entity.zChunk = this.chunkPos.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ this.markUnsaved(); // Paper + } + + @Override +@@ -587,6 +588,7 @@ public class LevelChunk implements ChunkAccess { + return; + } + entityCounts.decrement(entity.getMinecraftKeyString()); ++ this.markUnsaved(); // Paper + // Paper end + this.entities.remove(entity); // Paper + } diff --git a/Remapped-Spigot-Server-Patches/0249-Add-some-Debug-to-Chunk-Entity-slices.patch b/Remapped-Spigot-Server-Patches/0249-Add-some-Debug-to-Chunk-Entity-slices.patch new file mode 100644 index 000000000..1978f629c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0249-Add-some-Debug-to-Chunk-Entity-slices.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 23 Jul 2018 22:44:23 -0400 +Subject: [PATCH] Add some Debug to Chunk Entity slices + +If we detect unexpected state, log and try to recover + +This should hopefully avoid duplicate entities ever being created +if the entity was to end up in 2 different chunk slices + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a2cc3e58d59ed3d9f443b77c44d8200cc09b4da9..7847078c54154e28ab066ea8a329f929df1e1a37 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -156,6 +156,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + } + }; ++ public List entitySlice = null; + // Paper end + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index bd9b19d988ecf72e099efeff6ec3483a352174ec..09aa608bd303b618ae2c0ebd237bcbdba60a37a8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -26,7 +26,9 @@ import net.minecraft.ReportedException; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; +@@ -550,6 +552,25 @@ public class LevelChunk implements ChunkAccess { + if (k >= this.entitySlices.length) { + k = this.entitySlices.length - 1; + } ++ // Paper - remove from any old list if its in one ++ List nextSlice = this.entitySlices[k]; // the next list to be added to ++ List currentSlice = entity.entitySlice; ++ if (nextSlice == currentSlice) { ++ if (Level.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity was already in this chunk!" + entity, new Throwable()); ++ return; // ??? silly plugins ++ } ++ if (currentSlice != null && currentSlice.contains(entity)) { ++ // Still in an old chunk... ++ if (Level.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity is still in another chunk!" + entity, new Throwable()); ++ LevelChunk chunk = entity.getCurrentChunk(); ++ if (chunk != null) { ++ chunk.removeEntity(entity); ++ } else { ++ removeEntity(entity); ++ } ++ currentSlice.remove(entity); // Just incase the above did not remove from the previous slice ++ } ++ // Paper end + + if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper + entity.inChunk = true; +@@ -559,6 +580,7 @@ public class LevelChunk implements ChunkAccess { + entity.zChunk = this.chunkPos.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ entity.entitySlice = this.entitySlices[k]; // Paper + this.markUnsaved(); // Paper + } + +@@ -584,6 +606,10 @@ public class LevelChunk implements ChunkAccess { + + // Paper start + if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null); ++ if (entitySlices[section] == entity.entitySlice) { ++ entity.entitySlice = null; ++ entity.inChunk = false; ++ } + if (!this.entitySlices[section].remove(entity)) { + return; + } +@@ -742,7 +768,7 @@ public class LevelChunk implements ChunkAccess { + // Paper start - neighbour cache + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; +- ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); +@@ -802,7 +828,7 @@ public class LevelChunk implements ChunkAccess { + // Paper start - neighbour cache + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; +- ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); diff --git a/Remapped-Spigot-Server-Patches/0250-SkeletonHorse-Additions.patch b/Remapped-Spigot-Server-Patches/0250-SkeletonHorse-Additions.patch new file mode 100644 index 000000000..6599e0685 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0250-SkeletonHorse-Additions.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 27 Jul 2018 22:36:31 -0500 +Subject: [PATCH] SkeletonHorse Additions + + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index a3bad391a719363077740aa810c9412df34b4ae5..e92a8c4c49c452e1f3f0c06398f2a74e3432262f 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -25,6 +25,7 @@ public final class EntitySelector { + public static final Predicate ATTACK_ALLOWED = (entity) -> { + return !(entity instanceof Player) || !entity.isSpectator() && !((Player) entity).isCreative() && entity.level.getDifficulty() != Difficulty.PEACEFUL; + }; ++ public static Predicate notSpectator() { return NO_SPECTATORS; } // Paper - OBFHELPER + public static final Predicate NO_SPECTATORS = (entity) -> { + return !entity.isSpectator(); + }; +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java +index 7ef99acaa24525c68b1528bd3232738baab8e1c6..eaab482ae341ddda0754e726357cd845121fb043 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java +@@ -26,7 +26,7 @@ public class SkeletonHorse extends AbstractHorse { + + private final SkeletonTrapGoal skeletonTrapGoal = new SkeletonTrapGoal(this); + private boolean isTrap; +- private int trapTime; ++ private int trapTime; public int getTrapTime() { return this.trapTime; } // Paper - OBFHELPER + + public SkeletonHorse(EntityType type, Level world) { + super(type, world); +@@ -145,10 +145,12 @@ public class SkeletonHorse extends AbstractHorse { + return 0.96F; + } + ++ public boolean isTrap() { return this.isTrap(); } // Paper - OBFHELPER + public boolean isTrap() { + return this.isTrap; + } + ++ public void setTrap(boolean trap) { this.setTrap(trap); } // Paper - OBFHELPER + public void setTrap(boolean trapped) { + if (trapped != this.isTrap) { + this.isTrap = trapped; +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +index 6c2cbaae2afe076c8b7bc53ffa91fe37e423d120..7f5fb28a7deabe1b62ed21e5ed1ea1ecca0d15b8 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +@@ -14,10 +14,14 @@ import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.level.Level; ++import org.bukkit.entity.HumanEntity; ++ ++import java.util.List; + + public class SkeletonTrapGoal extends Goal { + + private final SkeletonHorse horse; ++ private List eligiblePlayers; // Paper + + public SkeletonTrapGoal(SkeletonHorse skeletonHorse) { + this.horse = skeletonHorse; +@@ -25,12 +29,13 @@ public class SkeletonTrapGoal extends Goal { + + @Override + public boolean canUse() { +- return this.horse.level.hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); ++ return !(eligiblePlayers = this.horse.level.findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D, false)).isEmpty(); // Paper + } + + @Override + public void tick() { + ServerLevel worldserver = (ServerLevel) this.horse.level; ++ if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper + DifficultyInstance difficultydamagescaler = worldserver.getCurrentDifficultyAt(this.horse.blockPosition()); + + this.horse.setTrap(false); +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 66681b9f0e2531d3da25629e44180417b32b4d66..6a5430fe54a5c8ad119a0f3842961825a54d8d7a 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -1,6 +1,9 @@ + package net.minecraft.world.level; + ++import com.google.common.collect.ImmutableList; + import com.google.common.collect.Lists; ++import org.bukkit.entity.HumanEntity; ++ + import java.util.Iterator; + import java.util.List; + import java.util.UUID; +@@ -115,6 +118,28 @@ public interface EntityGetter { + return entityhuman; + } + ++ // Paper start ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { ++ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? EntitySelector.notSpectator() : EntitySelector.canAITarget()); ++ } ++ ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { ++ ImmutableList.Builder builder = ImmutableList.builder(); ++ ++ for (Player human : this.players()) { ++ if (predicate == null || predicate.test(human)) { ++ double distanceSquared = human.getDistanceSquared(x, y, z); ++ ++ if (radius < 0.0D || distanceSquared < radius * radius) { ++ builder.add(human.getBukkitEntity()); ++ } ++ } ++ } ++ ++ return builder.build(); ++ } ++ // Paper end ++ + @Nullable + default Player getNearestPlayer(Entity entity, double maxDistance) { + return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, false); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +index b52ca4a612e30542ef4029cb1340f616bc4c36e6..7f984639fc2697cad9d0393467b0cb896d1e55a4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +@@ -25,4 +25,26 @@ public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHo + public Variant getVariant() { + return Variant.SKELETON_HORSE; + } ++ ++ // Paper start ++ @Override ++ public net.minecraft.world.entity.animal.horse.SkeletonHorse getHandle() { ++ return (net.minecraft.world.entity.animal.horse.SkeletonHorse) super.getHandle(); ++ } ++ ++ @Override ++ public int getTrapTime() { ++ return getHandle().getTrapTime(); ++ } ++ ++ @Override ++ public boolean isTrap() { ++ return getHandle().isTrap(); ++ } ++ ++ @Override ++ public void setTrap(boolean trap) { ++ getHandle().setTrap(trap); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0251-Prevent-Saving-Bad-entities-to-chunks.patch b/Remapped-Spigot-Server-Patches/0251-Prevent-Saving-Bad-entities-to-chunks.patch new file mode 100644 index 000000000..946c60832 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0251-Prevent-Saving-Bad-entities-to-chunks.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 26 Jul 2018 00:11:12 -0400 +Subject: [PATCH] Prevent Saving Bad entities to chunks + +See https://github.com/PaperMC/Paper/issues/1223 + +Minecraft is saving invalid entities to the chunk files. + +Avoid saving bad data, and also make improvements to handle +loading these chunks. Any invalid entity will be instant killed, +so lets avoid adding it to the world... + +This lets us be safer about the dupe UUID resolver too, as now +we can ignore instant killed entities and avoid risk of duplicating +an invalid entity. + +This should reduce log occurrences of dupe uuid messages. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a5d7781b13a6d61238d026f064512f7162e1e868..8e8e5f30c512ed7d8ee987550c22d3e9df845043 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1151,6 +1151,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + List[] aentityslice = chunk.getEntitySlices(); // Spigot + int i = aentityslice.length; + ++ java.util.List toMoveChunks = new java.util.ArrayList<>(); // Paper + for (int j = 0; j < i; ++j) { + List entityslice = aentityslice[j]; // Spigot + Iterator iterator = entityslice.iterator(); +@@ -1163,11 +1164,25 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + throw (IllegalStateException) Util.pauseInIde((Throwable) (new IllegalStateException("Removing entity while ticking!"))); + } + ++ // Paper start - move out entities that shouldn't be in this chunk before it unloads ++ if (!entity.removed && (int) Math.floor(entity.getX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk.getPos().z) { ++ toMoveChunks.add(entity); ++ continue; ++ } ++ // Paper end ++ + this.entitiesById.remove(entity.getId()); + this.onEntityRemoved(entity); ++ ++ if (entity.removed) iterator.remove(); // Paper - don't save dead entities during unload + } + } + } ++ // Paper start - move out entities that shouldn't be in this chunk before it unloads ++ for (Entity entity : toMoveChunks) { ++ this.updateChunkPos(entity); ++ } ++ // Paper end + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 0efaf4d0f58bcf38b427e76bf09b96e354294159..542d6f322df5f44ad9f504c8e14c88e3fa540657 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -27,6 +27,7 @@ import net.minecraft.nbt.LongArrayTag; + import net.minecraft.nbt.ShortTag; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.ThreadedLevelLightEngine; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +@@ -349,6 +350,7 @@ public class ChunkSerializer { + nbttagcompound1.put("TileEntities", nbttaglist1); + ListTag nbttaglist2 = new ListTag(); + ++ java.util.List toUpdate = new java.util.ArrayList<>(); // Paper + if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) { + LevelChunk chunk1 = (LevelChunk) chunk; + +@@ -366,13 +368,28 @@ public class ChunkSerializer { + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); + CompoundTag nbttagcompound4 = new CompoundTag(); +- ++ // Paper start ++ if ((int) Math.floor(entity.getX()) >> 4 != chunk1.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk1.getPos().z) { ++ toUpdate.add(entity); ++ continue; ++ } ++ if (entity.removed || hasPlayerPassenger(entity)) { ++ continue; ++ } ++ // Paper end + if (entity.save(nbttagcompound4)) { + chunk1.setLastSaveHadEntities(true); + nbttaglist2.add(nbttagcompound4); + } + } + } ++ ++ // Paper start - move entities to the correct chunk ++ for (Entity entity : toUpdate) { ++ world.updateChunkPos(entity); ++ } ++ // Paper end ++ + } else { + ProtoChunk protochunk = (ProtoChunk) chunk; + +@@ -431,6 +448,19 @@ public class ChunkSerializer { + nbttagcompound1.put("Structures", packStructureData(chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences())); + return nbttagcompound; + } ++ // Paper start - this is saved with the player ++ private static boolean hasPlayerPassenger(Entity entity) { ++ for (Entity passenger : entity.passengers) { ++ if (passenger instanceof ServerPlayer) { ++ return true; ++ } ++ if (hasPlayerPassenger(passenger)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + + public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag tag) { + if (tag != null) { diff --git a/Remapped-Spigot-Server-Patches/0252-Don-t-call-getItemMeta-on-hasItemMeta.patch b/Remapped-Spigot-Server-Patches/0252-Don-t-call-getItemMeta-on-hasItemMeta.patch new file mode 100644 index 000000000..0133184a6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0252-Don-t-call-getItemMeta-on-hasItemMeta.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Thu, 26 Jul 2018 14:10:23 +0200 +Subject: [PATCH] Don't call getItemMeta on hasItemMeta + +Spigot 1.13 checks if any field (which are manually copied from the ItemStack's "tag" NBT tag) on the ItemMeta class of an ItemStack is set. + +We could just check if the "tag" NBT tag is empty, albeit that would break some plugins. The only general tag added on 1.13 is "Damage", and we can just check if the "tag" NBT tag contains any other tag that's not "Damage" (https://minecraft.gamepedia.com/Player.dat_format#Item_structure) making the `hasItemStack` method behave as before. + +Returns true if getDamage() == 0 or has damage tag or other tag is set. +Check the `ItemMetaTest#testTaggedButNotMeta` method to see how this method behaves. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 44caf00330e4f4f74745973dbe709980f0b61269..9e06912b0c13c3d61bc95e526acaa28f96b46fb9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -569,7 +569,7 @@ public final class CraftItemStack extends ItemStack { + + @Override + public boolean hasItemMeta() { +- return hasItemMeta(handle) && !CraftItemFactory.instance().equals(getItemMeta(), null); ++ return hasItemMeta(handle) && (handle.getDamageValue() != 0 || (handle.getTag() != null && handle.getTag().tags.size() >= (handle.getTag().contains(CraftMetaItem.DAMAGE.NBT) ? 2 : 1))); // Paper - keep 1.12 CraftBukkit behavior without calling getItemMeta + } + + static boolean hasItemMeta(net.minecraft.world.item.ItemStack item) { +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index 42f577ed3508ba5a380648461e149f16ce97c9bd..b85a0a4c4f134dd6012d9141244ecf97b4300b65 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -96,6 +96,34 @@ public class ItemMetaTest extends AbstractTestingBase { + assertThat(itemMeta.hasConflictingEnchant(null), is(false)); + } + ++ // Paper start ++ private void testItemMeta(ItemStack stack) { ++ assertThat("Should not have ItemMeta", stack.hasItemMeta(), is(false)); ++ ++ stack.setDurability((short) 0); ++ assertThat("ItemStack with zero durability should not have ItemMeta", stack.hasItemMeta(), is(false)); ++ ++ stack.setDurability((short) 2); ++ assertThat("ItemStack with non-zero durability should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setLore(java.util.Collections.singletonList("Lore")); ++ assertThat("ItemStack with lore and durability should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setDurability((short) 0); ++ assertThat("ItemStack with lore should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setLore(null); ++ } ++ ++ @Test ++ public void testHasItemMeta() { ++ ItemStack itemStack = new ItemStack(Material.SHEARS); ++ ++ testItemMeta(itemStack); ++ testItemMeta(CraftItemStack.asCraftCopy(itemStack)); ++ } ++ // Paper end ++ + @Test + public void testConflictingStoredEnchantment() { + EnchantmentStorageMeta itemMeta = (EnchantmentStorageMeta) Bukkit.getItemFactory().getItemMeta(Material.ENCHANTED_BOOK); diff --git a/Remapped-Spigot-Server-Patches/0253-Ignore-Dead-Entities-in-entityList-iteration.patch b/Remapped-Spigot-Server-Patches/0253-Ignore-Dead-Entities-in-entityList-iteration.patch new file mode 100644 index 000000000..ae6ea8935 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0253-Ignore-Dead-Entities-in-entityList-iteration.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 28 Jul 2018 12:18:27 -0400 +Subject: [PATCH] Ignore Dead Entities in entityList iteration + +A spigot change delays removal of entities from the entity list. +This causes a change in behavior from Vanilla where getEntities type +methods will return dead entities that they shouldn't otherwise be doing. + +This will ensure that dead entities are skipped from iteration since +they shouldn't of been in the list in the first place. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index e95b91cefb0374bd5bb57cc090f5ecd566d7a618..8fd716bf2e1402694798b8be03fd85821153be44 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -209,6 +209,7 @@ public class PaperCommand extends Command { + Collection entities = world.entitiesById.values(); + entities.forEach(e -> { + ResourceLocation key = e.getMinecraftKey(); ++ if (e.shouldBeRemoved) return; // Paper + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkPos chunk = new ChunkPos(e.xChunk, e.zChunk); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 8e8e5f30c512ed7d8ee987550c22d3e9df845043..84b2cd661697545186677ab7966556d9288c650b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1303,6 +1303,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + entity.origin = entity.getBukkitEntity().getLocation(); + } + // Paper end ++ entity.shouldBeRemoved = false; // Paper - shouldn't be removed after being re-added + new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + } + +@@ -1315,6 +1316,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.removeFromChunk(entity); + this.entitiesById.remove(entity.getId()); + this.onEntityRemoved(entity); ++ entity.shouldBeRemoved = true; // Paper + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7847078c54154e28ab066ea8a329f929df1e1a37..5bf6bc6a01ccde8a4d67b49293bb326cb09248d8 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -275,6 +275,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end ++ public boolean shouldBeRemoved; // Paper + + public float getBukkitYaw() { + return this.yRot; +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 09aa608bd303b618ae2c0ebd237bcbdba60a37a8..db28bfe95c885cdefa855c7aaa3bcf92bc52df26 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -862,6 +862,7 @@ public class LevelChunk implements ChunkAccess { + + for (int i1 = 0; i1 < l; ++i1) { + Entity entity1 = (Entity) list1.get(i1); ++ if (entity1.shouldBeRemoved) continue; // Paper + + if (entity1.getBoundingBox().intersects(box) && entity1 != except) { + if (predicate == null || predicate.test(entity1)) { +@@ -899,6 +900,7 @@ public class LevelChunk implements ChunkAccess { + + while (iterator.hasNext()) { + T entity = (T) iterator.next(); // CraftBukkit - decompile error ++ if (entity.shouldBeRemoved) continue; // Paper + + if ((type == null || entity.getType() == type) && entity.getBoundingBox().intersects(box) && predicate.test(entity)) { + result.add(entity); +@@ -921,6 +923,7 @@ public class LevelChunk implements ChunkAccess { + + while (iterator.hasNext()) { + T t0 = (T) iterator.next(); // CraftBukkit - decompile error ++ if (t0.shouldBeRemoved) continue; // Paper + + if (entityClass.isInstance(t0) && t0.getBoundingBox().intersects(box) && (predicate == null || predicate.test(t0))) { // Spigot - instance check + result.add(t0); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 3a3466cd9bbd34dbc0b79567f5579e84a81d6009..9807612aed6c4393cbe1f4b6078e45bf1ba3deb2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1022,6 +1022,7 @@ public class CraftWorld implements World { + for (Object o : world.entitiesById.values()) { + if (o instanceof net.minecraft.world.entity.Entity) { + net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o; ++ if (mcEnt.shouldBeRemoved) continue; // Paper + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null +@@ -1041,6 +1042,7 @@ public class CraftWorld implements World { + for (Object o : world.entitiesById.values()) { + if (o instanceof net.minecraft.world.entity.Entity) { + net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o; ++ if (mcEnt.shouldBeRemoved) continue; // Paper + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null +@@ -1067,6 +1069,7 @@ public class CraftWorld implements World { + + for (Object entity: world.entitiesById.values()) { + if (entity instanceof net.minecraft.world.entity.Entity) { ++ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper + Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { +@@ -1090,6 +1093,7 @@ public class CraftWorld implements World { + + for (Object entity: world.entitiesById.values()) { + if (entity instanceof net.minecraft.world.entity.Entity) { ++ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper + Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { diff --git a/Remapped-Spigot-Server-Patches/0254-Implement-Expanded-ArmorStand-API.patch b/Remapped-Spigot-Server-Patches/0254-Implement-Expanded-ArmorStand-API.patch new file mode 100644 index 000000000..4909987c8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0254-Implement-Expanded-ArmorStand-API.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Thu, 26 Jul 2018 02:25:46 -0400 +Subject: [PATCH] Implement Expanded ArmorStand API + +Add the following: +- Add proper methods for getting and setting items in both hands. Deprecates old methods +- Enable/Disable slot interactions + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 7fc69adc8afa971ee3cf815c6002628ae2149a5b..06e52a0c5decf717e35c605d6bcb46c3c7b29656 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -426,6 +426,7 @@ public class ArmorStand extends LivingEntity { + return enumitemslot; + } + ++ public final boolean isSlotDisabled(net.minecraft.world.entity.EquipmentSlot slot) { return this.isDisabled(slot); } // Paper - OBFHELPER + private boolean isDisabled(net.minecraft.world.entity.EquipmentSlot slot) { + return (this.disabledSlots & 1 << slot.getFilterFlag()) != 0 || slot.getType() == net.minecraft.world.entity.EquipmentSlot.Type.HAND && !this.isShowArms(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 16f996d505b96da8a40c7709214ebbd2a0d0d9f3..d4da5214a39b718671dcaf687cb0ff8668ce9728 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -238,5 +238,78 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public void setCanMove(boolean move) { + getHandle().canMove = move; + } ++ ++ @Override ++ public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) { ++ com.google.common.base.Preconditions.checkNotNull(slot, "slot"); ++ return getHandle().getItemBySlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)).asBukkitMirror(); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.EquipmentSlot slot, ItemStack item) { ++ com.google.common.base.Preconditions.checkNotNull(slot, "slot"); ++ switch (slot) { ++ case HAND: ++ getEquipment().setItemInMainHand(item); ++ return; ++ case OFF_HAND: ++ getEquipment().setItemInOffHand(item); ++ return; ++ case FEET: ++ setBoots(item); ++ return; ++ case LEGS: ++ setLeggings(item); ++ return; ++ case CHEST: ++ setChestplate(item); ++ return; ++ case HEAD: ++ setHelmet(item); ++ return; ++ } ++ throw new UnsupportedOperationException(slot.name()); ++ } ++ ++ @Override ++ public java.util.Set getDisabledSlots() { ++ java.util.Set disabled = new java.util.HashSet<>(); ++ for (org.bukkit.inventory.EquipmentSlot slot : org.bukkit.inventory.EquipmentSlot.values()) { ++ if (this.isSlotDisabled(slot)) { ++ disabled.add(slot); ++ } ++ } ++ return disabled; ++ } ++ ++ @Override ++ public void setDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ int disabled = 0; ++ for (org.bukkit.inventory.EquipmentSlot slot : slots) { ++ if (slot == org.bukkit.inventory.EquipmentSlot.OFF_HAND) continue; ++ net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); ++ disabled += (1 << nmsSlot.getFilterFlag()) + (1 << (nmsSlot.getFilterFlag() + 8)) + (1 << (nmsSlot.getFilterFlag() + 16)); ++ } ++ getHandle().disabledSlots = disabled; ++ } ++ ++ @Override ++ public void addDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ java.util.Collections.addAll(disabled, slots); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public void removeDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ for (final org.bukkit.inventory.EquipmentSlot slot : slots) disabled.remove(slot); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { ++ return getHandle().isSlotDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0255-AnvilDamageEvent.patch b/Remapped-Spigot-Server-Patches/0255-AnvilDamageEvent.patch new file mode 100644 index 000000000..8e4a47418 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0255-AnvilDamageEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 20 Jul 2018 23:37:03 -0500 +Subject: [PATCH] AnvilDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index aa4b03dc9a0d6b3f387f081a1887672b90c60ef9..3d53edae7e3d5bb00913384ad0eb67551a65750e 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -80,7 +80,16 @@ public class AnvilMenu extends ItemCombinerMenu { + + if (!player.abilities.instabuild && iblockdata.is((Tag) BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) { + BlockState iblockdata1 = AnvilBlock.damage(iblockdata); +- ++ // Paper start ++ com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null); ++ if (!event.callEvent()) { ++ return; ++ } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) { ++ iblockdata1 = null; ++ } else { ++ iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, iblockdata.getValue(AnvilBlock.FACING)); ++ } ++ // Paper end + if (iblockdata1 == null) { + world.removeBlock(blockposition, false); + world.levelEvent(1029, blockposition, 0); diff --git a/Remapped-Spigot-Server-Patches/0256-Add-TNTPrimeEvent.patch b/Remapped-Spigot-Server-Patches/0256-Add-TNTPrimeEvent.patch new file mode 100644 index 000000000..812624262 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0256-Add-TNTPrimeEvent.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Mon, 16 Jul 2018 00:05:05 +0300 +Subject: [PATCH] Add TNTPrimeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index ae3cf71f14526e1f356216dfaa899c8f5083d46d..37a9e9df7f7f816c214c37e545288bf9329626ed 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -61,6 +61,7 @@ import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.event.entity.EntityExplodeEvent; + import org.bukkit.event.entity.EntityRegainHealthEvent; + // CraftBukkit end ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + + public class EnderDragon extends Mob implements Enemy { + +@@ -515,6 +516,11 @@ public class EnderDragon extends Mob implements Enemy { + }); + craftBlock.getNMS().spawnAfterBreak((ServerLevel) level, blockposition, ItemStack.EMPTY); + } ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = level.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getSourceMob().getBukkitEntity()).callEvent()) ++ continue; ++ // Paper end + nmsBlock.wasExploded(level, blockposition, explosionSource); + + this.level.removeBlock(blockposition, false); +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 85170008de6e77cfb8e4f55ae440a8428d868af4..31b6c1333c7d0af28385e804e94348cef398748b 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import com.google.common.collect.ImmutableMap; + import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + import java.util.Map; + import java.util.Random; + import java.util.function.Function; +@@ -11,6 +12,7 @@ import net.minecraft.Util; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Vec3i; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.item.context.BlockPlaceContext; + import net.minecraft.world.level.BlockGetter; +@@ -288,7 +290,7 @@ public class FireBlock extends BaseFireBlock { + + world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3); + } else { +- world.removeBlock(blockposition, false); ++ if(iblockdata.getBlock() != Blocks.TNT) world.removeBlock(blockposition, false); // Paper - TNTPrimeEvent - We might be cancelling it below, move the setAir down + } + + Block block = iblockdata.getBlock(); +@@ -296,6 +298,13 @@ public class FireBlock extends BaseFireBlock { + if (block instanceof TntBlock) { + TntBlock blocktnt = (TntBlock) block; + ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = MCUtil.toBukkitBlock(world, blockposition); ++ if (!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) { ++ return; ++ } ++ world.setAir(blockposition, false); ++ // Paper end + TntBlock.explode(world, blockposition); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java +index 5c06e2f69f6c0a03f12fab6accc5f9a79ae37118..76665be4b3f2e2821c35d3d6d6407c4da4f224e7 100644 +--- a/src/main/java/net/minecraft/world/level/block/TntBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java +@@ -22,6 +22,7 @@ import net.minecraft.world.level.block.state.StateDefinition; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.BooleanProperty; + import net.minecraft.world.phys.BlockHitResult; ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + + public class TntBlock extends Block { + +@@ -36,6 +37,11 @@ public class TntBlock extends Block { + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock())) { + if (world.hasNeighborSignal(pos)) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, pos);; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) ++ return; ++ // Paper end + explode(world, pos); + world.removeBlock(pos, false); + } +@@ -46,6 +52,11 @@ public class TntBlock extends Block { + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + if (world.hasNeighborSignal(pos)) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, pos);; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) ++ return; ++ // Paper end + explode(world, pos); + world.removeBlock(pos, false); + } +@@ -64,6 +75,12 @@ public class TntBlock extends Block { + @Override + public void wasExploded(Level world, BlockPos pos, Explosion explosion) { + if (!world.isClientSide) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, pos); ++ org.bukkit.entity.Entity source = explosion.source != null ? explosion.source.getBukkitEntity() : null; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) ++ return; ++ // Paper end + PrimedTnt entitytntprimed = new PrimedTnt(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, explosion.getSourceMob()); + + entitytntprimed.setFuse((short) (world.random.nextInt(entitytntprimed.getLife() / 4) + entitytntprimed.getLife() / 8)); +@@ -92,6 +109,11 @@ public class TntBlock extends Block { + if (item != Items.FLINT_AND_STEEL && item != Items.FIRE_CHARGE) { + return super.use(state, world, pos, player, hand, hit); + } else { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, pos); ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) ++ return InteractionResult.FAIL; ++ // Paper end + explode(world, pos, (LivingEntity) player); + world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11); + if (!player.isCreative()) { +@@ -121,6 +143,13 @@ public class TntBlock extends Block { + } + // CraftBukkit end + ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition); ++ if (!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end ++ + explode(world, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null); + world.removeBlock(blockposition, false); + } diff --git a/Remapped-Spigot-Server-Patches/0257-Break-up-and-make-tab-spam-limits-configurable.patch b/Remapped-Spigot-Server-Patches/0257-Break-up-and-make-tab-spam-limits-configurable.patch new file mode 100644 index 000000000..a6f013522 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0257-Break-up-and-make-tab-spam-limits-configurable.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 29 Jul 2018 05:02:15 +0100 +Subject: [PATCH] Break up and make tab spam limits configurable + +Due to the changes in 1.13, clients will send a tab completion request +for all bukkit commands in order to factor in the lack of support for +brigadier and provide backwards support in the API. + +Craftbukkit, however; has moved the chat spam limiter to also interact +with the tab completion request, which while good for avoiding abuse, +causes 1.13 clients to easilly be kicked from a server in bukkit due +to this. Removing the spam limit could cause issues for servers, however, +there is no way for servers to manipulate this without blindly cancelling +kick events, which only causes additional complications. This also causes +issues in that the tab spam limit and chat share the same field but different +limits, meaning that a player having typed a long command may be kicked from +the server. + +Splitting the field up and making it configurable allows for server owners +to take the burden of this into their own hand without having to rely on +plugins doing unsafe things. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 77a03abd59db4a43f6f2d59d4c7ef176e782f205..bd508025b771424c942fd856c31d520b6f548082 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -296,4 +296,18 @@ public class PaperConfig { + Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); + } + } ++ ++ public static int tabSpamIncrement = 1; ++ public static int tabSpamLimit = 500; ++ private static void tabSpamLimiters() { ++ tabSpamIncrement = getInt("settings.spam-limiter.tab-spam-increment", tabSpamIncrement); ++ // Older versions used a smaller limit, which is too low for 1.13, we'll bump this up if default ++ if (version < 14) { ++ if (tabSpamIncrement == 10) { ++ set("settings.spam-limiter.tab-spam-increment", 2); ++ tabSpamIncrement = 2; ++ } ++ } ++ tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 645f7b7c862acf77d70ca0b05308945424bc4d32..900c9b1106a153bc386f6c3d9c11226f8ac69f86 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -228,6 +228,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit start - multithreaded fields + private volatile int chatSpamTickCount; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(ServerGamePacketListenerImpl.class, "chatThrottle"); ++ private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits + // CraftBukkit end + private int dropSpamTickCount; + private final Int2ShortMap expectedAcks = new Int2ShortOpenHashMap(); +@@ -363,6 +364,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.server.getProfiler().pop(); + // CraftBukkit start + for (int spam; (spam = this.chatSpamTickCount) > 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; +@@ -714,7 +716,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { + // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start +- if (chatSpamField.addAndGet(this, 1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable + server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper + return; + } diff --git a/Remapped-Spigot-Server-Patches/0258-Add-hand-to-bucket-events.patch b/Remapped-Spigot-Server-Patches/0258-Add-hand-to-bucket-events.patch new file mode 100644 index 000000000..80b6896fe --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0258-Add-hand-to-bucket-events.patch @@ -0,0 +1,341 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 2 Aug 2018 08:44:35 -0500 +Subject: [PATCH] Add hand to bucket events + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d34da1eb172a7dcda564680afecf3dc145bf09f3..125a75576442eaa4f1ff6dd153bdb31097497a3f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -724,7 +724,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop cachedSingleHashSet; // Paper + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { +- super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); ++ super(world, world.getSpawn(), world.getSharedSpawnAngle(), profile); + this.respawnDimension = Level.OVERWORLD; + interactionManager.player = this; + this.gameMode = interactionManager; +@@ -254,7 +254,7 @@ public class ServerPlayer extends Player implements ContainerListener { + // Yes, this doesn't match Vanilla, but it's the best we can do for now. + // If this is an issue, PRs are welcome + public final BlockPos getSpawnPoint(ServerLevel worldserver) { +- BlockPos blockposition = worldserver.getSharedSpawnPos(); ++ BlockPos blockposition = worldserver.getSpawn(); + + if (worldserver.dimensionType().hasSkyLight() && worldserver.worldDataServer.getGameType() != GameType.ADVENTURE) { + int i = Math.max(0, this.server.getSpawnRadius(worldserver)); +@@ -291,7 +291,7 @@ public class ServerPlayer extends Player implements ContainerListener { + // CraftBukkit end + + private void fudgeSpawnLocation(ServerLevel world) { +- BlockPos blockposition = world.getSharedSpawnPos(); ++ BlockPos blockposition = world.getSpawn(); + + if (world.dimensionType().hasSkyLight() && world.worldDataServer.getGameType() != GameType.ADVENTURE) { // CraftBukkit + int i = Math.max(0, this.server.getSpawnRadius(world)); +@@ -464,7 +464,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + if (world == null || position == null) { + world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); +- position = Vec3.atCenterOf(((ServerLevel) world).getSharedSpawnPos()); ++ position = Vec3.atCenterOf(((ServerLevel) world).getSpawn()); + } + this.level = world; + this.setPos(position.x(), position.y(), position.z()); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 51b1ce465d23b971f7e08a3175319a33183d0398..0bb397407b55bd1c464ac603ec4c189045aabbb2 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -808,7 +808,7 @@ public abstract class PlayerList { + entityplayer1.setShiftKeyDown(false); + + // entityplayer1.playerConnection.a(entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch); +- entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSharedSpawnPos(), worldserver1.getSharedSpawnAngle())); ++ entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSpawn(), worldserver1.getSharedSpawnAngle())); + entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); + entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel)); + this.sendLevelInfo(entityplayer1, worldserver1); +@@ -1090,7 +1090,7 @@ public abstract class PlayerList { + + player.connection.send(new ClientboundSetBorderPacket(worldborder, ClientboundSetBorderPacket.Type.INITIALIZE)); + player.connection.send(new ClientboundSetTimePacket(world.getGameTime(), world.getDayTime(), world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); +- player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSharedSpawnPos(), world.getSharedSpawnAngle())); ++ player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSpawn(), world.getSharedSpawnAngle())); + if (world.isRaining()) { + // CraftBukkit start - handle player weather + // entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.b, 0.0F)); +diff --git a/src/main/java/net/minecraft/server/rcon/RconConsoleSource.java b/src/main/java/net/minecraft/server/rcon/RconConsoleSource.java +index c283ea9528493fb95088870c84c6d6e3963aabb7..2633aca30601682a53b0236b94a12f0f18a87fc2 100644 +--- a/src/main/java/net/minecraft/server/rcon/RconConsoleSource.java ++++ b/src/main/java/net/minecraft/server/rcon/RconConsoleSource.java +@@ -33,7 +33,7 @@ public class RconConsoleSource implements CommandSource { + public CommandSourceStack createCommandSourceStack() { + ServerLevel worldserver = this.server.overworld(); + +- return new CommandSourceStack(this, Vec3.atLowerCornerOf((Vec3i) worldserver.getSharedSpawnPos()), Vec2.ZERO, worldserver, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null); ++ return new CommandSourceStack(this, Vec3.atLowerCornerOf((Vec3i) worldserver.getSpawn()), Vec2.ZERO, worldserver, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null); + } + + // CraftBukkit start - Send a String +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5bf6bc6a01ccde8a4d67b49293bb326cb09248d8..4503bd65b3454bad94bb7b869f4e72e3121d8a3d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2688,7 +2688,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + if (flag1) { + blockposition1 = ServerLevel.END_SPAWN_POINT; + } else { +- blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos()); ++ blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSpawn()); + } + // CraftBukkit start + CraftPortalEvent event = callPortalEvent(this, destination, blockposition1, PlayerTeleportEvent.TeleportCause.END_PORTAL, 0, 0); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java +index 90393bb196c0895f387259c2dccbb29e2ca11c87..9c67b603d4d0ee5cb7f86b25ed8754afaf9cf7b3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java +@@ -87,7 +87,7 @@ public class Cow extends Animal { + + if (itemstack.getItem() == Items.BUCKET && !this.isBaby()) { + // CraftBukkit start - Got milk? +- org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level, player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET); ++ org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level, player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); // Paper - add enumHand + + if (event.isCancelled()) { + return InteractionResult.PASS; +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index b426e155d13a1b2eb5ddb24a2d83cd7d1e92026b..d0e847e58483695d2af1c1410826bb25231cd6f6 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -69,7 +69,7 @@ public class BucketItem extends Item { + if (iblockdata.getBlock() instanceof BucketPickup) { + // CraftBukkit start + Fluid dummyFluid = ((BucketPickup) iblockdata.getBlock()).takeLiquid(DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); +- PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getBucket()); ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getBucket(), hand); // Paper - add enumhand + + if (event.isCancelled()) { + ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) +@@ -97,7 +97,7 @@ public class BucketItem extends Item { + iblockdata = world.getBlockState(blockposition); + BlockPos blockposition2 = iblockdata.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockposition : blockposition1; + +- if (this.a(user, world, blockposition2, movingobjectpositionblock1, movingobjectpositionblock1.getDirection(), blockposition, itemstack)) { // CraftBukkit ++ if (this.a(user, world, blockposition2, movingobjectpositionblock1, movingobjectpositionblock1.getDirection(), blockposition, itemstack, hand)) { // CraftBukkit // Paper - add enumhand + this.checkExtraContent(world, itemstack, blockposition2); + if (user instanceof ServerPlayer) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) user, blockposition2, itemstack); +@@ -122,10 +122,12 @@ public class BucketItem extends Item { + public void checkExtraContent(Level world, ItemStack stack, BlockPos pos) {} + + public boolean emptyBucket(@Nullable Player player, Level world, BlockPos pos, @Nullable BlockHitResult movingobjectpositionblock) { +- return a(player, world, pos, movingobjectpositionblock, null, null, null); ++ // Paper start - add enumHand ++ return a(player, world, pos, movingobjectpositionblock, null, null, null, null); + } + +- public boolean a(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack) { ++ public boolean a(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack, InteractionHand enumhand) { ++ // Paper end + // CraftBukkit end + if (!(this.content instanceof FlowingFluid)) { + return false; +@@ -138,7 +140,7 @@ public class BucketItem extends Item { + + // CraftBukkit start + if (flag1 && entityhuman != null) { +- PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack); ++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); // Paper - add enumhand + if (event.isCancelled()) { + ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 +@@ -147,7 +149,7 @@ public class BucketItem extends Item { + } + // CraftBukkit end + if (!flag1) { +- return movingobjectpositionblock != null && this.a(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack); // CraftBukkit ++ return movingobjectpositionblock != null && this.a(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit // Paper - add enumhand + } else if (world.dimensionType().ultraWarm() && this.content.is((Tag) FluidTags.WATER)) { + int i = blockposition.getX(); + int j = blockposition.getY(); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7a9ccd203885b9b369767d1fb8c53783201d0f0f..b75ffafb6840b6acab6e5b0ef5e222c4fa130977 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -277,6 +277,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end + ++ // Paper start - moved up from WorldServer ++ public BlockPos getSpawn() { ++ BlockPos blockposition = new BlockPos(this.levelData.getXSpawn(), this.levelData.getYSpawn(), this.levelData.getZSpawn()); ++ ++ if (!this.getWorldBorder().isWithinBounds(blockposition)) { ++ blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, new BlockPos(this.getWorldBorder().getCenterX(), 0.0D, this.getWorldBorder().getCenterZ())); ++ } ++ ++ return blockposition; ++ } ++ // Paper end + @Override + public boolean isClientSide() { + return this.isClientSide; +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 8a71eaf2855be0d415d1f7b18dbec98353fe5b47..b90a275a0dc2913809ce16659eed445501e486de 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -268,7 +268,7 @@ public final class NaturalSpawner { + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { + if (squaredDistance <= 576.0D) { + return false; +- } else if (world.getSharedSpawnPos().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D)) { ++ } else if (world.getSpawn().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D)) { + return false; + } else { + ChunkPos chunkcoordintpair = new ChunkPos(pos); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9807612aed6c4393cbe1f4b6078e45bf1ba3deb2..159c32d6678e83f2d98ea6a1ad48346c9de017e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -327,7 +327,7 @@ public class CraftWorld implements World { + + @Override + public Location getSpawnLocation() { +- BlockPos spawn = world.getSharedSpawnPos(); ++ BlockPos spawn = world.getSpawn(); + return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ()); + } + +@@ -1926,7 +1926,7 @@ public class CraftWorld implements World { + public void setKeepSpawnInMemory(boolean keepLoaded) { + world.keepSpawnInMemory = keepLoaded; + // Grab the worlds spawn chunk +- BlockPos chunkcoordinates = this.world.getSharedSpawnPos(); ++ BlockPos chunkcoordinates = this.world.getSpawn(); + if (keepLoaded) { + world.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(chunkcoordinates), 11, Unit.INSTANCE); + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 4cd08821305590e21a01cc4dda05370c2b721ac2..1877267344df1ff5b4de6a4e0c239f488cd52c1f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -222,7 +222,7 @@ public class CraftEventFactory { + public static Entity entityDamage; // For use in EntityDamageByEntityEvent + + // helper methods +- private static boolean canBuild(ServerLevel world, Player player, int x, int z) { ++ private static boolean canBuild(Level world, Player player, int x, int z) { + int spawnSize = Bukkit.getServer().getSpawnRadius(); + + if (world.dimension() != Level.OVERWORLD) return true; +@@ -230,7 +230,7 @@ public class CraftEventFactory { + if (((CraftServer) Bukkit.getServer()).getHandle().getOps().isEmpty()) return true; + if (player.isOp()) return true; + +- BlockPos chunkcoordinates = world.getSharedSpawnPos(); ++ BlockPos chunkcoordinates = world.getSpawn(); + + int distanceFromSpawn = Math.max(Math.abs(x - chunkcoordinates.getX()), Math.abs(z - chunkcoordinates.getZ())); + return distanceFromSpawn > spawnSize; +@@ -412,6 +412,20 @@ public class CraftEventFactory { + } + + private static PlayerEvent getPlayerBucketEvent(boolean isFilling, ServerLevel world, net.minecraft.world.entity.player.Player who, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemstack, net.minecraft.world.item.Item item) { ++ // Paper start - add enumHand ++ return getPlayerBucketEvent(isFilling, world, who, changed, clicked, clickedFace, itemstack, item, null); ++ } ++ ++ public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(Level world, net.minecraft.world.entity.player.Player who, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemstack, InteractionHand enumHand) { ++ return (PlayerBucketEmptyEvent) getPlayerBucketEvent(false, world, who, changed, clicked, clickedFace, itemstack, Items.BUCKET, enumHand); ++ } ++ ++ public static PlayerBucketFillEvent callPlayerBucketFillEvent(Level world, net.minecraft.world.entity.player.Player who, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemInHand, net.minecraft.world.item.Item bucket, InteractionHand enumHand) { ++ return (PlayerBucketFillEvent) getPlayerBucketEvent(true, world, who, clicked, changed, clickedFace, itemInHand, bucket, enumHand); ++ } ++ ++ private static PlayerEvent getPlayerBucketEvent(boolean isFilling, Level world, net.minecraft.world.entity.player.Player who, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemstack, net.minecraft.world.item.Item item, InteractionHand enumHand) { ++ // Paper end + Player player = (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asNewCraftStack(item); + Material bucket = CraftMagicNumbers.getMaterial(itemstack.getItem()); +@@ -424,10 +438,10 @@ public class CraftEventFactory { + + PlayerEvent event; + if (isFilling) { +- event = new PlayerBucketFillEvent(player, block, blockClicked, blockFace, bucket, itemInHand); ++ event = new PlayerBucketFillEvent(player, block, blockClicked, blockFace, bucket, itemInHand, enumHand == null ? null : enumHand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); // Paper - add enumHand + ((PlayerBucketFillEvent) event).setCancelled(!canBuild(world, player, changed.getX(), changed.getZ())); + } else { +- event = new PlayerBucketEmptyEvent(player, block, blockClicked, blockFace, bucket, itemInHand); ++ event = new PlayerBucketEmptyEvent(player, block, blockClicked, blockFace, bucket, itemInHand, enumHand == null ? null : enumHand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); // Paper - add enumHand + ((PlayerBucketEmptyEvent) event).setCancelled(!canBuild(world, player, changed.getX(), changed.getZ())); + } + diff --git a/Remapped-Spigot-Server-Patches/0259-MC-135506-Experience-should-save-as-Integers.patch b/Remapped-Spigot-Server-Patches/0259-MC-135506-Experience-should-save-as-Integers.patch new file mode 100644 index 000000000..07dc1c1b3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0259-MC-135506-Experience-should-save-as-Integers.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 3 Aug 2018 00:04:54 -0400 +Subject: [PATCH] MC-135506: Experience should save as Integers + + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 52b90ef3a145325209d3d903a2b7c9a44c332cbe..37758cff3c4a8406c2f1496ae827ecdc953cf9f4 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -217,7 +217,7 @@ public class ExperienceOrb extends Entity { + public void addAdditionalSaveData(CompoundTag tag) { + tag.putShort("Health", (short) this.health); + tag.putShort("Age", (short) this.age); +- tag.putShort("Value", (short) this.value); ++ tag.putInt("Value", this.value); // Paper - save as Integer + this.savePaperNBT(tag); // Paper + } + +@@ -225,7 +225,7 @@ public class ExperienceOrb extends Entity { + public void readAdditionalSaveData(CompoundTag tag) { + this.health = tag.getShort("Health"); + this.age = tag.getShort("Age"); +- this.value = tag.getShort("Value"); ++ this.value = tag.getInt("Value"); // Paper - load as Integer + this.loadPaperNBT(tag); // Paper + } + diff --git a/Remapped-Spigot-Server-Patches/0260-Fix-client-rendering-skulls-from-same-user.patch b/Remapped-Spigot-Server-Patches/0260-Fix-client-rendering-skulls-from-same-user.patch new file mode 100644 index 000000000..057731ba6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0260-Fix-client-rendering-skulls-from-same-user.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 22 Nov 2016 00:40:42 -0500 +Subject: [PATCH] Fix client rendering skulls from same user + +See: https://github.com/PaperMC/Paper/issues/1304 + +Changes the UUID sent to client to be based on either +the texture payload, or random. + +This allows the client to render multiple skull textures from the same user, +for when different skins were used when skull was made. + +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index b4542ce6a8c37ab31e6ecaeb4cbad4742cca0f9b..10f1e3d761af83507bf71a00092641e22d0c8049 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -37,6 +37,7 @@ import net.minecraft.network.chat.Component; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.entity.SkullBlockEntity; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; + import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit +@@ -310,9 +311,18 @@ public class FriendlyByteBuf extends ByteBuf { + if (item.canBeDepleted() || item.shouldOverrideMultiplayerNbt()) { + // Spigot start - filter + itemstack = itemstack.copy(); +- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ //CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer needed due to NBT being supported + // Spigot end + nbttagcompound = itemstack.getTag(); ++ // Paper start ++ if (nbttagcompound != null && nbttagcompound.contains("SkullOwner", 10)) { ++ CompoundTag owner = nbttagcompound.getCompound("SkullOwner"); ++ if (owner.hasUUID("Id")) { ++ nbttagcompound.setUUID("SkullOwnerOrig", owner.getUUID("Id")); ++ SkullBlockEntity.sanitizeUUID(owner); ++ } ++ } ++ // Paper end + } + + this.writeNbt(nbttagcompound); +@@ -332,7 +342,16 @@ public class FriendlyByteBuf extends ByteBuf { + itemstack.setTag(this.readNbt()); + // CraftBukkit start + if (itemstack.getTag() != null) { +- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ // Paper start - Fix skulls of same owner - restore orig ID since we changed it on send to client ++ if (itemstack.tag.contains("SkullOwnerOrig")) { ++ CompoundTag owner = itemstack.tag.getCompound("SkullOwner"); ++ if (itemstack.tag.contains("SkullOwnerOrig")) { ++ owner.tags.put("Id", itemstack.tag.tags.get("SkullOwnerOrig")); ++ itemstack.tag.remove("SkullOwnerOrig"); ++ } ++ } ++ // Paper end ++ // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer needed due to NBT being supported + } + // CraftBukkit end + return itemstack; +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +index e5d4363edb8c494d2db69d2e0223a2db1519f64b..4fe15aa331ca18319ca46d1b426f0d6fd24341f0 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -15,6 +15,7 @@ import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.protocol.Packet; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.SkullBlockEntity; + import net.minecraft.world.level.chunk.ChunkBiomeContainer; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.level.chunk.LevelChunkSection; +@@ -69,6 +70,7 @@ public class ClientboundLevelChunkPacket implements Packet public + private boolean emptyCacheFlag; + private Entity entityRepresentation; + private BlockInWorld cachedBreakBlock; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +index eebaeaccc3ba1a9ec089d84b8de6c9d36034868f..6a1289424421083876d1808b7328cd3f01063a7e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +@@ -25,6 +25,7 @@ import java.util.concurrent.Executors; + import java.util.concurrent.Future; + import java.util.concurrent.TimeUnit; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; + import net.minecraft.server.MinecraftServer; +@@ -152,9 +153,37 @@ public class SkullBlockEntity extends BlockEntity /*implements ITickable*/ { // + @Nullable + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { +- return new ClientboundBlockEntityDataPacket(this.worldPosition, 4, this.getUpdateTag()); ++ return new ClientboundBlockEntityDataPacket(this.worldPosition, 4, sanitizeTileEntityUUID(this.getUpdateTag())); // Paper + } + ++ // Paper start ++ public static CompoundTag sanitizeTileEntityUUID(CompoundTag cmp) { ++ CompoundTag owner = cmp.getCompound("Owner"); ++ if (!owner.isEmpty()) { ++ sanitizeUUID(owner); ++ } ++ return cmp; ++ } ++ ++ public static void sanitizeUUID(CompoundTag owner) { ++ CompoundTag properties = owner.getCompound("Properties"); ++ ListTag list = null; ++ if (!properties.isEmpty()) { ++ list = properties.getList("textures", 10); ++ } ++ ++ if (list != null && !list.isEmpty()) { ++ String textures = ((CompoundTag)list.get(0)).getString("Value"); ++ if (textures != null && textures.length() > 3) { ++ UUID uuid = UUID.nameUUIDFromBytes(textures.getBytes()); ++ owner.setUUID("Id", uuid); ++ return; ++ } ++ } ++ owner.setUUID("Id", UUID.randomUUID()); ++ } ++ // Paper end ++ + @Override + public CompoundTag getUpdateTag() { + return this.save(new CompoundTag()); diff --git a/Remapped-Spigot-Server-Patches/0261-Add-Early-Warning-Feature-to-WatchDog.patch b/Remapped-Spigot-Server-Patches/0261-Add-Early-Warning-Feature-to-WatchDog.patch new file mode 100644 index 000000000..4f9ee3cc3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0261-Add-Early-Warning-Feature-to-WatchDog.patch @@ -0,0 +1,184 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Wed, 8 Aug 2018 15:30:52 -0400 +Subject: [PATCH] Add Early Warning Feature to WatchDog + +Detect when the server has been hung for a long duration, and start printing +thread dumps at an interval until the point of crash. + +This will help diagnose what was going on in that time before the crash. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index bd508025b771424c942fd856c31d520b6f548082..62621562137cba4804f0465c58d25ca2786328e5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -25,6 +25,7 @@ import org.bukkit.configuration.file.YamlConfiguration; + import co.aikar.timings.Timings; + import co.aikar.timings.TimingsManager; + import org.spigotmc.SpigotConfig; ++import org.spigotmc.WatchdogThread; + + public class PaperConfig { + +@@ -297,6 +298,14 @@ public class PaperConfig { + } + } + ++ public static int watchdogPrintEarlyWarningEvery = 5000; ++ public static int watchdogPrintEarlyWarningDelay = 10000; ++ private static void watchdogEarlyWarning() { ++ watchdogPrintEarlyWarningEvery = getInt("settings.watchdog.early-warning-every", 5000); ++ watchdogPrintEarlyWarningDelay = getInt("settings.watchdog.early-warning-delay", 10000); ++ WatchdogThread.doStart(SpigotConfig.timeoutTime, SpigotConfig.restartOnCrash ); ++ } ++ + public static int tabSpamIncrement = 1; + public static int tabSpamLimit = 500; + private static void tabSpamLimiters() { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 125a75576442eaa4f1ff6dd153bdb31097497a3f..5a76ca77b974ff6fe862c9e05a88b507a34b44be 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1017,6 +1017,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && monotonicMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ // Paper start ++ Logger log = Bukkit.getServer().getLogger(); ++ long currentTime = monotonicMillis(); ++ if ( lastTick != 0 && timeoutTime > 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") ) + { +- Logger log = Bukkit.getServer().getLogger(); ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ // Don't spam early warning dumps ++ if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; ++ if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ lastEarlyWarning = currentTime; ++ if (isLongTimeout) { ++ // Paper end + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); +@@ -93,29 +108,46 @@ public class WatchdogThread extends Thread + } + } + // Paper end ++ } else ++ { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); ++ log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); ++ } ++ // Paper end - Different message for short timeout + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // ++ // Paper start - Only print full dump on long timeouts ++ if ( isLongTimeout ) ++ { + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + dumpThread( thread, log ); + } ++ } else { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); ++ } ++ ++ + log.log( Level.SEVERE, "------------------------------" ); + ++ if ( isLongTimeout ) ++ { + if ( restart && !MinecraftServer.getServer().hasStopped() ) + { + RestartCommand.restart(); + } + break; ++ } // Paper end + } + + try + { +- sleep( 10000 ); ++ sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout + } catch ( InterruptedException ex ) + { + interrupt(); diff --git a/Remapped-Spigot-Server-Patches/0262-Make-EnderDragon-implement-Mob.patch b/Remapped-Spigot-Server-Patches/0262-Make-EnderDragon-implement-Mob.patch new file mode 100644 index 000000000..8392dc7ba --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0262-Make-EnderDragon-implement-Mob.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 10 Aug 2018 22:11:49 -0400 +Subject: [PATCH] Make EnderDragon implement Mob + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java +index bba2e3152ee7073b75ecce1a4792178db20344db..aea39a6cb778d2ef88f66b632aebd824aaef2ea6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java +@@ -1,17 +1,17 @@ + package org.bukkit.craftbukkit.entity; + +-import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.Mob; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.ComplexLivingEntity; + +-public abstract class CraftComplexLivingEntity extends CraftLivingEntity implements ComplexLivingEntity { +- public CraftComplexLivingEntity(CraftServer server, LivingEntity entity) { ++public abstract class CraftComplexLivingEntity extends CraftMob implements ComplexLivingEntity { // Paper ++ public CraftComplexLivingEntity(CraftServer server, Mob entity) { // Paper + super(server, entity); + } + + @Override +- public LivingEntity getHandle() { +- return (LivingEntity) entity; ++ public Mob getHandle() { // Paper ++ return (Mob) entity; // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0263-Use-ConcurrentHashMap-in-JsonList.patch b/Remapped-Spigot-Server-Patches/0263-Use-ConcurrentHashMap-in-JsonList.patch new file mode 100644 index 000000000..b981b8b04 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0263-Use-ConcurrentHashMap-in-JsonList.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Tue, 7 Aug 2018 01:24:23 -0600 +Subject: [PATCH] Use ConcurrentHashMap in JsonList + +This is specifically aimed at fixing #471 + +Using a ConcurrentHashMap because thread safety +The performance benefit of Map over ConcurrentMap is negligabe at best in this scenaio, as most operations will be get and not add or remove +Even without considering the use-case the benefits are still negligable + +Original ideas for the system included an expiration policy and/or handler +The simpler solution was to use a computeIfPresent in the get method +This will simultaneously have an O(1) lookup time and automatically expire any values +Since the get method (nor other similar methods) don't seem to have a critical need to flush the map to disk at any of these points further processing is simply wasteful +Meaning the original function expired values unrelated to the current value without actually having any explicit need to + +The h method was heavily modified to be much more efficient in its processing +Also instead of being called on every get, it's now called just before a save +This will eliminate stale values being flushed to disk + +Modified isEmpty to use the isEmpty() method instead of the slightly confusing size() < 1 +The point of this is readability, but does have a side-benefit of a small microptimization + +Finally, added a couple obfhelpers for the modified code + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 0bb397407b55bd1c464ac603ec4c189045aabbb2..7c307a16ca3962db65be09a0ddd058a4ce81c7be 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -614,7 +614,7 @@ public abstract class PlayerList { + } else if (!this.isWhitelisted(gameprofile, event)) { // Paper + //chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); // Paper + //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot // Paper - moved to isWhitelisted +- } else if (getIpBans().isBanned(socketaddress) && !getIpBans().get(socketaddress).hasExpired()) { ++ } else if (getIpBans().isBanned(socketaddress) && getIpBans().get(socketaddress) != null && !getIpBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans + IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); + + chatmessage = new TranslatableComponent("multiplayer.disconnect.banned_ip.reason", new Object[]{ipbanentry.getReason()}); +diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index 2cb235d695c244863a37454df22d5d94a291524d..e2982a8ac5448110378bc92247952332bdffe12c 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -12,6 +12,8 @@ import java.io.BufferedReader; + import java.io.BufferedWriter; + import java.io.File; + import java.io.IOException; ++import java.lang.reflect.ParameterizedType; // Paper ++import java.lang.reflect.Type; // Paper + import java.nio.charset.StandardCharsets; + import java.util.Collection; + import java.util.Iterator; +@@ -28,7 +30,22 @@ public abstract class StoredUserList> { + protected static final Logger LOGGER = LogManager.getLogger(); + private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); + private final File file; +- private final Map map = Maps.newHashMap(); ++ // Paper - replace HashMap is ConcurrentHashMap ++ private final Map map = Maps.newConcurrentMap(); private final Map getBackingMap() { return this.map; } // Paper - OBFHELPER ++ private boolean e = true; ++ private static final ParameterizedType f = new ParameterizedType() { ++ public Type[] getActualTypeArguments() { ++ return new Type[]{StoredUserEntry.class}; ++ } ++ ++ public Type getRawType() { ++ return List.class; ++ } ++ ++ public Type getOwnerType() { ++ return null; ++ } ++ }; + + public StoredUserList(File file) { + this.file = file; +@@ -51,8 +68,13 @@ public abstract class StoredUserList> { + + @Nullable + public V get(K key) { +- this.removeExpired(); +- return (V) this.map.get(this.getKeyForUser(key)); // CraftBukkit - fix decompile error ++ // Paper start ++ // this.g(); ++ // return (V) this.d.get(this.a(k0)); // CraftBukkit - fix decompile error ++ return (V) this.getBackingMap().computeIfPresent(this.getMappingKey(key), (k, v) -> { ++ return v.hasExpired() ? null : v; ++ }); ++ // Paper end + } + + public void remove(K key) { +@@ -81,9 +103,11 @@ public abstract class StoredUserList> { + // CraftBukkit end + + public boolean isEmpty() { +- return this.map.size() < 1; ++ // return this.d.size() < 1; // Paper ++ return this.getBackingMap().isEmpty(); // Paper - readability is the goal. As an aside, isEmpty() uses only sumCount() and a comparison. size() uses sumCount(), casts, and boolean logic + } + ++ protected final String getMappingKey(K k0) { return getKeyForUser(k0); } // Paper - OBFHELPER + protected String getKeyForUser(K profile) { + return profile.toString(); + } +@@ -92,15 +116,16 @@ public abstract class StoredUserList> { + return this.map.containsKey(this.getKeyForUser(k0)); + } + ++ private void removeStaleEntries() { removeExpired(); } // Paper - OBFHELPER + private void removeExpired() { +- List list = Lists.newArrayList(); +- Iterator iterator = this.map.values().iterator(); ++ /*List list = Lists.newArrayList(); ++ Iterator iterator = this.d.values().iterator(); + + while (iterator.hasNext()) { + V v0 = (V) iterator.next(); // CraftBukkit - decompile error + + if (v0.hasExpired()) { +- list.add(v0.getUser()); ++ list.add(v0.getKey()); + } + } + +@@ -109,9 +134,11 @@ public abstract class StoredUserList> { + while (iterator.hasNext()) { + K k0 = (K) iterator.next(); // CraftBukkit - decompile error + +- this.map.remove(this.getKeyForUser(k0)); +- } ++ this.d.remove(this.a(k0)); ++ }*/ + ++ this.getBackingMap().values().removeIf(StoredUserEntry::hasExpired); ++ // Paper end + } + + protected abstract StoredUserEntry createEntry(JsonObject json); +@@ -121,6 +148,7 @@ public abstract class StoredUserList> { + } + + public void save() throws IOException { ++ this.removeStaleEntries(); // Paper - remove expired values before saving + JsonArray jsonarray = new JsonArray(); + + this.map.values().stream().map((jsonlistentry) -> { diff --git a/Remapped-Spigot-Server-Patches/0264-Use-a-Queue-for-Queueing-Commands.patch b/Remapped-Spigot-Server-Patches/0264-Use-a-Queue-for-Queueing-Commands.patch new file mode 100644 index 000000000..b220fa4eb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0264-Use-a-Queue-for-Queueing-Commands.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 12 Aug 2018 02:33:39 -0400 +Subject: [PATCH] Use a Queue for Queueing Commands + +Lists are bad as Queues mmmkay. + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 9143fe99e43236bf65e6f098a30d522302ad78b7..4862a9519d4ba5f05b634a0335837bea9812edee 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -72,7 +72,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + private static final Logger LOGGER = LogManager.getLogger(); + private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$"); +- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); ++ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - use a proper queue + private QueryThreadGs4 queryThreadGs4; + public final RconConsoleSource rconConsoleSource; + private RconThread rconThread; +@@ -426,13 +426,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + public void handleConsoleInput(String command, CommandSourceStack commandSource) { +- this.consoleInput.add(new ConsoleInput(command, commandSource)); ++ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); + } + + public void handleConsoleInputs() { + MinecraftTimings.serverCommandTimer.startTiming(); // Spigot +- while (!this.consoleInput.isEmpty()) { +- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); ++ // Paper start - use proper queue ++ ConsoleInput servercommand; ++ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ // Paper end + + // CraftBukkit start - ServerCommand for preprocessing + ServerCommandEvent event = new ServerCommandEvent(console, servercommand.msg); diff --git a/Remapped-Spigot-Server-Patches/0265-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch b/Remapped-Spigot-Server-Patches/0265-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch new file mode 100644 index 000000000..52dbc8ee7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0265-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 15 Aug 2018 01:16:34 -0400 +Subject: [PATCH] Ability to get Tile Entities from a chunk without snapshots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 93b9e9d5932764f7e946dd3f8ab8191189c5d38f..423594177fe78600755d913f169f28dd1bfa2b37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -3,8 +3,10 @@ package org.bukkit.craftbukkit; + import com.google.common.base.Preconditions; + import com.google.common.base.Predicates; + import java.lang.ref.WeakReference; ++import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; ++import java.util.List; + import java.util.function.Predicate; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Registry; +@@ -129,9 +131,16 @@ public class CraftChunk implements Chunk { + + @Override + public BlockState[] getTileEntities() { ++ // Paper start ++ return getTileEntities(true); ++ } ++ ++ @Override ++ public BlockState[] getTileEntities(boolean useSnapshot) { + if (!isLoaded()) { + getWorld().getChunkAt(x, z); // Transient load for this tick + } ++ // Paper end + int index = 0; + net.minecraft.world.level.chunk.LevelChunk chunk = getHandle(); + +@@ -143,11 +152,33 @@ public class CraftChunk implements Chunk { + } + + BlockPos position = (BlockPos) obj; +- entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); ++ entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); // Paper ++ } ++ ++ return entities; ++ } ++ ++ // Paper start ++ @Override ++ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { ++ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); ++ if (!isLoaded()) { ++ getWorld().getChunkAt(x, z); // Transient load for this tick ++ } ++ net.minecraft.world.level.chunk.LevelChunk chunk = getHandle(); ++ ++ List entities = new ArrayList<>(); ++ ++ for (BlockPos position : chunk.blockEntities.keySet()) { ++ Block block = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); ++ if (blockPredicate.test(block)) { ++ entities.add(block.getState(useSnapshot)); ++ } + } + + return entities; + } ++ // Paper end + + @Override + public boolean isLoaded() { diff --git a/Remapped-Spigot-Server-Patches/0266-Allow-disabling-armour-stand-ticking.patch b/Remapped-Spigot-Server-Patches/0266-Allow-disabling-armour-stand-ticking.patch new file mode 100644 index 000000000..b471e98ef --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0266-Allow-disabling-armour-stand-ticking.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 15 Aug 2018 01:26:09 -0700 +Subject: [PATCH] Allow disabling armour stand ticking + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3562950df4868b1393790b1a1ff1fe0dc589c155..5ab0e7183e48134b7a0f736462516b1a8a333b04 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -384,4 +384,10 @@ public class PaperWorldConfig { + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); + } ++ ++ public boolean armorStandTick = true; ++ private void armorStandTick() { ++ this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); ++ log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 06e52a0c5decf717e35c605d6bcb46c3c7b29656..2994eee1d381af2c9ff3649dd48a2ae14c38c9d7 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -77,9 +77,16 @@ public class ArmorStand extends LivingEntity { + public Rotations leftLegPose; + public Rotations rightLegPose; + public boolean canMove = true; // Paper ++ // Paper start - Allow ArmorStands not to tick ++ public boolean canTick = true; ++ public boolean canTickSetByAPI = false; ++ private boolean noTickPoseDirty = false; ++ private boolean noTickEquipmentDirty = false; ++ // Paper end + + public ArmorStand(EntityType type, Level world) { + super(type, world); ++ if (world != null) this.canTick = world.paperConfig.armorStandTick; // Paper - armour stand ticking + this.handItems = NonNullList.a(2, ItemStack.EMPTY); + this.armorItems = NonNullList.a(4, ItemStack.EMPTY); + this.headPose = ArmorStand.DEFAULT_HEAD_POSE; +@@ -175,6 +182,7 @@ public class ArmorStand extends LivingEntity { + this.armorItems.set(enumitemslot.getIndex(), itemstack); + } + ++ this.noTickEquipmentDirty = true; // Paper - Allow equipment to be updated even when tick disabled + } + + @Override +@@ -255,6 +263,7 @@ public class ArmorStand extends LivingEntity { + } + + tag.put("Pose", this.writePose()); ++ if (this.canTickSetByAPI) tag.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - persist no tick setting + } + + @Override +@@ -286,6 +295,12 @@ public class ArmorStand extends LivingEntity { + this.setNoBasePlate(tag.getBoolean("NoBasePlate")); + this.setMarker(tag.getBoolean("Marker")); + this.noPhysics = !this.hasPhysics(); ++ // Paper start - persist no tick ++ if (tag.contains("Paper.CanTickOverride")) { ++ this.canTick = tag.getBoolean("Paper.CanTickOverride"); ++ this.canTickSetByAPI = true; ++ } ++ // Paper end + CompoundTag nbttagcompound1 = tag.getCompound("Pose"); + + this.readPose(nbttagcompound1); +@@ -641,7 +656,29 @@ public class ArmorStand extends LivingEntity { + + @Override + public void tick() { ++ // Paper start ++ if (!this.canTick) { ++ if (this.noTickPoseDirty) { ++ this.noTickPoseDirty = false; ++ this.updatePose(); ++ } ++ ++ if (this.noTickEquipmentDirty) { ++ this.noTickEquipmentDirty = false; ++ this.detectEquipmentUpdates(); ++ } ++ ++ return; ++ } ++ // Paper end ++ + super.tick(); ++ // Paper start - Split into separate method ++ updatePose(); ++ } ++ ++ public void updatePose() { ++ // Paper end + Rotations vector3f = (Rotations) this.entityData.get(ArmorStand.DATA_HEAD_POSE); + + if (!this.headPose.equals(vector3f)) { +@@ -764,29 +801,36 @@ public class ArmorStand extends LivingEntity { + public void setHeadPose(Rotations vector3f) { + this.headPose = vector3f; + this.entityData.set(ArmorStand.DATA_HEAD_POSE, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setBodyPose(Rotations vector3f) { + this.bodyPose = vector3f; + this.entityData.set(ArmorStand.DATA_BODY_POSE, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftArmPose(Rotations vector3f) { + this.leftArmPose = vector3f; + this.entityData.set(ArmorStand.DATA_LEFT_ARM_POSE, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightArmPose(Rotations vector3f) { + this.rightArmPose = vector3f; + this.entityData.set(ArmorStand.DATA_RIGHT_ARM_POSE, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftLegPose(Rotations vector3f) { ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + this.leftLegPose = vector3f; + this.entityData.set(ArmorStand.DATA_LEFT_LEG_POSE, vector3f); ++ + } + + public void setRightLegPose(Rotations vector3f) { ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + this.rightLegPose = vector3f; + this.entityData.set(ArmorStand.DATA_RIGHT_LEG_POSE, vector3f); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index d4da5214a39b718671dcaf687cb0ff8668ce9728..8363b1b2267da30cda2fb8ea4e844598e20e1422 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -311,5 +311,16 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { + return getHandle().isSlotDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); + } ++ ++ @Override ++ public boolean canTick() { ++ return this.getHandle().canTick; ++ } ++ ++ @Override ++ public void setCanTick(final boolean tick) { ++ this.getHandle().canTick = tick; ++ this.getHandle().canTickSetByAPI = true; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0267-Optimize-BlockPosition-helper-methods.patch b/Remapped-Spigot-Server-Patches/0267-Optimize-BlockPosition-helper-methods.patch new file mode 100644 index 000000000..fa154eb15 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0267-Optimize-BlockPosition-helper-methods.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Aug 2018 12:05:12 -0700 +Subject: [PATCH] Optimize BlockPosition helper methods + +Resolves #1338 + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index c5089b0da33a68e7cadbc4841b07f9d772d224a0..b13e5d05d862ea8c6031b8071f525f00bc48f7e7 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -121,58 +121,75 @@ public class BlockPos extends Vec3i { + + @Override + public BlockPos above() { +- return this.relative(Direction.UP); ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPos above(int distance) { +- return this.relative(Direction.UP, distance); ++ return distance == 0 ? this : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPos below() { +- return this.relative(Direction.DOWN); ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPos below(int distance) { +- return this.relative(Direction.DOWN, distance); ++ return distance == 0 ? this : new BlockPos(this.getX(), this.getY() - distance, this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPos north() { +- return this.relative(Direction.NORTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); // Paper - Optimize BlockPosition + } + + public BlockPos north(int distance) { +- return this.relative(Direction.NORTH, distance); ++ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Optimize BlockPosition + } + + public BlockPos south() { +- return this.relative(Direction.SOUTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); // Paper - Optimize BlockPosition + } + + public BlockPos south(int distance) { +- return this.relative(Direction.SOUTH, distance); ++ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Optimize BlockPosition + } + + public BlockPos west() { +- return this.relative(Direction.WEST); ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPos west(int distance) { +- return this.relative(Direction.WEST, distance); ++ return distance == 0 ? this : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPos east() { +- return this.relative(Direction.EAST); ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPos east(int distance) { +- return this.relative(Direction.EAST, distance); ++ return distance == 0 ? this : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPos relative(Direction direction) { +- return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); ++ // Paper Start - Optimize BlockPosition ++ switch(direction) { ++ case UP: ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); ++ case DOWN: ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); ++ case NORTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); ++ case SOUTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); ++ case WEST: ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); ++ case EAST: ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); ++ default: ++ return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); ++ } ++ // Paper End + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0268-Restore-vanlla-default-mob-spawn-range-and-water-ani.patch b/Remapped-Spigot-Server-Patches/0268-Restore-vanlla-default-mob-spawn-range-and-water-ani.patch new file mode 100644 index 000000000..50cfc368a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0268-Restore-vanlla-default-mob-spawn-range-and-water-ani.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Aug 2018 12:43:16 -0400 +Subject: [PATCH] Restore vanlla default mob-spawn-range and water animals + limit + + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 0efcbab8f8806aeb8dd8bd6384e5a7cee375d100..34ee684901906fc2ef5f0d09680d2686b813e52b 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -165,7 +165,7 @@ public class SpigotWorldConfig + public byte mobSpawnRange; + private void mobSpawnRange() + { +- mobSpawnRange = (byte) getInt( "mob-spawn-range", 6 ); ++ mobSpawnRange = (byte) getInt( "mob-spawn-range", 8 ); // Paper - Vanilla + log( "Mob Spawn Range: " + mobSpawnRange ); + } + +diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml +index 6474a7fb738e1238cc272afc5ff14b645947e688..6d71bd0db752e6f523364ca5351579b6bcb434c8 100644 +--- a/src/main/resources/configurations/bukkit.yml ++++ b/src/main/resources/configurations/bukkit.yml +@@ -26,7 +26,7 @@ settings: + spawn-limits: + monsters: 70 + animals: 10 +- water-animals: 15 ++ water-animals: 5 + water-ambient: 20 + ambient: 15 + chunk-gc: diff --git a/Remapped-Spigot-Server-Patches/0269-Slime-Pathfinder-Events.patch b/Remapped-Spigot-Server-Patches/0269-Slime-Pathfinder-Events.patch new file mode 100644 index 000000000..115607482 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0269-Slime-Pathfinder-Events.patch @@ -0,0 +1,229 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 08:18:42 -0500 +Subject: [PATCH] Slime Pathfinder Events + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index fc8f26e988f1e4826dcfdcf071293bb356163e62..120ceb28ee3aee8a09cf67b45ac95d3d6613c133 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -24,7 +24,6 @@ import net.minecraft.world.effect.MobEffects; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityDimensions; + import net.minecraft.world.entity.EntityType; +-import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.MobSpawnType; + import net.minecraft.world.entity.Pose; +@@ -43,6 +42,13 @@ import net.minecraft.world.level.WorldGenLevel; + import net.minecraft.world.level.biome.Biomes; + import net.minecraft.world.level.levelgen.WorldgenRandom; + import net.minecraft.world.phys.Vec3; ++// Paper start ++import com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent; ++import com.destroystokyo.paper.event.entity.SlimeSwimEvent; ++import com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent; ++import com.destroystokyo.paper.event.entity.SlimeWanderEvent; ++import org.bukkit.entity.LivingEntity; ++// Paper end + // CraftBukkit start + import java.util.ArrayList; + import java.util.List; +@@ -103,6 +109,7 @@ public class Slime extends Mob implements Enemy { + @Override + public void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); ++ tag.putBoolean("Paper.canWander", this.canWander); // Paper + tag.putInt("Size", this.getSize() - 1); + tag.putBoolean("wasOnGround", this.wasOnGround); + } +@@ -117,6 +124,11 @@ public class Slime extends Mob implements Enemy { + + this.setSize(i + 1, false); + super.readAdditionalSaveData(tag); ++ // Paper start - check exists before loading or this will be loaded as false ++ if (tag.contains("Paper.canWander")) { ++ this.canWander = tag.getBoolean("Paper.canWander"); ++ } ++ // Paper end + this.wasOnGround = tag.getBoolean("wasOnGround"); + } + +@@ -218,7 +230,7 @@ public class Slime extends Mob implements Enemy { + super.remove(); + return; + } +- List slimes = new ArrayList<>(j); ++ List slimes = new ArrayList<>(j); + // CraftBukkit end + + for (int l = 0; l < k; ++l) { +@@ -242,7 +254,7 @@ public class Slime extends Mob implements Enemy { + if (CraftEventFactory.callEntityTransformEvent(this, slimes, EntityTransformEvent.TransformReason.SPLIT).isCancelled()) { + return; + } +- for (LivingEntity living : slimes) { ++ for (net.minecraft.world.entity.LivingEntity living : slimes) { + this.level.addEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason + } + // CraftBukkit end +@@ -255,7 +267,7 @@ public class Slime extends Mob implements Enemy { + public void push(Entity entity) { + super.push(entity); + if (entity instanceof IronGolem && this.isDealsDamage()) { +- this.dealDamage((LivingEntity) entity); ++ this.dealDamage((net.minecraft.world.entity.LivingEntity) entity); + } + + } +@@ -263,18 +275,18 @@ public class Slime extends Mob implements Enemy { + @Override + public void playerTouch(Player player) { + if (this.isDealsDamage()) { +- this.dealDamage((LivingEntity) player); ++ this.dealDamage((net.minecraft.world.entity.LivingEntity) player); + } + + } + +- protected void dealDamage(LivingEntity target) { ++ protected void dealDamage(net.minecraft.world.entity.LivingEntity target) { + if (this.isAlive()) { + int i = this.getSize(); + + if (this.distanceToSqr((Entity) target) < 0.6D * (double) i * 0.6D * (double) i && this.canSee(target) && target.hurt(DamageSource.mobAttack(this), this.getAttackDamage())) { + this.playSound(SoundEvents.SLIME_ATTACK, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); +- this.doEnchantDamageEffects((LivingEntity) this, (Entity) target); ++ this.doEnchantDamageEffects((net.minecraft.world.entity.LivingEntity) this, (Entity) target); + } + } + +@@ -396,7 +408,7 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return !this.slime.isPassenger(); ++ return !this.slime.isPassenger() && this.slime.canWander && new SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper + } + + @Override +@@ -417,7 +429,7 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper + } + + @Override +@@ -443,14 +455,18 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return this.slime.getTarget() == null && (this.slime.onGround || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return this.slime.getTarget() == null && (this.slime.onGround || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - add canWander + } + + @Override + public void tick() { + if (--this.nextRandomizeTime <= 0) { + this.nextRandomizeTime = 40 + this.slime.getRandom().nextInt(60); +- this.chosenDegrees = (float) this.slime.getRandom().nextInt(360); ++ // Paper start ++ SlimeChangeDirectionEvent event = new SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (float) this.slime.getRandom().nextInt(360)); ++ if (!this.slime.canWander || !event.callEvent()) return; ++ this.chosenDegrees = event.getNewYaw(); ++ // Paper end + } + + ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setDirection(this.chosenDegrees, false); +@@ -469,9 +485,17 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- LivingEntity entityliving = this.slime.getTarget(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!entityliving.isAlive() ? false : (entityliving instanceof Player && ((Player) entityliving).abilities.invulnerable ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl)); ++ // Paper start ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (entityliving instanceof Player && ((Player) entityliving).abilities.invulnerable) { ++ return false; ++ } ++ return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end + } + + @Override +@@ -482,9 +506,17 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canContinueToUse() { +- LivingEntity entityliving = this.slime.getTarget(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!entityliving.isAlive() ? false : (entityliving instanceof Player && ((Player) entityliving).abilities.invulnerable ? false : --this.growTiredTimer > 0)); ++ // Paper start ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (entityliving instanceof Player && ((Player) entityliving).abilities.invulnerable) { ++ return false; ++ } ++ return --this.growTiredTimer > 0 && this.slime.canWander && new SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end + } + + @Override +@@ -492,6 +524,13 @@ public class Slime extends Mob implements Enemy { + this.slime.lookAt((Entity) this.slime.getTarget(), 10.0F, 10.0F); + ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setDirection(this.slime.yRot, this.slime.isDealsDamage()); + } ++ ++ // Paper start - clear timer and target when goal resets ++ public void stop() { ++ this.growTiredTimer = 0; ++ this.slime.setTarget(null); ++ } ++ // Paper end + } + + static class SlimeMoveControl extends MoveControl { +@@ -550,4 +589,15 @@ public class Slime extends Mob implements Enemy { + } + } + } ++ ++ // Paper start ++ private boolean canWander = true; ++ public boolean canWander() { ++ return canWander; ++ } ++ ++ public void setWander(boolean canWander) { ++ this.canWander = canWander; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index 93af0fd9e15954d7d9f28d7dc29ee18055908348..340036135588d06e43cbd229dd3a6613b04bb9ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -34,4 +34,14 @@ public class CraftSlime extends CraftMob implements Slime { + public EntityType getType() { + return EntityType.SLIME; + } ++ ++ // Paper start ++ public boolean canWander() { ++ return getHandle().canWander(); ++ } ++ ++ public void setWander(boolean canWander) { ++ getHandle().setWander(canWander); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0270-Configurable-speed-for-water-flowing-over-lava.patch b/Remapped-Spigot-Server-Patches/0270-Configurable-speed-for-water-flowing-over-lava.patch new file mode 100644 index 000000000..c8fef8a86 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0270-Configurable-speed-for-water-flowing-over-lava.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 8 Aug 2018 16:33:21 -0600 +Subject: [PATCH] Configurable speed for water flowing over lava + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5ab0e7183e48134b7a0f736462516b1a8a333b04..f280dbff4a09bc611a9ca565c6d697d08801f53b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -390,4 +390,10 @@ public class PaperWorldConfig { + this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); + log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); + } ++ ++ public int waterOverLavaFlowSpeed; ++ private void waterOverLavaFlowSpeed() { ++ waterOverLavaFlowSpeed = getInt("water-over-lava-flow-speed", 5); ++ log("Water over lava flow speed: " + waterOverLavaFlowSpeed); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +index f99b90e4b3210747077f2bf3adbcf7b5fb9821ec..97f2d9082e49010fb8780c5fdd8957f71b31e43e 100644 +--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +@@ -23,6 +23,7 @@ import net.minecraft.world.level.material.FlowingFluid; + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.material.Fluids; ++import net.minecraft.world.level.material.Material; + import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.level.storage.loot.LootContext; + import net.minecraft.world.phys.shapes.CollisionContext; +@@ -100,11 +101,28 @@ public class LiquidBlock extends Block implements BucketPickup { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.getLiquidTicks().a(pos, state.getFluidState().getType(), this.fluid.getTickDelay((LevelReader) world)); ++ world.getLiquidTicks().a(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper + } + + } + ++ // Paper start - Get flow speed. Throttle if its water and flowing adjacent to lava ++ public int getFlowSpeed(Level world, BlockPos blockposition) { ++ if (this.material == Material.WATER) { ++ if ( ++ world.getMaterialIfLoaded(blockposition.north(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.south(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.west(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.east(1)) == Material.LAVA ++ ) { ++ return world.paperConfig.waterOverLavaFlowSpeed; ++ } ++ } ++ return this.fluid.getTickDelay(world); ++ } ++ // Paper end ++ ++ + @Override + public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor world, BlockPos pos, BlockPos posFrom) { + if (state.getFluidState().isSource() || newState.getFluidState().isSource()) { +@@ -117,7 +135,7 @@ public class LiquidBlock extends Block implements BucketPickup { + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.getLiquidTicks().a(pos, state.getFluidState().getType(), this.fluid.getTickDelay((LevelReader) world)); ++ world.getLiquidTicks().a(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper + } + + } diff --git a/Remapped-Spigot-Server-Patches/0271-Optimize-CraftBlockData-Creation.patch b/Remapped-Spigot-Server-Patches/0271-Optimize-CraftBlockData-Creation.patch new file mode 100644 index 000000000..b9e1c45f8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0271-Optimize-CraftBlockData-Creation.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 23 Aug 2018 11:45:32 -0400 +Subject: [PATCH] Optimize CraftBlockData Creation + +Avoids a hashmap lookup by cacheing a reference to the CraftBlockData +and cloning it when one is needed. + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 5d7794c9533bd37193f196bda616adaaace2bbde..57eedaeedaa24bd274fb55c6e4521f1305382645 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -374,6 +374,14 @@ public abstract class BlockBehaviour { + this.hasPostProcess = blockbase_info.hasPostProcess; + this.emissiveRendering = blockbase_info.emissiveRendering; + } ++ // Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time ++ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; ++ ++ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { ++ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(getBlockData()); ++ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); ++ } ++ // Paper end + + public void initCache() { + if (!this.getBlock().hasDynamicShape()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 3a90b504ebbe86350f5fee5baa818e40d884d24f..0b6c6dfc380cea87bd88c3eb8a199e072dcbf56c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -509,7 +509,17 @@ public class CraftBlockData implements BlockData { + return craft; + } + ++ // Paper start - optimize creating BlockData to not need a map lookup ++ static { ++ // Initialize cached data for all IBlockData instances after registration ++ Block.BLOCK_STATE_REGISTRY.iterator().forEachRemaining(BlockState::createCraftBlockData); ++ } + public static CraftBlockData fromData(BlockState data) { ++ return data.createCraftBlockData(); ++ } ++ ++ public static CraftBlockData createData(BlockState data) { ++ // Paper end + return MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); + } + diff --git a/Remapped-Spigot-Server-Patches/0272-Optimize-RegistryMaterials.patch b/Remapped-Spigot-Server-Patches/0272-Optimize-RegistryMaterials.patch new file mode 100644 index 000000000..9ee9585d1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0272-Optimize-RegistryMaterials.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 26 Aug 2018 20:49:50 -0400 +Subject: [PATCH] Optimize RegistryMaterials + +Use larger initial sizes to increase bucket capacity on the BiMap + +BiMap.get was seen to be using a good bit of CPU time. + +diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java +index 928d4b980a1f703915454ffb304dc329fa4223df..da3733db4a5817673703f6c0cf37b5ee3bf91a99 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -30,6 +30,7 @@ import net.minecraft.Util; + import net.minecraft.resources.RegistryDataPackCodec; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; // Paper + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -38,7 +39,7 @@ public class MappedRegistry extends WritableRegistry { + + protected static final Logger LOGGER = LogManager.getLogger(); + private final ObjectList byId = new ObjectArrayList(256); +- private final Object2IntMap toId = new Object2IntOpenCustomHashMap(Util.identityStrategy()); ++ private final Reference2IntOpenHashMap bg = new Reference2IntOpenHashMap(2048);// Paper - use bigger expected size to reduce collisions and direct intent for FastUtil to be identity map + private final BiMap storage; + private final BiMap, T> keyStorage; + private final Map lifecycles; +@@ -48,10 +49,10 @@ public class MappedRegistry extends WritableRegistry { + + public MappedRegistry(ResourceKey> key, Lifecycle lifecycle) { + super(key, lifecycle); +- this.toId.defaultReturnValue(-1); +- this.storage = HashBiMap.create(); +- this.keyStorage = HashBiMap.create(); +- this.lifecycles = Maps.newIdentityHashMap(); ++ this.bg.defaultReturnValue(-1); ++ this.storage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions ++ this.keyStorage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions ++ this.lifecycles = new java.util.IdentityHashMap<>(2048); // Paper - use bigger expected size to reduce collisions + this.elementsLifecycle = lifecycle; + } + +@@ -77,7 +78,7 @@ public class MappedRegistry extends WritableRegistry { + Validate.notNull(entry); + this.byId.size(Math.max(this.byId.size(), rawId + 1)); + this.byId.set(rawId, entry); +- this.toId.put(entry, rawId); ++ this.bg.put(entry, rawId); + this.randomCache = null; + if (checkDuplicateKeys && this.keyStorage.containsKey(key)) { + MappedRegistry.LOGGER.debug("Adding duplicate key '{}' to registry", key); +@@ -113,12 +114,12 @@ public class MappedRegistry extends WritableRegistry { + if (t0 == null) { + i = rawId.isPresent() ? rawId.getAsInt() : this.nextId; + } else { +- i = this.toId.getInt(t0); ++ i = this.bg.getInt(t0); + if (rawId.isPresent() && rawId.getAsInt() != i) { + throw new IllegalStateException("ID mismatch"); + } + +- this.toId.removeInt(t0); ++ this.bg.removeInt(t0); + this.lifecycles.remove(t0); + } + +@@ -138,7 +139,7 @@ public class MappedRegistry extends WritableRegistry { + + @Override + public int getId(@Nullable T entry) { +- return this.toId.getInt(entry); ++ return this.bg.getInt(entry); + } + + @Nullable +@@ -195,7 +196,7 @@ public class MappedRegistry extends WritableRegistry { + this.randomCache = collection.toArray(new Object[collection.size()]); + } + +- return Util.getRandom(this.randomCache, random); ++ return (T) Util.getRandom(this.randomCache, random); // Paper - Decompile fix + } + + public static Codec> networkCodec(ResourceKey> resourcekey, Lifecycle lifecycle, Codec entryCodec) { +@@ -215,7 +216,7 @@ public class MappedRegistry extends WritableRegistry { + Iterator iterator = registrymaterials.iterator(); + + while (iterator.hasNext()) { +- T t0 = iterator.next(); ++ T t0 = (T) iterator.next(); // Paper - Decompile fix + + builder.add(new MappedRegistry.RegistryEntry<>((ResourceKey) registrymaterials.getResourceKey(t0).get(), registrymaterials.getId(t0), t0)); + } diff --git a/Remapped-Spigot-Server-Patches/0273-Add-PhantomPreSpawnEvent.patch b/Remapped-Spigot-Server-Patches/0273-Add-PhantomPreSpawnEvent.patch new file mode 100644 index 000000000..a3b507be8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0273-Add-PhantomPreSpawnEvent.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 25 Aug 2018 19:56:51 -0500 +Subject: [PATCH] Add PhantomPreSpawnEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index 4a2fecdfbda34d6360d50e2ac017907a62b4a043..e37137a2890330b92e05d6f76c46ffc99a527803 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -161,6 +161,11 @@ public class Phantom extends FlyingMob implements Enemy { + } + + this.setPhantomSize(tag.getInt("Size")); ++ // Paper start ++ if (tag.hasUUID("Paper.SpawningEntity")) { ++ this.spawningEntity = tag.getUUID("Paper.SpawningEntity"); ++ } ++ // Paper end + } + + @Override +@@ -170,6 +175,11 @@ public class Phantom extends FlyingMob implements Enemy { + tag.putInt("AY", this.anchorPoint.getY()); + tag.putInt("AZ", this.anchorPoint.getZ()); + tag.putInt("Size", this.getPhantomSize()); ++ // Paper start ++ if (this.spawningEntity != null) { ++ tag.setUUID("Paper.SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -216,6 +226,15 @@ public class Phantom extends FlyingMob implements Enemy { + return entitysize.scale(f); + } + ++ // Paper start ++ java.util.UUID spawningEntity; ++ ++ public java.util.UUID getSpawningEntity() { ++ return spawningEntity; ++ } ++ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ // Paper end ++ + class PhantomAttackPlayerTargetGoal extends Goal { + + private final TargetingConditions attackTargeting; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index ee18e2826f6390ef5a29557b733fd00db5bc409f..42effcbd3ca7c38a4e8b1aa835543ad243112a33 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -4,6 +4,7 @@ import java.util.Iterator; + import java.util.Random; + import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.stats.ServerStatsCounter; +@@ -73,8 +74,17 @@ public class PhantomSpawner implements CustomSpawner { + int k = 1 + random.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + + for (int l = 0; l < k; ++l) { ++ // Paper start ++ com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(MCUtil.toLocation(world, blockposition1), ((ServerPlayer) entityhuman).getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ // Paper end + Phantom entityphantom = (Phantom) EntityType.PHANTOM.create((Level) world); +- ++ entityphantom.setSpawningEntity(entityhuman.getUUID()); // Paper + entityphantom.moveTo(blockposition1, 0.0F, 0.0F); + groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null); + world.addAllEntities(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 15eb09595dad996314c169f9dd7d381e43f77be9..92162fa22f5e98b7837bde5830bd47c31b8b52d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -34,4 +34,10 @@ public class CraftPhantom extends CraftFlying implements Phantom { + public EntityType getType() { + return EntityType.PHANTOM; + } ++ ++ // Paper start ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().getSpawningEntity(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0274-Add-More-Creeper-API.patch b/Remapped-Spigot-Server-Patches/0274-Add-More-Creeper-API.patch new file mode 100644 index 000000000..3a4091fe8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0274-Add-More-Creeper-API.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 11:50:26 -0500 +Subject: [PATCH] Add More Creeper API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index d9b5cf8ac01289801ded01d928fa7ead96551be5..336736fae0b49a05e48c1c70a225da316bb73e66 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -289,7 +289,18 @@ public class Creeper extends Monster { + } + + public void ignite() { +- this.entityData.set(Creeper.DATA_IS_IGNITED, true); ++ // Paper start ++ setIgnited(true); ++ } ++ ++ public void setIgnited(boolean ignited) { ++ if (isIgnited() != ignited) { ++ com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); ++ if (event.callEvent()) { ++ this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); ++ } ++ } ++ // Paper end + } + + public boolean canDropMobsSkull() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +index ded0b7c1619aada95492e7ec25c0e0f3d008d0ad..9f68beb8c79ed1c429ee9f9efab8b8604258293b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +@@ -100,4 +100,14 @@ public class CraftCreeper extends CraftMonster implements Creeper { + public EntityType getType() { + return EntityType.CREEPER; + } ++ ++ // Paper start ++ public void setIgnited(boolean ignited) { ++ getHandle().setIgnited(ignited); ++ } ++ ++ public boolean isIgnited() { ++ return getHandle().isIgnited(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0275-Inventory-removeItemAnySlot.patch b/Remapped-Spigot-Server-Patches/0275-Inventory-removeItemAnySlot.patch new file mode 100644 index 000000000..b542a82e8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0275-Inventory-removeItemAnySlot.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 28 Aug 2018 23:04:15 -0400 +Subject: [PATCH] Inventory#removeItemAnySlot + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index 9d70ef8c3967596491a804e4d66f2ec1b13992c9..ef2d18d19a86b3701855aa1ac126462e663f8fcd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -222,10 +222,16 @@ public class CraftInventory implements Inventory { + } + + private int first(ItemStack item, boolean withAmount) { ++ // Paper start ++ return first(item, withAmount, getStorageContents()); ++ } ++ ++ private int first(ItemStack item, boolean withAmount, ItemStack[] inventory) { ++ // Paper end + if (item == null) { + return -1; + } +- ItemStack[] inventory = getStorageContents(); ++ //ItemStack[] inventory = getStorageContents(); // Paper - let param deal + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null) continue; + +@@ -348,6 +354,17 @@ public class CraftInventory implements Inventory { + + @Override + public HashMap removeItem(ItemStack... items) { ++ // Paper start ++ return removeItem(false, items); ++ } ++ ++ @Override ++ public HashMap removeItemAnySlot(ItemStack... items) { ++ return removeItem(true, items); ++ } ++ ++ private HashMap removeItem(boolean searchEntire, ItemStack... items) { ++ // Paper end + Validate.notNull(items, "Items cannot be null"); + HashMap leftover = new HashMap(); + +@@ -358,7 +375,10 @@ public class CraftInventory implements Inventory { + int toDelete = item.getAmount(); + + while (true) { +- int first = first(item, false); ++ // Paper start - Allow searching entire contents ++ ItemStack[] toSearch = searchEntire ? getContents() : getStorageContents(); ++ int first = first(item, false, toSearch); ++ // Paper end + + // Drat! we don't have this type in the inventory + if (first == -1) { diff --git a/Remapped-Spigot-Server-Patches/0276-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/Remapped-Spigot-Server-Patches/0276-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch new file mode 100644 index 000000000..3b436ea5a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0276-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Sep 2018 19:34:33 -0700 +Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted + chunks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 159c32d6678e83f2d98ea6a1ad48346c9de017e1..57a2af56b53567371fdb6d0a55866e1e4e37cf3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -490,7 +490,7 @@ public class CraftWorld implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- ChunkAccess chunk = world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); ++ ChunkAccess chunk = world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + + // If generate = false, but the chunk already exists, we will get this back. + if (chunk instanceof ImposterProtoChunk) { diff --git a/Remapped-Spigot-Server-Patches/0277-Add-ray-tracing-methods-to-LivingEntity.patch b/Remapped-Spigot-Server-Patches/0277-Add-ray-tracing-methods-to-LivingEntity.patch new file mode 100644 index 000000000..f1edc6251 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0277-Add-ray-tracing-methods-to-LivingEntity.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 3 Sep 2018 18:20:03 -0500 +Subject: [PATCH] Add ray tracing methods to LivingEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 205c639d26652befebae925fc6e40976c370710f..2e25cb2a04d150d3154bf0d7f0eccb97e65ff53e 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3605,6 +3605,23 @@ public abstract class LivingEntity extends Entity { + this.broadcastBreakEvent(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND); + } + // Paper start ++ public HitResult getRayTrace(int maxDistance) { ++ return getRayTrace(maxDistance, ClipContext.Fluid.NONE); ++ } ++ ++ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); ++ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); ++ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); ++ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); ++ ++ return level.clip(raytrace); ++ } ++ + public int shieldBlockingDelay = level.paperConfig.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 6dd7a722e10a2727f68318b880f2726bb816f198..7e3a215f1592bed9f35e22076d9e35a5a49a430e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.block.TargetBlockInfo; + import com.google.common.base.Preconditions; + import com.google.common.collect.Sets; + import java.util.ArrayList; +@@ -8,6 +9,7 @@ import java.util.Iterator; + import java.util.List; + import java.util.Set; + import java.util.UUID; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.damagesource.DamageSource; +@@ -28,6 +30,8 @@ import net.minecraft.world.entity.projectile.ThrownEgg; + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.entity.projectile.ThrownExperienceBottle; + import net.minecraft.world.entity.projectile.ThrownTrident; ++import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.HitResult; + import org.apache.commons.lang.Validate; + import org.bukkit.FluidCollisionMode; + import org.bukkit.Location; +@@ -37,6 +41,7 @@ import org.bukkit.attribute.AttributeInstance; + import org.bukkit.block.Block; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.entity.memory.CraftMemoryKey; + import org.bukkit.craftbukkit.entity.memory.CraftMemoryMapper; + import org.bukkit.craftbukkit.inventory.CraftEntityEquipment; +@@ -190,6 +195,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return blocks.get(0); + } + ++ // Paper start ++ @Override ++ public Block getTargetBlock(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ HitResult rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof BlockHitResult) ? null : CraftBlock.at(getHandle().level, ((BlockHitResult)rayTrace).getBlockPos()); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ HitResult rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof BlockHitResult) ? null : MCUtil.toBukkitBlockFace(((BlockHitResult)rayTrace).getDirection()); ++ } ++ ++ @Override ++ public TargetBlockInfo getTargetBlockInfo(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ HitResult rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof BlockHitResult) ? null : ++ new TargetBlockInfo(CraftBlock.at(getHandle().level, ((BlockHitResult)rayTrace).getBlockPos()), ++ MCUtil.toBukkitBlockFace(((BlockHitResult)rayTrace).getDirection())); ++ } ++ // Paper end ++ + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return getLineOfSight(transparent, maxDistance, 2); diff --git a/Remapped-Spigot-Server-Patches/0278-Expose-attack-cooldown-methods-for-Player.patch b/Remapped-Spigot-Server-Patches/0278-Expose-attack-cooldown-methods-for-Player.patch new file mode 100644 index 000000000..bfe6717c6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0278-Expose-attack-cooldown-methods-for-Player.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 4 Sep 2018 15:02:00 -0500 +Subject: [PATCH] Expose attack cooldown methods for Player + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 709e930eef7bae5694238ed8c4d0ef59316bb715..14d5acff198338c68162e33d4a90f74be77cb15f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2100,6 +2100,7 @@ public abstract class Player extends LivingEntity { + this.entityData.set(Player.DATA_SHOULDER_RIGHT, entityTag); + } + ++ public float getCooldownPeriod() { return this.getCurrentItemAttackStrengthDelay(); } // Paper - OBFHELPER + public float getCurrentItemAttackStrengthDelay() { + return (float) (1.0D / this.getAttributeValue(Attributes.ATTACK_SPEED) * 20.0D); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index c7f66dddf0a0850ca4048dd47cd2ded114caa07e..ee823c4ed5b9fcfaa900b470c582435f0b909ebc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2186,6 +2186,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + connection.send(new net.minecraft.network.protocol.game.ClientboundOpenBookPacket(net.minecraft.world.InteractionHand.MAIN_HAND)); + connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(0, slot, inventory.getSelected())); + } ++ ++ public float getCooldownPeriod() { ++ return getHandle().getCooldownPeriod(); ++ } ++ ++ public float getCooledAttackStrength(float adjustTicks) { ++ return getHandle().getAttackStrengthScale(adjustTicks); ++ } ++ ++ public void resetCooldown() { ++ getHandle().resetAttackStrengthTicker(); ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0279-Improve-death-events.patch b/Remapped-Spigot-Server-Patches/0279-Improve-death-events.patch new file mode 100644 index 000000000..15db46e30 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0279-Improve-death-events.patch @@ -0,0 +1,458 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 21 Aug 2018 01:39:35 +0100 +Subject: [PATCH] Improve death events + +This adds the ability to cancel the death events and to modify the sound +an entity makes when dying. (In cases were no sound should it will be +called with shouldPlaySound set to false allowing unsilencing of silent +entities) + +It makes handling of entity deaths a lot nicer as you no longer need +to listen on the damage event and calculate if the entity dies yourself +to cancel the death which has the benefit of also receiving the dropped +items and experience which is otherwise only properly possible by using +internal code. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 5664e292046d4fcdb81340df8cee8d04aa27ca55..75419c866641ab654349cde6ca3fbdef701dd8d9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -211,6 +211,10 @@ public class ServerPlayer extends Player implements ContainerListener { + public int latency; + public boolean wonGame; + private int containerUpdateDelay; // Paper ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket = false; ++ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; ++ // Paper end + + // CraftBukkit start + public String displayName; +@@ -714,6 +718,15 @@ public class ServerPlayer extends Player implements ContainerListener { + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + + org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), defaultMessage.getString(), keepInventory); // Paper - Adventure ++ // Paper start - cancellable death event ++ if (event.isCancelled()) { ++ // make compatible with plugins that might have already set the health in an event listener ++ if (this.getHealth() <= 0) { ++ this.setHealth((float) event.getReviveHealth()); ++ } ++ return; ++ } ++ // Paper end + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +@@ -860,8 +873,17 @@ public class ServerPlayer extends Player implements ContainerListener { + } + } + } +- +- return super.hurt(source, amount); ++ // Paper start - cancellable death events ++ //return super.damageEntity(damagesource, f); ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.hurt(source, amount); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.connection.send(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end + } + } + } +diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +index 8a0863a695bb33698fb3fe6e06599f6f6f47011f..36e665009418d5177016a744eb920fbf99f534fc 100644 +--- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java ++++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +@@ -203,6 +203,7 @@ public class CombatTracker { + this.nextLocation = null; + } + ++ public final void reset() { this.recheckStatus(); } // Paper - OBFHELPER + public void recheckStatus() { + int i = this.inCombat ? 300 : 100; + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4503bd65b3454bad94bb7b869f4e72e3121d8a3d..e7fed1f8bb8ffb164ddcdab51f41c369d6e3103d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1537,6 +1537,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + // CraftBukkit end + ++ public final void runKillTrigger(Entity entity, int kills, DamageSource damageSource) { this.awardKillScore(entity, kills, damageSource); } // Paper - OBFHELPER + public void awardKillScore(Entity killer, int score, DamageSource damageSource) { + if (killer instanceof ServerPlayer) { + CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) killer, this, damageSource); +@@ -2441,6 +2442,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.fallDistance = 0.0F; + } + ++ public final void onKill(ServerLevel worldserver, net.minecraft.world.entity.LivingEntity entityLiving) { this.killed(worldserver, entityLiving); } // Paper - OBFHELPER + public void killed(ServerLevel worldserver, net.minecraft.world.entity.LivingEntity entityliving) {} + + protected void moveTowardsClosestSpace(double x, double y, double z) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 2e25cb2a04d150d3154bf0d7f0eccb97e65ff53e..7a2292e6907a2ae2026bd7243e864bd8300ecafa 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -186,7 +186,7 @@ public abstract class LivingEntity extends Entity { + protected float animStep; + protected float animStepO; + protected float rotOffs; +- protected int deathScore; ++ protected int deathScore;protected int getKillCount() { return this.deathScore; } // Paper - OBFHELPER + public float lastHurt; + protected boolean jumping; + public float xxa; +@@ -230,6 +230,7 @@ public abstract class LivingEntity extends Entity { + public Set collidableExemptions = new HashSet<>(); + public boolean canPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper ++ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event + + @Override + public float getBukkitYaw() { +@@ -1345,13 +1346,17 @@ public abstract class LivingEntity extends Entity { + if (knockbackCancelled) this.level.broadcastEntityEvent(this, (byte) 2); // Paper - Disable explosion knockback + if (this.isDeadOrDying()) { + if (!this.checkTotemDeathProtection(source)) { +- SoundEvent soundeffect = this.getDeathSound(); ++ // Paper start - moved into CraftEventFactory event caller for cancellable death event ++ //SoundEffect soundeffect = this.getSoundDeath(); + +- if (flag1 && soundeffect != null) { +- this.playSound(soundeffect, this.getSoundVolume(), this.getVoicePitch()); +- } ++// if (flag1 && soundeffect != null) { ++// this.playSound(soundeffect, this.getSoundVolume(), this.dH()); ++// } ++ this.silentDeath = !flag1; // mark entity as dying silently ++ // Paper end + + this.die(source); ++ this.silentDeath = false; // Paper - cancellable death event - reset to default + } + } else if (flag1) { + this.playHurtSound(source); +@@ -1490,27 +1495,48 @@ public abstract class LivingEntity extends Entity { + Entity entity = source.getEntity(); + LivingEntity entityliving = this.getKillCredit(); + +- if (this.deathScore >= 0 && entityliving != null) { +- entityliving.awardKillScore(this, this.deathScore, source); ++ /* // Paper - move down to make death event cancellable - this is the runKillTrigger below ++ if (this.aO >= 0 && entityliving != null) { ++ entityliving.a(this, this.aO, damagesource); + } + + if (this.isSleeping()) { +- this.stopSleeping(); ++ this.entityWakeup(); + } ++ */ // Paper + + this.dead = true; +- this.getCombatTracker().recheckStatus(); ++ // this.getCombatTracker().g(); // Paper - moved into if below as .reset() + if (this.level instanceof ServerLevel) { + if (entity != null) { +- entity.killed((ServerLevel) this.level, this); ++ // entity.a((WorldServer) this.world, this); // Paper - move below into if for onKill + } + +- this.dropAllDeathLoot(source); ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.d(source); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ if (this.getKillCount() >= 0 && entityliving != null) { ++ entityliving.runKillTrigger(this, this.getKillCount(), source); ++ } ++ if (this.isSleeping()) { ++ this.stopSleeping(); ++ } ++ this.getCombatTracker().reset(); ++ if (entity != null) { ++ entity.onKill((ServerLevel) this.level, this); ++ } ++ } else { ++ this.dead = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); ++ } ++ // Paper end + this.createWitherRose(entityliving); + } + ++ if (this.dead) { // Paper + this.level.broadcastEntityEvent(this, (byte) 3); + this.setPose(Pose.DYING); ++ } // Paper + } + } + +@@ -1518,7 +1544,7 @@ public abstract class LivingEntity extends Entity { + if (!this.level.isClientSide) { + boolean flag = false; + +- if (adversary instanceof WitherBoss) { ++ if (this.dead && adversary instanceof WitherBoss) { // Paper + if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + BlockPos blockposition = this.blockPosition(); + BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); +@@ -1546,8 +1572,9 @@ public abstract class LivingEntity extends Entity { + } + } + +- protected void dropAllDeathLoot(DamageSource source) { +- Entity entity = source.getEntity(); ++ protected org.bukkit.event.entity.EntityDeathEvent processDeath(DamageSource damagesource) { return d(damagesource); } // Paper - OBFHELPER ++ protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper ++ Entity entity = damagesource.getEntity(); + int i; + + if (entity instanceof net.minecraft.world.entity.player.Player) { +@@ -1560,19 +1587,22 @@ public abstract class LivingEntity extends Entity { + + this.dropEquipment(); // CraftBukkit - from below + if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.dropFromLootTable(source, flag); +- this.dropCustomDeathLoot(source, i, flag); ++ this.dropFromLootTable(damagesource, flag); ++ this.dropCustomDeathLoot(damagesource, i, flag); + } + // CraftBukkit start - Call death event +- CraftEventFactory.callEntityDeathEvent(this, this.drops); ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper ++ this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end + + // this.dropInventory();// CraftBukkit - moved up + this.dropExperience(); ++ return deathEvent; // Paper + } + + protected void dropEquipment() {} ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled + + // CraftBukkit start + public int getExpReward() { +@@ -1657,6 +1687,7 @@ public abstract class LivingEntity extends Entity { + return SoundEvents.GENERIC_HURT; + } + ++ public final SoundEvent getDeathSoundEffect() { return this.getDeathSound(); } // Paper - OBFHELPER + @Nullable + protected SoundEvent getDeathSound() { + return SoundEvents.GENERIC_DEATH; +@@ -2193,10 +2224,12 @@ public abstract class LivingEntity extends Entity { + + } + ++ public final float getDeathSoundVolume() { return this.getSoundVolume(); } // Paper - OBFHELPER + protected float getSoundVolume() { + return 1.0F; + } + ++ public float getSoundPitch() { return getVoicePitch();} // Paper - OBFHELPER + protected float getVoicePitch() { + return this.isBaby() ? (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.5F : (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index c2d98222f575d7383e4c040730f6d531bdb0d7b6..46792914f574800c893eb197fa7b3b87ce7e500b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -647,15 +647,25 @@ public class Fox extends Animal { + } + + @Override +- protected void dropAllDeathLoot(DamageSource source) { +- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); ++ protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper ++ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND).copy(); // Paper ++ ++ // Paper start - Cancellable death event ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.d(damagesource); ++ ++ // Below is code to drop ++ ++ if (deathEvent == null || deathEvent.isCancelled()) { ++ return deathEvent; ++ } ++ // Paper end + + if (!itemstack.isEmpty()) { + this.spawnAtLocation(itemstack); + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + +- super.dropAllDeathLoot(source); ++ return deathEvent; // Paper + } + + public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +index 34a9843267ef739e5889791fb4899fabe1f864bc..63723044ae6c607e6d36bc0b0c6b525037df34b2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +@@ -68,11 +68,19 @@ public abstract class AbstractChestedHorse extends AbstractHorse { + this.spawnAtLocation((ItemLike) Blocks.CHEST); + } + +- this.setChest(false); ++ //this.setCarryingChest(false); // Paper - moved to post death logic + } + + } + ++ // Paper start ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { ++ if (this.hasChest() && (event == null || !event.isCancelled())) { ++ this.setChest(false); ++ } ++ } ++ // Paper end ++ + @Override + public void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 2994eee1d381af2c9ff3649dd48a2ae14c38c9d7..33d51852ed6fe3f5adcdecf8f405a23689f4265a 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -610,7 +610,7 @@ public class ArmorStand extends LivingEntity { + this.armorItems.set(i, ItemStack.EMPTY); + } + } +- this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above ++ this.d(damageSource); // CraftBukkit - moved from above + + } + +@@ -742,7 +742,8 @@ public class ArmorStand extends LivingEntity { + + @Override + public void kill() { +- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event ++ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event // Paper - make cancellable ++ if (event.isCancelled()) return; // Paper - make cancellable + this.remove(); + } + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 14d5acff198338c68162e33d4a90f74be77cb15f..3aadc4ab5fe7b2ee9e20e0789ddcfe750599972f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -581,7 +581,7 @@ public abstract class Player extends LivingEntity { + super.die(source); + this.reapplyPosition(); + if (!this.isSpectator()) { +- this.dropAllDeathLoot(source); ++ this.d(source); + } + + if (source != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ee823c4ed5b9fcfaa900b470c582435f0b909ebc..a3e65028d3e0c09a65cd9c28b037fe01a2ed1d76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1836,7 +1836,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void sendHealthUpdate() { +- getHandle().connection.send(new ClientboundSetHealthPacket(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); ++ // Paper start - cancellable death event ++ //getHandle().playerConnection.sendPacket(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); ++ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel()); ++ if (this.getHandle().queueHealthUpdatePacket) { ++ this.getHandle().queuedHealthUpdatePacket = packet; ++ } else { ++ this.getHandle().connection.send(packet); ++ } ++ // Paper end + } + + public void injectScaledMaxHealth(Collection collection, boolean force) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 1877267344df1ff5b4de6a4e0c239f488cd52c1f..e696d2e52532df25d74a1f559e2c9ca0f3d5058d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -18,6 +18,8 @@ import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundSource; + import net.minecraft.util.Unit; + import net.minecraft.world.Container; + import net.minecraft.world.InteractionHand; +@@ -793,9 +795,16 @@ public class CraftEventFactory { + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); ++ populateFields(victim, event); // Paper - make cancellable + CraftWorld world = (CraftWorld) entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); + ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + victim.expToDrop = event.getDroppedExp(); + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { +@@ -811,8 +820,15 @@ public class CraftEventFactory { + CraftPlayer entity = victim.getBukkitEntity(); + PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure + event.setKeepInventory(keepInventory); ++ populateFields(victim, event); // Paper - make cancellable + org.bukkit.World world = entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + + victim.keepLevel = event.getKeepLevel(); + victim.newLevel = event.getNewLevel(); +@@ -829,6 +845,31 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - helper methods for making death event cancellable ++ // Add information to death event ++ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); ++ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); ++ SoundEvent soundEffect = victim.getDeathSoundEffect(); ++ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.getBukkit(soundEffect) : null); ++ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name())); ++ event.setDeathSoundVolume(victim.getDeathSoundVolume()); ++ event.setDeathSoundPitch(victim.getSoundPitch()); ++ } ++ ++ // Play death sound manually ++ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { ++ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null; ++ double x = event.getEntity().getLocation().getX(); ++ double y = event.getEntity().getLocation().getY(); ++ double z = event.getEntity().getLocation().getZ(); ++ SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.getSoundEffect(event.getDeathSound()); ++ SoundSource soundCategory = SoundSource.valueOf(event.getDeathSoundCategory().name()); ++ victim.level.playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); ++ } ++ } ++ // Paper end + /** + * Server methods + */ diff --git a/Remapped-Spigot-Server-Patches/0280-Allow-chests-to-be-placed-with-NBT-data.patch b/Remapped-Spigot-Server-Patches/0280-Allow-chests-to-be-placed-with-NBT-data.patch new file mode 100644 index 000000000..59c2f88e0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0280-Allow-chests-to-be-placed-with-NBT-data.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Sep 2018 18:43:31 -0500 +Subject: [PATCH] Allow chests to be placed with NBT data + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 79a7c37f15840dbd97510874ac12437d2b854999..02bfa4fb8055e60a84e878ffbf18303c0ee25b1d 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -296,6 +296,7 @@ public final class ItemStack { + enuminteractionresult = InteractionResult.FAIL; // cancel placement + // PAIL: Remove this when MC-99075 fixed + placeEvent.getPlayer().updateInventory(); ++ world.capturedTileEntities.clear(); // Paper - clear out tile entities as chests and such will pop loot + // revert back all captured blocks + for (BlockState blockstate : blocks) { + blockstate.update(true, false); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +index 17289d28b6d0023279a573715ee3d182988dd651..ab11c7a5a397047a35245b149d77bf035e718a0c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -326,7 +326,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity { // Pape + // CraftBukkit start + @Override + public boolean onlyOpCanSetNbt() { +- return true; ++ return false; // Paper + } + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0281-Mob-Pathfinding-API.patch b/Remapped-Spigot-Server-Patches/0281-Mob-Pathfinding-API.patch new file mode 100644 index 000000000..8ae73987b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0281-Mob-Pathfinding-API.patch @@ -0,0 +1,290 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 9 Sep 2018 13:30:00 -0400 +Subject: [PATCH] Mob Pathfinding API + +Implements Pathfinding API for mobs + +diff --git a/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3e7971b7ca5be0442378c9e7482775e05918d0ac +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +@@ -0,0 +1,141 @@ ++package com.destroystokyo.paper.entity; ++ ++import org.apache.commons.lang.Validate; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Mob; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import net.minecraft.world.level.pathfinder.Node; ++import net.minecraft.world.level.pathfinder.Path; ++import PathResult; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinder { ++ ++ private final net.minecraft.world.entity.Mob entity; ++ ++ public PaperPathfinder(net.minecraft.world.entity.Mob entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public Mob getEntity() { ++ return entity.getBukkitMob(); ++ } ++ ++ @Override ++ public void stopPathfinding() { ++ entity.getNavigation().stopPathfinding(); ++ } ++ ++ @Override ++ public boolean hasPath() { ++ return entity.getNavigation().getPathEntity() != null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult getCurrentPath() { ++ Path path = entity.getNavigation().getPathEntity(); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(Location loc) { ++ Validate.notNull(loc, "Location can not be null"); ++ Path path = entity.getNavigation().calculateDestination(loc.getX(), loc.getY(), loc.getZ()); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(LivingEntity target) { ++ Validate.notNull(target, "Target can not be null"); ++ Path path = entity.getNavigation().calculateDestination(((CraftLivingEntity) target).getHandle()); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Override ++ public boolean moveTo(@Nonnull PathResult path, double speed) { ++ Validate.notNull(path, "PathResult can not be null"); ++ Path pathEntity = ((PaperPathResult) path).path; ++ return entity.getNavigation().setDestination(pathEntity, speed); ++ } ++ ++ @Override ++ public boolean canOpenDoors() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldOpenDoors(); ++ } ++ ++ @Override ++ public void setCanOpenDoors(boolean canOpenDoors) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldOpenDoors(canOpenDoors); ++ } ++ ++ @Override ++ public boolean canPassDoors() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldPassDoors(); ++ } ++ ++ @Override ++ public void setCanPassDoors(boolean canPassDoors) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldPassDoors(canPassDoors); ++ } ++ ++ @Override ++ public boolean canFloat() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldFloat(); ++ } ++ ++ @Override ++ public void setCanFloat(boolean canFloat) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldFloat(canFloat); ++ } ++ ++ public class PaperPathResult implements com.destroystokyo.paper.entity.PaperPathfinder.PathResult { ++ ++ private final Path path; ++ PaperPathResult(Path path) { ++ this.path = path; ++ } ++ ++ @Nullable ++ @Override ++ public Location getFinalPoint() { ++ Node point = path.getFinalPoint(); ++ return point != null ? toLoc(point) : null; ++ } ++ ++ @Override ++ public List getPoints() { ++ List points = new ArrayList<>(); ++ for (Node point : path.getPoints()) { ++ points.add(toLoc(point)); ++ } ++ return points; ++ } ++ ++ @Override ++ public int getNextPointIndex() { ++ return path.getNextIndex(); ++ } ++ ++ @Nullable ++ @Override ++ public Location getNextPoint() { ++ if (!path.hasNext()) { ++ return null; ++ } ++ return toLoc(path.getPoints().get(path.getNextIndex())); ++ } ++ } ++ ++ private Location toLoc(Node point) { ++ return new Location(entity.level.getWorld(), point.getX(), point.getY(), point.getZ()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index c3082f5dd64413a47421cb01538bec846bf21d2c..a362506f38e8d30543b6cd6d215db561290dac76 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -100,7 +100,7 @@ public abstract class PathNavigation { + } + + @Nullable +- public final Path createPath(double x, double y, double z, int distance) { ++ public final Path calculateDestination(double d0, double d1, double d2) { return createPath(d0, d1, d2, 0); } public final Path createPath(double x, double y, double z, int distance) { // Paper - OBFHELPER + return this.createPath(new BlockPos(x, y, z), distance); + } + +@@ -125,7 +125,7 @@ public abstract class PathNavigation { + } + + @Nullable +- public Path createPath(Entity entity, int distance) { ++ public final Path calculateDestination(Entity entity) { return createPath(entity, 0); } public Path createPath(Entity entity, int distance) { + return this.a(ImmutableSet.of(entity.blockPosition()), entity, 16, true, distance); // Paper + } + +@@ -190,6 +190,7 @@ public abstract class PathNavigation { + return pathentity != null && this.moveTo(pathentity, speed); + } + ++ public boolean setDestination(@Nullable Path pathentity, double speed) { return moveTo(pathentity, speed); } // Paper - OBFHELPER + public boolean moveTo(@Nullable Path path, double speed) { + if (path == null) { + this.path = null; +@@ -217,7 +218,7 @@ public abstract class PathNavigation { + } + } + +- @Nullable ++ @Nullable public Path getPathEntity() { return getPath(); } @Nullable // Paper - OBFHELPER + public Path getPath() { + return this.path; + } +@@ -341,6 +342,7 @@ public abstract class PathNavigation { + return !this.isDone(); + } + ++ public void stopPathfinding() { stop(); } // Paper - OBFHELPER + public void stop() { + this.path = null; + } +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Node.java b/src/main/java/net/minecraft/world/level/pathfinder/Node.java +index c1ac95d784935f5d3d827e2e390162f594991d2c..27b5d3d02d1f3aa048fefc3ef2222c8031e7661f 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/Node.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Node.java +@@ -5,9 +5,9 @@ import net.minecraft.util.Mth; + + public class Node { + +- public final int x; +- public final int y; +- public final int z; ++ public final int x; public final int getX() { return x; } // Paper - OBFHELPER ++ public final int y; public final int getY() { return y; } // Paper - OBFHELPER ++ public final int z; public final int getZ() { return z; } // Paper - OBFHELPER + private final int hash; + public int heapIdx = -1; + public float g; +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java +index 0941bd177f65abfed3991267448df7df259d7f04..ddc9a9ececf44ce5524fd98a872e8a53cd7cc4f5 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java +@@ -16,9 +16,9 @@ public abstract class NodeEvaluator { + protected int entityWidth; + protected int entityHeight; + protected int entityDepth; +- protected boolean canPassDoors; +- protected boolean canOpenDoors; +- protected boolean canFloat; ++ protected boolean canPassDoors; public boolean shouldPassDoors() { return canPassDoors; } public void setShouldPassDoors(boolean b) { canPassDoors = b; } // Paper - obfhelper ++ protected boolean canOpenDoors; public boolean shouldOpenDoors() { return canOpenDoors; } public void setShouldOpenDoors(boolean b) { canOpenDoors = b; } // Paper - obfhelper ++ protected boolean canFloat; public boolean shouldFloat() { return canFloat; } public void setShouldFloat(boolean b) { canFloat = b; } // Paper - obfhelper + + public NodeEvaluator() {} + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java +index 7bc0787634e3c5a6f76181b166793fb7591767e4..fd5b369b59669b893aaaec17aef1a526fd23d8c0 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java +@@ -8,13 +8,14 @@ import net.minecraft.world.phys.Vec3; + + public class Path { + +- private final List nodes; ++ private final List nodes; public List getPoints() { return nodes; } // Paper - OBFHELPER + private Node[] openSet = new Node[0]; + private Node[] closedSet = new Node[0]; +- private int nextNodeIndex; ++ private int nextNodeIndex; public int getNextIndex() { return this.nextNodeIndex; } // Paper - OBFHELPER + private final BlockPos target; + private final float distToTarget; + private final boolean reached; ++ public boolean hasNext() { return getNextIndex() < getPoints().size(); } // Paper + + public Path(List nodes, BlockPos target, boolean reachesTarget) { + this.nodes = nodes; +@@ -36,7 +37,7 @@ public class Path { + } + + @Nullable +- public Node getEndNode() { ++ public Node getFinalPoint() { return getEndNode(); } @Nullable public Node getEndNode() { // Paper - OBFHELPER + return !this.nodes.isEmpty() ? (Node) this.nodes.get(this.nodes.size() - 1) : null; + } + +@@ -84,7 +85,7 @@ public class Path { + return this.getEntityPosAtNode(entity, this.nextNodeIndex); + } + +- public BlockPos getNextNodePos() { ++ public BlockPos getNext() { return getNextNodePos(); } public BlockPos getNextNodePos() { // Paper - OBFHELPER + return ((Node) this.nodes.get(this.nextNodeIndex)).asBlockPos(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index fd2c5a4e245647f51c1191991dc315b773ff73d4..b5fe55a77c8558cf2ea32689ff57911530df75f9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -11,8 +11,11 @@ import org.bukkit.loot.LootTable; + public abstract class CraftMob extends CraftLivingEntity implements Mob { + public CraftMob(CraftServer server, net.minecraft.world.entity.Mob entity) { + super(server, entity); ++ paperPathfinder = new com.destroystokyo.paper.entity.PaperPathfinder(entity); // Paper + } + ++ private final com.destroystokyo.paper.entity.PaperPathfinder paperPathfinder; // Paper ++ @Override public com.destroystokyo.paper.entity.Pathfinder getPathfinder() { return paperPathfinder; } // Paper + @Override + public void setTarget(LivingEntity target) { + net.minecraft.world.entity.Mob entity = getHandle(); diff --git a/Remapped-Spigot-Server-Patches/0282-Prevent-chunk-loading-from-Fluid-Flowing.patch b/Remapped-Spigot-Server-Patches/0282-Prevent-chunk-loading-from-Fluid-Flowing.patch new file mode 100644 index 000000000..9234ee7b6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0282-Prevent-chunk-loading-from-Fluid-Flowing.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:36:16 -0400 +Subject: [PATCH] Prevent chunk loading from Fluid Flowing + + +diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +index 967123992ee86f13f4ca6e336eaf8cebed086a1a..7544bf227b1dded0f854cc1b30d246d120f65b20 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -175,7 +175,8 @@ public abstract class FlowingFluid extends Fluid { + Direction enumdirection = (Direction) entry.getKey(); + FluidState fluid1 = (FluidState) entry.getValue(); + BlockPos blockposition1 = pos.relative(enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ BlockState iblockdata1 = world.getTypeIfLoaded(blockposition1); // Paper ++ if (iblockdata1 == null) continue; // Paper + + if (this.canSpreadTo(world, pos, blockState, enumdirection, blockposition1, iblockdata1, world.getFluidState(blockposition1), fluid1.getType())) { + // CraftBukkit start +@@ -202,7 +203,8 @@ public abstract class FlowingFluid extends Fluid { + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + BlockPos blockposition1 = pos.relative(enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ BlockState iblockdata1 = world.getTypeIfLoaded(blockposition1); // Paper ++ if (iblockdata1 == null) continue; // Paper + FluidState fluid = iblockdata1.getFluidState(); + + if (fluid.getType().isSame((Fluid) this) && this.canPassThroughWall(enumdirection, (BlockGetter) world, pos, state, blockposition1, iblockdata1)) { +@@ -319,11 +321,18 @@ public abstract class FlowingFluid extends Fluid { + if (enumdirection1 != enumdirection) { + BlockPos blockposition2 = blockposition.relative(enumdirection1); + short short0 = getCacheKey(blockposition1, blockposition2); +- Pair pair = (Pair) short2objectmap.computeIfAbsent(short0, (k) -> { +- BlockState iblockdata1 = world.getBlockState(blockposition2); ++ // Paper start - avoid loading chunks ++ Pair pair = short2objectmap.get(short0); ++ if (pair == null) { ++ BlockState iblockdatax = world.getTypeIfLoaded(blockposition2); ++ if (iblockdatax == null) { ++ continue; ++ } + +- return Pair.of(iblockdata1, iblockdata1.getFluidState()); +- }); ++ pair = Pair.of(iblockdatax, iblockdatax.getFluidState()); ++ short2objectmap.put(short0, pair); ++ } ++ // Paper end + BlockState iblockdata1 = (BlockState) pair.getFirst(); + FluidState fluid = (FluidState) pair.getSecond(); + +@@ -395,11 +404,16 @@ public abstract class FlowingFluid extends Fluid { + Direction enumdirection = (Direction) iterator.next(); + BlockPos blockposition1 = pos.relative(enumdirection); + short short0 = getCacheKey(pos, blockposition1); +- Pair pair = (Pair) short2objectmap.computeIfAbsent(short0, (j) -> { +- BlockState iblockdata1 = world.getBlockState(blockposition1); +- +- return Pair.of(iblockdata1, iblockdata1.getFluidState()); +- }); ++ // Paper start ++ Pair pair = (Pair) short2objectmap.get(short0); ++ if (pair == null) { ++ BlockState iblockdatax = world.getTypeIfLoaded(blockposition1); ++ if (iblockdatax == null) continue; ++ ++ pair = Pair.of(iblockdatax, iblockdatax.getFluidState()); ++ short2objectmap.put(short0, pair); ++ } ++ // Paper end + BlockState iblockdata1 = (BlockState) pair.getFirst(); + FluidState fluid = (FluidState) pair.getSecond(); + FluidState fluid1 = this.getNewLiquid(world, blockposition1, iblockdata1); diff --git a/Remapped-Spigot-Server-Patches/0283-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch b/Remapped-Spigot-Server-Patches/0283-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch new file mode 100644 index 000000000..30b7b72f6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0283-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch @@ -0,0 +1,446 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Wed, 12 Sep 2018 18:53:55 +0300 +Subject: [PATCH] Implement an API for CanPlaceOn and CanDestroy NBT values + + +diff --git a/src/main/java/net/minecraft/commands/arguments/blocks/BlockStateParser.java b/src/main/java/net/minecraft/commands/arguments/blocks/BlockStateParser.java +index 9175c74c2119e7052d744db77badcae6be05f3b4..52f8ad848a22ddca856f6f256276ea59416f9664 100644 +--- a/src/main/java/net/minecraft/commands/arguments/blocks/BlockStateParser.java ++++ b/src/main/java/net/minecraft/commands/arguments/blocks/BlockStateParser.java +@@ -57,7 +57,7 @@ public class BlockStateParser { + private final boolean forTesting; + private final Map, Comparable> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable + private final Map vagueProperties = Maps.newHashMap(); +- private ResourceLocation id = new ResourceLocation(""); ++ private ResourceLocation id = new ResourceLocation(""); public final ResourceLocation getBlockKey() { return this.id; } // Paper - OBFHELPER + private StateDefinition definition; + private BlockState state; + @Nullable +@@ -86,11 +86,13 @@ public class BlockStateParser { + return this.nbt; + } + ++ public final @Nullable ResourceLocation getTagKey() { return getTag(); } // Paper - OBFHELPER + @Nullable + public ResourceLocation getTag() { + return this.tag; + } + ++ public final BlockStateParser parse(boolean parseTile) throws CommandSyntaxException { return this.parse(parseTile); } // Paper - OBFHELPER + public BlockStateParser parse(boolean allowNbt) throws CommandSyntaxException { + this.suggestions = this::suggestBlockIdOrTag; + if (this.reader.canRead() && this.reader.peek() == '#') { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 64f166fa93e998a58a895d785ff8c9e62dacb1bb..45abfebf3f947dcbd2e7b1d95be8ba918f044e51 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -39,12 +39,14 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import net.minecraft.commands.arguments.blocks.BlockStateParser; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtIo; + import net.minecraft.nbt.StringTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.TextComponent; ++import net.minecraft.resources.ResourceLocation; + import net.minecraft.world.item.BlockItem; + import org.apache.commons.codec.binary.Base64; + import org.apache.commons.lang.Validate; +@@ -83,6 +85,12 @@ import org.bukkit.persistence.PersistentDataContainer; + import static org.spigotmc.ValidateUtils.*; + // Spigot end + ++// Paper start ++import com.destroystokyo.paper.Namespaced; ++import com.destroystokyo.paper.NamespacedTag; ++import java.util.Collections; ++// Paper end ++ + /** + * Children must include the following: + * +@@ -266,6 +274,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Specific(Specific.To.NBT) + static final ItemMetaKey BLOCK_DATA = new ItemMetaKey("BlockStateTag"); + static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues"); ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ static final ItemMetaKey CAN_DESTROY = new ItemMetaKey("CanDestroy"); ++ static final ItemMetaKey CAN_PLACE_ON = new ItemMetaKey("CanPlaceOn"); ++ // Paper end + + // We store the raw original JSON representation of all text data. See SPIGOT-5063, SPIGOT-5656, SPIGOT-5304 + private String displayName; +@@ -279,6 +291,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private int hideFlag; + private boolean unbreakable; + private int damage; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ private Set placeableKeys = Sets.newHashSet(); ++ private Set destroyableKeys = Sets.newHashSet(); ++ // Paper end + + private static final Set HANDLED_TAGS = Sets.newHashSet(); + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); +@@ -316,6 +332,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.hideFlag = meta.hideFlag; + this.unbreakable = meta.unbreakable; + this.damage = meta.damage; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (meta.hasPlaceableKeys()) { ++ this.placeableKeys = new java.util.HashSet<>(meta.placeableKeys); ++ } ++ ++ if (meta.hasDestroyableKeys()) { ++ this.destroyableKeys = new java.util.HashSet<>(meta.destroyableKeys); ++ } ++ // Paper end + this.unhandledTags.putAll(meta.unhandledTags); + this.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw()); + +@@ -379,6 +404,31 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + persistentDataContainer.put(key, compound.get(key)); + } + } ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (tag.contains(CAN_DESTROY.NBT)) { ++ ListTag list = tag.getList(CAN_DESTROY.NBT, CraftMagicNumbers.NBT.TAG_STRING); ++ for (int i = 0; i < list.size(); i++) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { ++ continue; ++ } ++ ++ this.destroyableKeys.add(namespaced); ++ } ++ } ++ ++ if (tag.contains(CAN_PLACE_ON.NBT)) { ++ ListTag list = tag.getList(CAN_PLACE_ON.NBT, CraftMagicNumbers.NBT.TAG_STRING); ++ for (int i = 0; i < list.size(); i++) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { ++ continue; ++ } ++ ++ this.placeableKeys.add(namespaced); ++ } ++ } ++ // Paper end + + Set keys = tag.getAllKeys(); + for (String key : keys) { +@@ -517,6 +567,34 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + setDamage(damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ Iterable canPlaceOnSerialized = SerializableMeta.getObject(Iterable.class, map, CAN_PLACE_ON.BUKKIT, true); ++ if (canPlaceOnSerialized != null) { ++ for (Object canPlaceOnElement : canPlaceOnSerialized) { ++ String canPlaceOnRaw = (String) canPlaceOnElement; ++ Namespaced value = this.deserializeNamespaced(canPlaceOnRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.placeableKeys.add(value); ++ } ++ } ++ ++ Iterable canDestroySerialized = SerializableMeta.getObject(Iterable.class, map, CAN_DESTROY.BUKKIT, true); ++ if (canDestroySerialized != null) { ++ for (Object canDestroyElement : canDestroySerialized) { ++ String canDestroyRaw = (String) canDestroyElement; ++ Namespaced value = this.deserializeNamespaced(canDestroyRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.destroyableKeys.add(value); ++ } ++ } ++ // Paper end ++ + String internal = SerializableMeta.getString(map, "internal", true); + if (internal != null) { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(internal)); +@@ -645,6 +723,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (hasDamage()) { + itemTag.putInt(DAMAGE.NBT, damage); + } ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (hasPlaceableKeys()) { ++ List items = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ itemTag.put(CAN_PLACE_ON.NBT, createNonComponentStringList(items)); ++ } ++ ++ if (hasDestroyableKeys()) { ++ List items = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ itemTag.put(CAN_DESTROY.NBT, createNonComponentStringList(items)); ++ } ++ // Paper end + + for (Map.Entry e : unhandledTags.entrySet()) { + itemTag.put(e.getKey(), e.getValue()); +@@ -661,6 +756,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ static ListTag createNonComponentStringList(List list) { ++ if (list == null || list.isEmpty()) { ++ return null; ++ } ++ ++ ListTag tagList = new ListTag(); ++ for (String value : list) { ++ tagList.add(StringTag.valueOf(value)); // Paper - NBTTagString.of(String str) ++ } ++ ++ return tagList; ++ } ++ // Paper end ++ + ListTag createStringList(List list) { + if (list == null) { + return null; +@@ -744,7 +854,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Overridden + boolean isEmpty() { +- return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); ++ return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers() || hasPlaceableKeys() || hasDestroyableKeys()); // Paper - Implement an API for CanPlaceOn and CanDestroy NBT values + } + + // Paper start +@@ -1168,7 +1278,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hideFlag == that.hideFlag) + && (this.isUnbreakable() == that.isUnbreakable()) + && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) +- && (this.version == that.version); ++ && (this.version == that.version) ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ && (this.hasPlaceableKeys() ? that.hasPlaceableKeys() && this.placeableKeys.equals(that.placeableKeys) : !that.hasPlaceableKeys()) ++ && (this.hasDestroyableKeys() ? that.hasDestroyableKeys() && this.destroyableKeys.equals(that.destroyableKeys) : !that.hasDestroyableKeys()); ++ // Paper end + } + + /** +@@ -1203,6 +1317,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + hash = 61 * hash + (hasDamage() ? this.damage : 0); + hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); + hash = 61 * hash + version; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ hash = 61 * hash + (hasPlaceableKeys() ? this.placeableKeys.hashCode() : 0); ++ hash = 61 * hash + (hasDestroyableKeys() ? this.destroyableKeys.hashCode() : 0); ++ // Paper end + return hash; + } + +@@ -1227,6 +1345,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.unbreakable = this.unbreakable; + clone.damage = this.damage; + clone.version = this.version; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (this.placeableKeys != null) { ++ clone.placeableKeys = Sets.newHashSet(this.placeableKeys); ++ } ++ if (this.destroyableKeys != null) { ++ clone.destroyableKeys = Sets.newHashSet(this.destroyableKeys); ++ } ++ // Paper end + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); +@@ -1284,6 +1410,24 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + builder.put(DAMAGE.BUKKIT, damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (hasPlaceableKeys()) { ++ List cerealPlaceable = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_PLACE_ON.BUKKIT, cerealPlaceable); ++ } ++ ++ if (hasDestroyableKeys()) { ++ List cerealDestroyable = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_DESTROY.BUKKIT, cerealDestroyable); ++ } ++ // Paper end ++ + final Map internalTags = new HashMap(unhandledTags); + serializeInternal(internalTags); + if (!internalTags.isEmpty()) { +@@ -1448,6 +1592,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaArmorStand.SHOW_ARMS.NBT, + CraftMetaArmorStand.SMALL.NBT, + CraftMetaArmorStand.MARKER.NBT, ++ CAN_DESTROY.NBT, ++ CAN_PLACE_ON.NBT, + // Paper end + CraftMetaCompass.LODESTONE_DIMENSION.NBT, + CraftMetaCompass.LODESTONE_POS.NBT, +@@ -1475,4 +1621,147 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + // Paper end + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ @Override ++ @SuppressWarnings("deprecation") ++ public Set getCanDestroy() { ++ return !hasDestroyableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public void setCanDestroy(Set canDestroy) { ++ Validate.notNull(canDestroy, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.destroyableKeys, canDestroy); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public Set getCanPlaceOn() { ++ return !hasPlaceableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.placeableKeys); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public void setCanPlaceOn(Set canPlaceOn) { ++ Validate.notNull(canPlaceOn, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.placeableKeys, canPlaceOn); ++ } ++ ++ @Override ++ public Set getDestroyableKeys() { ++ return !hasDestroyableKeys() ? Collections.emptySet() : Sets.newHashSet(this.destroyableKeys); ++ } ++ ++ @Override ++ public void setDestroyableKeys(Collection canDestroy) { ++ Validate.notNull(canDestroy, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canDestroy), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.destroyableKeys.clear(); ++ this.destroyableKeys.addAll(canDestroy); ++ } ++ ++ @Override ++ public Set getPlaceableKeys() { ++ return !hasPlaceableKeys() ? Collections.emptySet() : Sets.newHashSet(this.placeableKeys); ++ } ++ ++ @Override ++ public void setPlaceableKeys(Collection canPlaceOn) { ++ Validate.notNull(canPlaceOn, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canPlaceOn), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.placeableKeys.clear(); ++ this.placeableKeys.addAll(canPlaceOn); ++ } ++ ++ @Override ++ public boolean hasPlaceableKeys() { ++ return this.placeableKeys != null && !this.placeableKeys.isEmpty(); ++ } ++ ++ @Override ++ public boolean hasDestroyableKeys() { ++ return this.destroyableKeys != null && !this.destroyableKeys.isEmpty(); ++ } ++ ++ @Deprecated ++ private void legacyClearAndReplaceKeys(Collection toUpdate, Collection beingSet) { ++ if (beingSet.stream().anyMatch(Material::isLegacy)) { ++ throw new IllegalArgumentException("Set must not contain any legacy materials!"); ++ } ++ ++ toUpdate.clear(); ++ toUpdate.addAll(beingSet.stream().map(Material::getKey).collect(java.util.stream.Collectors.toSet())); ++ } ++ ++ @Deprecated ++ private Set legacyGetMatsFromKeys(Collection names) { ++ Set mats = Sets.newHashSet(); ++ for (Namespaced key : names) { ++ if (!(key instanceof org.bukkit.NamespacedKey)) { ++ continue; ++ } ++ ++ Material material = Material.matchMaterial(key.toString(), false); ++ if (material != null) { ++ mats.add(material); ++ } ++ } ++ ++ return mats; ++ } ++ ++ private @Nullable Namespaced deserializeNamespaced(String raw) { ++ boolean isTag = raw.length() > 0 && raw.codePointAt(0) == '#'; ++ BlockStateParser blockParser = new BlockStateParser(new com.mojang.brigadier.StringReader(raw), true); ++ try { ++ blockParser = blockParser.parse(false); ++ } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { ++ e.printStackTrace(); ++ return null; ++ } ++ ++ ResourceLocation key; ++ if (isTag) { ++ key = blockParser.getTagKey(); ++ } else { ++ key = blockParser.getBlockKey(); ++ } ++ ++ if (key == null) { ++ return null; ++ } ++ ++ // don't DC the player if something slips through somehow ++ Namespaced resource = null; ++ try { ++ if (isTag) { ++ resource = new NamespacedTag(key.getNamespace(), key.getPath()); ++ } else { ++ resource = CraftNamespacedKey.fromMinecraft(key); ++ } ++ } catch (IllegalArgumentException ex) { ++ org.bukkit.Bukkit.getLogger().warning("Namespaced resource does not validate: " + key.toString()); ++ ex.printStackTrace(); ++ } ++ ++ return resource; ++ } ++ ++ private @Nonnull String serializeNamespaced(Namespaced resource) { ++ return resource.toString(); ++ } ++ ++ // not a fan of this ++ private boolean ofAcceptableType(Collection namespacedResources) { ++ ++ for (Namespaced resource : namespacedResources) { ++ if (!(resource instanceof org.bukkit.NamespacedKey || resource instanceof com.destroystokyo.paper.NamespacedTag)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0284-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch b/Remapped-Spigot-Server-Patches/0284-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch new file mode 100644 index 000000000..dff6bf782 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0284-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:56:36 -0400 +Subject: [PATCH] Prevent Mob AI Rules from Loading Chunks + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +index 415661c61eb85ac57dd2ba81fb62f8d9df54153f..c9825bc1894904fab34bec8223adf8e343bb6623 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +@@ -29,11 +29,13 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + private final Block blockToRemove; + private final Mob removerMob; + private int ticksSinceReachedGoal; ++ private Level world; // Paper + + public RemoveBlockGoal(Block targetBlock, PathfinderMob mob, double speed, int maxYDifference) { + super(mob, speed, 24, maxYDifference); + this.blockToRemove = targetBlock; + this.removerMob = mob; ++ this.world = mob.level; // Paper + } + + @Override +@@ -131,7 +133,9 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + + @Nullable + private BlockPos getPosWithBlock(BlockPos pos, BlockGetter world) { +- if (world.getBlockState(pos).is(this.blockToRemove)) { ++ Block block = world.getBlockIfLoaded(pos); // Paper ++ if (block == null) return null; // Paper ++ if (block.is(this.blockToRemove)) { // Paper + return pos; + } else { + BlockPos[] ablockposition = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()}; +@@ -141,7 +145,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + for (int j = 0; j < i; ++j) { + BlockPos blockposition1 = ablockposition1[j]; + +- if (world.getBlockState(blockposition1).is(this.blockToRemove)) { ++ if (world.getBlockIfLoaded(blockposition1).is(this.blockToRemove)) { // Paper + return blockposition1; + } + } +@@ -152,7 +156,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + + @Override + protected boolean isValidTarget(LevelReader world, BlockPos pos) { +- ChunkAccess ichunkaccess = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, false); ++ ChunkAccess ichunkaccess = world.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper + + return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).is(this.blockToRemove) && ichunkaccess.getBlockState(pos.above()).isAir() && ichunkaccess.getBlockState(pos.above(2)).isAir(); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/util/RandomPos.java b/src/main/java/net/minecraft/world/entity/ai/util/RandomPos.java +index 246cbddb23781e323d022db2fbeef72c9eeaad2b..55d484fd4774cfad8f8ba3263b387243540e31b1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/util/RandomPos.java ++++ b/src/main/java/net/minecraft/world/entity/ai/util/RandomPos.java +@@ -13,6 +13,7 @@ import net.minecraft.util.Mth; + import net.minecraft.world.entity.PathfinderMob; + import net.minecraft.world.entity.ai.navigation.PathNavigation; + import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.pathfinder.BlockPathTypes; + import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; + import net.minecraft.world.phys.Vec3; +@@ -128,6 +129,7 @@ public class RandomPos { + } + + blockposition2 = new BlockPos((double) k1 + mob.getX(), (double) l1 + mob.getY(), (double) i2 + mob.getZ()); ++ if (!mob.level.hasChunkAt(blockposition2)) continue; // Paper + if (blockposition2.getY() >= 0 && blockposition2.getY() <= mob.level.getMaxBuildHeight() && (!flag3 || mob.isWithinRestriction(blockposition2)) && (!validPositionsOnly || navigationabstract.isStableDestination(blockposition2))) { + if (aboveGround) { + blockposition2 = moveUpToAboveSolid(blockposition2, random.nextInt(distanceAboveGroundRange + 1) + minDistanceAboveGround, mob.level.getMaxBuildHeight(), (blockposition3) -> { +@@ -135,7 +137,8 @@ public class RandomPos { + }); + } + +- if (notInWater || !mob.level.getFluidState(blockposition2).is((Tag) FluidTags.WATER)) { ++ FluidState fluid = mob.level.getFluidIfLoaded(blockposition2); // Paper ++ if (notInWater || (fluid != null && !fluid.is((Tag) FluidTags.WATER))) { // Paper + BlockPathTypes pathtype = WalkNodeEvaluator.getBlockPathTypeStatic((BlockGetter) mob.level, blockposition2.mutable()); + + if (mob.getPathfindingMalus(pathtype) == 0.0F) { diff --git a/Remapped-Spigot-Server-Patches/0285-Prevent-mob-spawning-from-loading-generating-chunks.patch b/Remapped-Spigot-Server-Patches/0285-Prevent-mob-spawning-from-loading-generating-chunks.patch new file mode 100644 index 000000000..759560db0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0285-Prevent-mob-spawning-from-loading-generating-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:12:57 -0400 +Subject: [PATCH] Prevent mob spawning from loading/generating chunks + +also prevents if out of world border bounds + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index b90a275a0dc2913809ce16659eed445501e486de..e23875ae07c23fed1161ea070e63bbc3a30168a0 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -174,9 +174,9 @@ public final class NaturalSpawner { + StructureFeatureManager structuremanager = world.structureFeatureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); +- BlockState iblockdata = chunk.getBlockState(pos); ++ BlockState iblockdata = world.getTypeIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn + +- if (!iblockdata.isRedstoneConductor(chunk, pos)) { ++ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + int j = 0; + int k = 0; +@@ -205,7 +205,7 @@ public final class NaturalSpawner { + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); + +- if (isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { ++ if (isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2) && world.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { + biomesettingsmobs_c = getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, (BlockPos) blockposition_mutableblockposition); + if (biomesettingsmobs_c == null) { diff --git a/Remapped-Spigot-Server-Patches/0286-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/Remapped-Spigot-Server-Patches/0286-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch new file mode 100644 index 000000000..db7b1e631 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0286-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:47:01 -0400 +Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning + +Uses an EnumMap as well as a Set paired List for O(1) contains calls. + +diff --git a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +index 58ee27a994b4cd845b8bb28e80cc2102c860f097..528f42c63a1186b8827bfe7cf6193e14da938cb3 100644 +--- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -30,19 +30,28 @@ public class MobSpawnSettings { + }, (enumcreaturetype) -> { + return ImmutableList.of(); + })), ImmutableMap.of(), false); ++ // Paper start- decompile error workaround ++ private static class bProxy extends MobSpawnSettings.MobSpawnCost { ++ private bProxy(double gravityLimit, double mass) { ++ super(gravityLimit, mass); ++ } ++ } ++ private static class cProxy extends MobSpawnSettings.SpawnerData { ++ public cProxy(EntityType type, int weight, int minGroupSize, int maxGroupSize) { ++ super(type, weight, minGroupSize, maxGroupSize); ++ } ++ }; ++ // Paper end + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { +- RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { +- return biomesettingsmobs.d; ++ RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { // Paper - add type to builder ++ return biomesettingsmobs.creatureGenerationProbability; + }); +- Codec codec = MobCategory.CODEC; +- Codec codec1 = BiomeSettingsMobs.c.b.listOf(); +- Logger logger = MobSpawnSettings.LOGGER; +- +- logger.getClass(); +- return instance.group(recordcodecbuilder, Codec.simpleMap(codec, codec1.promotePartial(Util.prefix("Spawn data: ", logger::error)), StringRepresentable.keys(MobCategory.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { +- return biomesettingsmobs.e; +- }), Codec.simpleMap(Registry.ENTITY_TYPE, BiomeSettingsMobs.b.a, Registry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { +- return biomesettingsmobs.f; ++ // Paper - remove unused vars ++ ++ return instance.group(recordcodecbuilder, Codec.simpleMap(MobCategory.CODEC, cProxy.CODEC.listOf().promotePartial(Util.prefix("Spawn data: ", MobSpawnSettings.LOGGER::error)), StringRepresentable.keys(MobCategory.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { // Paper - inline codec, cProxy, LOGGER ++ return biomesettingsmobs.spawners; ++ }), Codec.simpleMap(Registry.ENTITY_TYPE, bProxy.CODEC, Registry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { // Paper - decompile error - bProxy ++ return biomesettingsmobs.mobSpawnCosts; + }), Codec.BOOL.fieldOf("player_spawn_friendly").orElse(false).forGetter(MobSpawnSettings::playerSpawnFriendly)).apply(instance, MobSpawnSettings::new); + }); + private final float creatureGenerationProbability; +@@ -76,11 +85,43 @@ public class MobSpawnSettings { + + public static class Builder { + +- private final Map> spawners = (Map) Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((enumcreaturetype) -> { ++ // Paper start - keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it ++ public static class MobList extends java.util.ArrayList { ++ java.util.Set biomes = new java.util.HashSet<>(); ++ ++ @Override ++ public boolean contains(Object o) { ++ return biomes.contains(o); ++ } ++ ++ @Override ++ public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { ++ biomes.add(BiomeSettingsMobs); ++ return super.add(BiomeSettingsMobs); ++ } ++ ++ @Override ++ public MobSpawnSettings.SpawnerData remove(int index) { ++ MobSpawnSettings.SpawnerData removed = super.remove(index); ++ if (removed != null) { ++ biomes.remove(removed); ++ } ++ return removed; ++ } ++ ++ @Override ++ public void clear() { ++ biomes.clear(); ++ super.clear(); ++ } ++ } ++ // use toImmutableEnumMap collector ++ private final Map> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((enumcreaturetype) -> { + return enumcreaturetype; + }, (enumcreaturetype) -> { +- return Lists.newArrayList(); ++ return new MobList(); // Use MobList instead of ArrayList + })); ++ // Paper end + private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); + private float creatureGenerationProbability = 0.1F; + private boolean playerCanSpawn; diff --git a/Remapped-Spigot-Server-Patches/0287-Implement-furnace-cook-speed-multiplier-API.patch b/Remapped-Spigot-Server-Patches/0287-Implement-furnace-cook-speed-multiplier-API.patch new file mode 100644 index 000000000..4b104b38e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0287-Implement-furnace-cook-speed-multiplier-API.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tassu +Date: Thu, 13 Sep 2018 08:45:21 +0300 +Subject: [PATCH] Implement furnace cook speed multiplier API + +Signed-off-by: Tassu + +Fixed an issue where a furnace's cook-speed multiplier rounds down +to the nearest Integer when updating its current cook time. + +Modified by: Eric Su + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 8c55c1d88ef2e20e82bcdae0b9b3d381e562051f..4126a36dbc7750108a883f0be14dcb0d2e6d7ae8 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -38,6 +38,7 @@ import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec3; + // CraftBukkit start ++import java.util.List; + import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.craftbukkit.inventory.CraftItemStack; +@@ -56,6 +57,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + protected NonNullList items; + public int litTime; + private int litDuration; ++ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API + public int cookingProgress; + public int cookingTotalTime; + protected final ContainerData dataAccess; +@@ -256,6 +258,11 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.recipesUsed.put(new ResourceLocation(s), nbttagcompound1.getInt(s)); + } + ++ // Paper start - cook speed API ++ if (tag.contains("Paper.CookSpeedMultiplier")) { ++ this.cookSpeedMultiplier = tag.getDouble("Paper.CookSpeedMultiplier"); ++ } ++ // Paper end + } + + @Override +@@ -264,6 +271,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + tag.putShort("BurnTime", (short) this.litTime); + tag.putShort("CookTime", (short) this.cookingProgress); + tag.putShort("CookTimeTotal", (short) this.cookingTotalTime); ++ tag.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API + ContainerHelper.saveAllItems(tag, this.items); + CompoundTag nbttagcompound1 = new CompoundTag(); + +@@ -324,7 +332,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + + if (this.isLit() && this.canBurn(irecipe)) { + ++this.cookingProgress; +- if (this.cookingProgress == this.cookingTotalTime) { ++ if (this.cookingProgress >= this.cookingTotalTime) { // Paper - cook speed multiplier API + this.cookingProgress = 0; + this.cookingTotalTime = this.getTotalCookTime(); + this.burn(irecipe); +@@ -424,9 +432,13 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + } + } + +- protected int getTotalCookTime() { +- return (this.hasLevel()) ? (Integer) this.level.getRecipeManager().getRecipeFor((RecipeType) this.recipeType, this, this.level).map(AbstractCookingRecipe::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 // Eclipse fail ++ // Paper begin - Expose this function so CraftFurnace can correctly scale the total cooking time to a new multiplier ++ public int getTotalCookTime() { ++ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ ++ int cookTime = (this.hasLevel()) ? (Integer) this.level.getRecipeManager().getRecipeFor((RecipeType) this.recipeType, this, this.level).map(AbstractCookingRecipe::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 // Eclipse fail ++ return (int) Math.ceil (cookTime / this.cookSpeedMultiplier); + } ++ // Paper end + + public static boolean isFuel(ItemStack stack) { + return getFuel().containsKey(stack.getItem()); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +index 5028a6388f95a14df8d1590cddd7414d8de5bf78..a69785c331c6cee34ba4e93f47865ab8e29ec9b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +@@ -63,4 +63,20 @@ public abstract class CraftFurnace extends + public void setCookTimeTotal(int cookTimeTotal) { + this.getSnapshot().cookingTotalTime = cookTimeTotal; + } ++ ++ // Paper start - cook speed multiplier API ++ @Override ++ public double getCookSpeedMultiplier() { ++ return this.getSnapshot().cookSpeedMultiplier; ++ } ++ ++ @Override ++ public void setCookSpeedMultiplier(double multiplier) { ++ com.google.common.base.Preconditions.checkArgument(multiplier >= 0, "Furnace speed multiplier cannot be negative"); ++ com.google.common.base.Preconditions.checkArgument(multiplier <= 200, "Furnace speed multiplier cannot more than 200"); ++ T snapshot = this.getSnapshot(); ++ snapshot.cookSpeedMultiplier = multiplier; ++ snapshot.cookingTotalTime = snapshot.getTotalCookTime(); // Update the snapshot's current total cook time to scale with the newly set multiplier ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0288-PreSpawnerSpawnEvent.patch b/Remapped-Spigot-Server-Patches/0288-PreSpawnerSpawnEvent.patch new file mode 100644 index 000000000..cb777e4c2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0288-PreSpawnerSpawnEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 18 Sep 2018 23:53:23 +0100 +Subject: [PATCH] PreSpawnerSpawnEvent + +This adds a separate event before an entity is spawned by a spawner +which contains the location of the spawner too similarly to how the +SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for +spawners. + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index ac572eba10a7239d71dfae060f623b076d4252ce..1ce675d0d24ceb5724f5ac2d8f671e38f2735f74 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -132,11 +132,11 @@ public abstract class BaseSpawner { + + org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); + if (type != null) { +- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; +- event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( + MCUtil.toLocation(world, d3, d4, d5), + type, +- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ MCUtil.toLocation(world, blockposition) + ); + if (!event.callEvent()) { + flag = true; diff --git a/Remapped-Spigot-Server-Patches/0289-Catch-JsonParseException-in-Entity-and-TE-names.patch b/Remapped-Spigot-Server-Patches/0289-Catch-JsonParseException-in-Entity-and-TE-names.patch new file mode 100644 index 000000000..a418dc54d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0289-Catch-JsonParseException-in-Entity-and-TE-names.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 22 Sep 2018 15:56:59 -0400 +Subject: [PATCH] Catch JsonParseException in Entity and TE names + +As a result, data that no longer parses correctly will not crash the server +instead just logging the exception and continuing (and in most cases should +fix the data) + +Player data is fixed pretty much immediately but some block data (like +Shulkers) may need to be changed in order for it to re-save properly + +No more crashing though. + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 68c3f069f8f832ab3d146748348aded69b5ad823..1fecc81b25109592907623741225a6222a8c5ccc 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -7,6 +7,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.network.chat.Component; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; +@@ -514,4 +516,19 @@ public final class MCUtil { + return null; + } + } ++ ++ @Nullable ++ public static Component getBaseComponentFromNbt(String key, CompoundTag compound) { ++ if (!compound.contains(key)) { ++ return null; ++ } ++ String string = compound.getString(key); ++ try { ++ return Component.Serializer.jsonToComponent(string); ++ } catch (com.google.gson.JsonParseException e) { ++ org.bukkit.Bukkit.getLogger().warning("Unable to parse " + key + " from " + compound +": " + e.getMessage()); ++ } ++ ++ return null; ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index 9ad077259563a3d960d32a59d4b6fc3cfbe3440c..00dc4cd436023b946d7005f17a7ba983a4bbdfb6 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -12,6 +12,7 @@ import net.minecraft.commands.CommandSourceStack; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TextComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.StringUtil; +@@ -72,7 +73,7 @@ public abstract class BaseCommandBlock implements CommandSource { + this.command = tag.getString("Command"); + this.successCount = tag.getInt("SuccessCount"); + if (tag.contains("CustomName", 8)) { +- this.setName(Component.Serializer.fromJson(tag.getString("CustomName"))); ++ this.setName(MCUtil.getBaseComponentFromNbt("CustomName", tag)); // Paper - Catch ParseException + } + + if (tag.contains("TrackOutput", 1)) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java +index 2e3ec85e7bd1c375db0662dba2617d8924dbd2a6..2c885be67b9c992b96f5caf78130d46abb455ceb 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java +@@ -9,6 +9,7 @@ import net.minecraft.nbt.ListTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; ++import net.minecraft.server.MCUtil; + import net.minecraft.world.Nameable; + import net.minecraft.world.item.DyeColor; + import net.minecraft.world.item.ItemStack; +@@ -70,7 +71,7 @@ public class BannerBlockEntity extends BlockEntity implements Nameable { + public void load(BlockState state, CompoundTag tag) { + super.load(state, tag); + if (tag.contains("CustomName", 8)) { +- this.name = Component.Serializer.fromJson(tag.getString("CustomName")); ++ this.name = MCUtil.getBaseComponentFromNbt("CustomName", tag); // Paper - Catch ParseException + } + + if (this.hasLevel()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index a7358274fe367a113b304a5ce332b8dcf721b7af..2b420109e9bed184aaa4ffbcee666b4c325c5a28 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -4,6 +4,7 @@ import javax.annotation.Nullable; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.Container; +@@ -30,7 +31,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + super.load(state, tag); + this.lockKey = LockCode.fromTag(tag); + if (tag.contains("CustomName", 8)) { +- this.name = Component.Serializer.fromJson(tag.getString("CustomName")); ++ this.name = MCUtil.getBaseComponentFromNbt("CustomName", tag); // Paper - Catch ParseException + } + + } diff --git a/Remapped-Spigot-Server-Patches/0290-Honor-EntityAgeable.ageLock.patch b/Remapped-Spigot-Server-Patches/0290-Honor-EntityAgeable.ageLock.patch new file mode 100644 index 000000000..a2b0c4fa7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0290-Honor-EntityAgeable.ageLock.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Sep 2018 20:59:53 -0500 +Subject: [PATCH] Honor EntityAgeable.ageLock + + +diff --git a/src/main/java/net/minecraft/world/entity/AgableMob.java b/src/main/java/net/minecraft/world/entity/AgableMob.java +index d7c19e5607bcf92c874b3656c2742f4c84dceb12..354311dc541588212a2eacba38e60c7e34aa4c2b 100644 +--- a/src/main/java/net/minecraft/world/entity/AgableMob.java ++++ b/src/main/java/net/minecraft/world/entity/AgableMob.java +@@ -82,6 +82,7 @@ public abstract class AgableMob extends PathfinderMob { + } + + public void ageUp(int age, boolean overGrow) { ++ if (ageLocked) return; // Paper - GH-1459 + int j = this.getAge(); + int k = j; + diff --git a/Remapped-Spigot-Server-Patches/0291-Configurable-connection-throttle-kick-message.patch b/Remapped-Spigot-Server-Patches/0291-Configurable-connection-throttle-kick-message.patch new file mode 100644 index 000000000..354aa4e66 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0291-Configurable-connection-throttle-kick-message.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 2 Oct 2018 09:57:50 +0100 +Subject: [PATCH] Configurable connection throttle kick message + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 62621562137cba4804f0465c58d25ca2786328e5..7178b37f7978c7e9031a22726005c5099fd78fe0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -281,6 +281,11 @@ public class PaperConfig { + authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); + } + ++ public static String connectionThrottleKickMessage = "Connection throttled! Please wait before reconnecting."; ++ private static void connectionThrottleKickMessage() { ++ connectionThrottleKickMessage = getString("messages.kick.connection-throttle", connectionThrottleKickMessage); ++ } ++ + private static void savePlayerData() { + Object val = config.get("settings.save-player-data"); + if (val instanceof Boolean) { +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 45c9dc9c2a580a5cd57fd4e891fbaa2b1336f5c5..6f98be2b9b00f71dd041e7511c70166fdecf0749 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -50,7 +50,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + synchronized (throttleTracker) { + if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { + throttleTracker.put(address, currentTime); +- TranslatableComponent chatmessage = new TranslatableComponent("Connection throttled! Please wait before reconnecting."); ++ TranslatableComponent chatmessage = new TranslatableComponent(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage); // Paper - Configurable connection throttle kick message + this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); + this.connection.disconnect(chatmessage); + return; diff --git a/Remapped-Spigot-Server-Patches/0292-Hook-into-CB-plugin-rewrites.patch b/Remapped-Spigot-Server-Patches/0292-Hook-into-CB-plugin-rewrites.patch new file mode 100644 index 000000000..06025aa8c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0292-Hook-into-CB-plugin-rewrites.patch @@ -0,0 +1,184 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 3 Oct 2018 20:09:18 -0400 +Subject: [PATCH] Hook into CB plugin rewrites + +Allows us to do fun stuff like rewrite the OBC util fastutil location to +our own relocation. Also lets us rewrite NMS calls for when we're +debugging in an IDE pre-relocate. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 45cbdcfc131bbc0e2bf23cc30a572df72eecd51c..49e5a86c223f1b28ce7da6ced276b2f880777856 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -6,7 +6,9 @@ import java.io.FileOutputStream; + import java.io.InputStream; + import java.util.Arrays; + import java.util.Enumeration; ++import java.util.HashMap; + import java.util.HashSet; ++import java.util.Map; + import java.util.Set; + import java.util.jar.JarEntry; + import java.util.jar.JarFile; +@@ -20,10 +22,15 @@ import org.bukkit.plugin.AuthorNagException; + import org.objectweb.asm.ClassReader; + import org.objectweb.asm.ClassVisitor; + import org.objectweb.asm.ClassWriter; ++import org.objectweb.asm.FieldVisitor; ++import org.objectweb.asm.Handle; ++import org.objectweb.asm.Label; + import org.objectweb.asm.MethodVisitor; + import org.objectweb.asm.Opcodes; + import org.objectweb.asm.Type; + ++import javax.annotation.Nonnull; ++ + /** + * This file is imported from Commodore. + * +@@ -46,6 +53,42 @@ public class Commodore + "org/bukkit/inventory/ItemStack (I)V setTypeId" + ) ); + ++ // Paper start - Plugin rewrites ++ private static final Map SEARCH_AND_REMOVE = initReplacementsMap(); ++ private static Map initReplacementsMap() ++ { ++ Map getAndRemove = new HashMap<>(); ++ // Be wary of maven shade's relocations ++ getAndRemove.put( "org/bukkit/".concat( "craftbukkit/libs/it/unimi/dsi/fastutil/" ), "org/bukkit/".concat( "craftbukkit/libs/" ) ); // Remap fastutil to our location ++ ++ if ( Boolean.getBoolean( "debug.rewriteForIde" ) ) ++ { ++ // unversion incoming calls for pre-relocate debug work ++ final String NMS_REVISION_PACKAGE = "v1_16_R3/"; ++ ++ getAndRemove.put( "net/minecraft/".concat( "server/" + NMS_REVISION_PACKAGE ), NMS_REVISION_PACKAGE ); ++ getAndRemove.put( "org/bukkit/".concat( "craftbukkit/" + NMS_REVISION_PACKAGE ), NMS_REVISION_PACKAGE ); ++ } ++ ++ return getAndRemove; ++ } ++ ++ @Nonnull ++ private static String getOriginalOrRewrite(@Nonnull String original) ++ { ++ String rewrite = null; ++ for ( Map.Entry entry : SEARCH_AND_REMOVE.entrySet() ) ++ { ++ if ( original.contains( entry.getKey() ) ) ++ { ++ rewrite = original.replace( entry.getValue(), "" ); ++ } ++ } ++ ++ return rewrite != null ? rewrite : original; ++ } ++ // Paper end ++ + public static void main(String[] args) + { + OptionParser parser = new OptionParser(); +@@ -130,15 +173,86 @@ public class Commodore + + cr.accept( new ClassVisitor( Opcodes.ASM9, cw ) + { ++ // Paper start - Rewrite plugins ++ @Override ++ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ if ( signature != null ) { ++ signature = getOriginalOrRewrite( signature ); ++ } ++ ++ return super.visitField( access, name, desc, signature, value) ; ++ } ++ // Paper end ++ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) + { + return new MethodVisitor( api, super.visitMethod( access, name, desc, signature, exceptions ) ) + { ++ // Paper start - Plugin rewrites ++ @Override ++ public void visitInvokeDynamicInsn(String name, String desc, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) ++ { ++ // Paper start - Rewrite plugins ++ name = getOriginalOrRewrite( name ); ++ if ( desc != null ) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ } ++ // Paper end ++ ++ super.visitInvokeDynamicInsn( name, desc, bootstrapMethodHandle, bootstrapMethodArguments ); ++ } ++ ++ @Override ++ public void visitTypeInsn(int opcode, String type) ++ { ++ type = getOriginalOrRewrite( type ); ++ ++ super.visitTypeInsn( opcode, type ); ++ } ++ ++ @Override ++ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { ++ for ( int i = 0; i < local.length; i++ ) ++ { ++ if ( !( local[i] instanceof String ) ) { continue; } ++ ++ local[i] = getOriginalOrRewrite( (String) local[i] ); ++ } ++ ++ for ( int i = 0; i < stack.length; i++ ) ++ { ++ if ( !( stack[i] instanceof String ) ) { continue; } ++ ++ stack[i] = getOriginalOrRewrite( (String) stack[i] ); ++ } ++ ++ super.visitFrame( type, nLocal, local, nStack, stack ); ++ } ++ ++ @Override ++ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) ++ { ++ descriptor = getOriginalOrRewrite( descriptor ); ++ ++ super.visitLocalVariable( name, descriptor, signature, start, end, index ); ++ } ++ // Paper end + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) + { ++ // Paper start - Rewrite plugins ++ owner = getOriginalOrRewrite( owner ); ++ if ( desc != null ) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ } ++ // Paper end ++ + if ( owner.equals( "org/bukkit/block/Biome" ) ) + { + switch ( name ) +@@ -270,6 +384,14 @@ public class Commodore + return; + } + ++ // Paper start - Rewrite plugins ++ owner = getOriginalOrRewrite( owner) ; ++ if (desc != null) ++ { ++ desc = getOriginalOrRewrite(desc); ++ } ++ // Paper end ++ + if ( modern ) + { + if ( owner.equals( "org/bukkit/Material" ) ) diff --git a/Remapped-Spigot-Server-Patches/0293-Allow-setting-the-vex-s-summoner.patch b/Remapped-Spigot-Server-Patches/0293-Allow-setting-the-vex-s-summoner.patch new file mode 100644 index 000000000..ee472ab64 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0293-Allow-setting-the-vex-s-summoner.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 6 Oct 2018 21:47:44 -0500 +Subject: [PATCH] Allow setting the vex's summoner + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java +index ec4f6d96360e759ffc19de838fdbf3027164a424..a4be2bddc5f51601d419647a280c89f7101371f2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vex.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java +@@ -165,6 +165,7 @@ public class Vex extends Monster { + this.setVexFlag(1, charging); + } + ++ public void setOwner(Mob entityinsentient) { setOwner(entityinsentient); } // Paper - OBFHELPER + public void setOwner(Mob owner) { + this.owner = owner; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 56bec4350f36a94d4dfa71a234872a795c2dcb3f..07c470f8b049bea930337abc1cc87f4669d2f11a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -20,6 +20,10 @@ public class CraftVex extends CraftMonster implements Vex { + net.minecraft.world.entity.Mob owner = getHandle().getOwner(); + return owner != null ? (org.bukkit.entity.Mob) owner.getBukkitEntity() : null; + } ++ ++ public void setSummoner(org.bukkit.entity.Mob summoner) { ++ getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); ++ } + // Paper end + + @Override diff --git a/Remapped-Spigot-Server-Patches/0294-Add-sun-related-API.patch b/Remapped-Spigot-Server-Patches/0294-Add-sun-related-API.patch new file mode 100644 index 000000000..32037c620 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0294-Add-sun-related-API.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 Oct 2018 00:54:21 -0500 +Subject: [PATCH] Add sun related API + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index f3690ea49cf90c816b8b3554b47d6f2d9dfbe016..29a2eeee9f2011ed6fcc44f19041f616decfdb38 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1595,6 +1595,7 @@ public abstract class Mob extends LivingEntity { + + } + ++ public boolean isInDaylight() { return this.isSunBurnTick(); } // Paper - OBFHELPER + protected boolean isSunBurnTick() { + if (this.level.isDay() && !this.level.isClientSide) { + float f = this.getBrightness(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 57a2af56b53567371fdb6d0a55866e1e4e37cf3b..7b5abccac9793811bd56340c8f9d23806e832365 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -847,6 +847,13 @@ public class CraftWorld implements World { + } + } + ++ // Paper start ++ @Override ++ public boolean isDayTime() { ++ return getHandle().isDay(); ++ } ++ // Paper end ++ + @Override + public long getGameTime() { + return world.levelData.getGameTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index b5fe55a77c8558cf2ea32689ff57911530df75f9..1e3a0851c75d8067d2699f00bb3f6621d1d739d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -77,4 +77,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public long getSeed() { + return getHandle().lootTableSeed; + } ++ ++ // Paper start ++ @Override ++ public boolean isInDaylight() { ++ return getHandle().isInDaylight(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0295-Turtle-API.patch b/Remapped-Spigot-Server-Patches/0295-Turtle-API.patch new file mode 100644 index 000000000..042449088 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0295-Turtle-API.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 29 Sep 2018 16:08:23 -0500 +Subject: [PATCH] Turtle API + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 4584fad16a65f06e77e97a0804d88dbe83f7c5c1..c8680e795deeb68e0662eac7c760a103d1c767b4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -14,7 +14,7 @@ public abstract class MoveToBlockGoal extends Goal { + protected int nextStartTick; + protected int tryTicks; + private int maxStayTicks; +- protected BlockPos blockPos; ++ protected BlockPos blockPos;public final BlockPos getTargetPosition() { return this.blockPos; } // Paper - OBFHELPER + private boolean reachedTarget; + private final int searchRange; + private final int verticalSearchRange; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index c54f4b83b9f2fdb15ddb363be0a179a05eb3693b..42b636c4ebb6eb83c8a9f3f5f9a766d37d065dc3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -14,6 +14,7 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; +@@ -93,7 +94,7 @@ public class Turtle extends Animal { + this.entityData.set(Turtle.HOME_POS, pos); + } + +- private BlockPos getHomePos() { ++ public BlockPos getHomePos() { // Paper - public + return (BlockPos) this.entityData.get(Turtle.HOME_POS); + } + +@@ -109,31 +110,37 @@ public class Turtle extends Animal { + return (Boolean) this.entityData.get(Turtle.HAS_EGG); + } + +- private void setHasEgg(boolean hasEgg) { ++ public void setHasEgg(boolean hasEgg) { // Paper + this.entityData.set(Turtle.HAS_EGG, hasEgg); + } + ++ public final boolean isDigging() { return this.isLayingEgg(); } // Paper - OBFHELPER + public boolean isLayingEgg() { + return (Boolean) this.entityData.get(Turtle.LAYING_EGG); + } + ++ public final void setDigging(boolean digging) { this.setLayingEgg(digging); } // Paper - OBFHELPER + private void setLayingEgg(boolean diggingSand) { + this.layEggCounter = diggingSand ? 1 : 0; + this.entityData.set(Turtle.LAYING_EGG, diggingSand); + } + ++ public final boolean isGoingHome() { return this.isGoingHome(); } // Paper - OBFHELPER + private boolean isGoingHome() { + return (Boolean) this.entityData.get(Turtle.GOING_HOME); + } + ++ public final void setGoingHome(boolean goingHome) { this.setGoingHome(goingHome); } // Paper - OBFHELPER + private void setGoingHome(boolean landBound) { + this.entityData.set(Turtle.GOING_HOME, landBound); + } + ++ public final boolean isTravelling() { return this.isTravelling(); } // Paper - OBFHELPER + private boolean isTravelling() { + return (Boolean) this.entityData.get(Turtle.TRAVELLING); + } + ++ public final void setTravelling(boolean travelling) { this.setTravelling(travelling); } // Paper - OBFHELPER + private void setTravelling(boolean travelling) { + this.entityData.set(Turtle.TRAVELLING, travelling); + } +@@ -500,14 +507,17 @@ public class Turtle extends Animal { + + if (!this.turtle.isInWater() && this.isReachedTarget()) { + if (this.turtle.layEggCounter < 1) { +- this.turtle.setLayingEgg(true); ++ this.turtle.setDigging(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), MCUtil.toLocation(this.turtle.level, this.getTargetPosition())).callEvent()); // Paper + } else if (this.turtle.layEggCounter > 200) { + Level world = this.turtle.level; + + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1)).isCancelled()) { ++ // Paper start ++ int eggCount = this.turtle.random.nextInt(4) + 1; ++ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), MCUtil.toLocation(this.turtle.level, this.blockPos.above()), eggCount); ++ if (layEggEvent.callEvent() && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount())).isCancelled()) { + world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); +- world.setBlock(this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1), 3); ++ world.setBlock(this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()), 3); + } + // CraftBukkit end + this.turtle.setHasEgg(false); +@@ -636,7 +646,7 @@ public class Turtle extends Animal { + + @Override + public boolean canUse() { +- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(700) != 0 ? false : !this.turtle.getHomePos().closerThan((Position) this.turtle.position(), 64.0D))); ++ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(700) != 0 ? false : !this.turtle.getHomePos().closerThan((Position) this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +index ed08089f21c8958fc9fc7e6e73a2b6ff9108242c..e50f6051baf34981707adce56ab2d3e1f341fb4c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +@@ -24,4 +24,36 @@ public class CraftTurtle extends CraftAnimals implements Turtle { + public EntityType getType() { + return EntityType.TURTLE; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.Location getHome() { ++ return net.minecraft.server.MCUtil.toLocation(getHandle().level, getHandle().getHomePos()); ++ } ++ ++ @Override ++ public void setHome(org.bukkit.Location location) { ++ getHandle().setHomePos(net.minecraft.server.MCUtil.toBlockPosition(location)); ++ } ++ ++ @Override ++ public boolean isGoingHome() { ++ return getHandle().isGoingHome(); ++ } ++ ++ @Override ++ public boolean isDigging() { ++ return getHandle().isDigging(); ++ } ++ ++ @Override ++ public boolean hasEgg() { ++ return getHandle().hasEgg(); ++ } ++ ++ @Override ++ public void setHasEgg(boolean hasEgg) { ++ getHandle().setHasEgg(hasEgg); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0296-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch b/Remapped-Spigot-Server-Patches/0296-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch new file mode 100644 index 000000000..685d458bb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0296-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 17 Oct 2018 19:17:27 -0400 +Subject: [PATCH] MC-50319: Check other worlds for shooter of projectiles + +Say a player shoots an arrow through a nether portal, the game +would lose the shooter for determining things such as Player Kills, +because the entity is in another world. + +If the projectile fails to find the shooter in the current world, check +other worlds. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index d1dd173c11d751b15c3afd4309e386931fd9cf8d..d385fb6eee5000951c350b6ced5669dc3dcce725 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -44,7 +44,18 @@ public abstract class Projectile extends Entity { + + @Nullable + public Entity getOwner() { +- return this.ownerUUID != null && this.level instanceof ServerLevel ? ((ServerLevel) this.level).getEntity(this.ownerUUID) : (this.ownerNetworkId != 0 ? this.level.getEntity(this.ownerNetworkId) : null); ++ // Paper start - MC-50319 - shooter might be in another world (arrows through portals) ++ Entity entity = this.ownerUUID != null && this.level instanceof ServerLevel ? ((ServerLevel) this.level).getEntity(this.ownerUUID) : (this.ownerNetworkId != 0 ? this.level.getEntity(this.ownerNetworkId) : null); ++ if (entity == null) { ++ for (ServerLevel world : level.getServer().getAllLevels()) { ++ entity = world.getEntity(this.ownerUUID); ++ if (entity != null) { ++ break; ++ } ++ } ++ } ++ return entity; ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0297-Call-player-spectator-target-events-and-improve-impl.patch b/Remapped-Spigot-Server-Patches/0297-Call-player-spectator-target-events-and-improve-impl.patch new file mode 100644 index 000000000..4b6ccafca --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0297-Call-player-spectator-target-events-and-improve-impl.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Caleb Bassham +Date: Fri, 28 Sep 2018 02:32:19 -0500 +Subject: [PATCH] Call player spectator target events and improve + implementation + +Use a proper teleport for teleporting to entities in different +worlds. + +Implementation improvements authored by Spottedleaf +Validate that the target entity is valid and deny spectate +requests from frozen players. + +Also, make sure the entity is spawned to the client before +sending the camera packet. If the entity isn't spawned clientside +when it receives the camera packet, then the client will not +spectate the target entity. + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 75419c866641ab654349cde6ca3fbdef701dd8d9..92139b271eb6c305787662ef8c7d221fb42296f7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1818,14 +1818,58 @@ public class ServerPlayer extends Player implements ContainerListener { + } + + public void setCamera(Entity entity) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity Event and improve implementation + Entity entity1 = this.getCamera(); + +- this.camera = (Entity) (entity == null ? this : entity); +- if (entity1 != this.camera) { +- this.connection.send(new ClientboundSetCameraPacket(this.camera)); +- this.connection.a(this.camera.getX(), this.camera.getY(), this.camera.getZ(), this.yRot, this.xRot, TeleportCause.SPECTATE); // CraftBukkit ++ if (entity == null) { ++ entity = this; + } + ++ if (entity1 == entity) return; // new spec target is the current spec target ++ ++ if (entity == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); ++ ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); ++ ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ return; ++ } ++ } ++ // Validate ++ if (entity != this) { ++ if (entity.removed || entity.shouldBeRemoved || !entity.valid || entity.level == null) { ++ MinecraftServer.LOGGER.info("Blocking player " + this.toString() + " from spectating invalid entity " + entity.toString()); ++ return; ++ } ++ if (this.isImmobile()) { ++ // use debug: clients might maliciously spam this ++ MinecraftServer.LOGGER.debug("Blocking frozen player " + this.toString() + " from spectating entity " + entity.toString()); ++ return; ++ } ++ } ++ ++ this.camera = entity; // only set after validating state ++ ++ if (entity != this) { ++ // Make sure we're in the right place ++ this.ejectPassengers(); // teleport can fail if we have passengers... ++ this.getBukkitEntity().teleport(new Location(entity.getCommandSenderWorld().getWorld(), entity.getX(), entity.getY(), entity.getZ(), this.yRot, this.xRot), TeleportCause.SPECTATE); // Correctly handle cross-world entities from api calls by using CB teleport ++ ++ // Make sure we're tracking the entity before sending ++ ChunkMap.TrackedEntity tracker = ((ServerLevel)entity.level).getChunkSource().chunkMap.entityMap.get(entity.getId()); ++ if (tracker != null) { // dumb plugins... ++ tracker.updatePlayer(this); ++ } ++ } else { ++ this.connection.teleport(this.camera.getX(), this.camera.getY(), this.camera.getZ(), this.yRot, this.xRot, TeleportCause.SPECTATE); // CraftBukkit ++ } ++ this.connection.send(new ClientboundSetCameraPacket(entity)); ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 900c9b1106a153bc386f6c3d9c11226f8ac69f86..4fd8d775790c037e82f9b0d29ed0eccf03c2dc66 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1353,6 +1353,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + // CraftBukkit start - Delegate to teleport(Location) ++ public final void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { this.a(d0, d1, d2, f, f1, cause); } // Paper - OBFHELPER + public void a(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { + this.a(d0, d1, d2, f, f1, Collections.emptySet(), cause); + } diff --git a/Remapped-Spigot-Server-Patches/0298-Add-Velocity-IP-Forwarding-Support.patch b/Remapped-Spigot-Server-Patches/0298-Add-Velocity-IP-Forwarding-Support.patch new file mode 100644 index 000000000..f1b44575c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0298-Add-Velocity-IP-Forwarding-Support.patch @@ -0,0 +1,303 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 8 Oct 2018 14:36:14 -0400 +Subject: [PATCH] Add Velocity IP Forwarding Support + +While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users +have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. +Further, the BungeeCord IP forwarding protocol still retains essentially its original +form, when there is brand new support for custom login plugin messages in 1.13. + +Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity +of messages, is packed into a binary format that is smaller than BungeeCord's +forwarding, and is integrated into the Minecraft login process by using the 1.13 +login plugin message packet. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7178b37f7978c7e9031a22726005c5099fd78fe0..3139c194f9b1bc3510d51a81f13ae43d00a3dc29 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -8,6 +8,7 @@ import java.io.IOException; + import java.lang.reflect.InvocationTargetException; + import java.lang.reflect.Method; + import java.lang.reflect.Modifier; ++import java.nio.charset.StandardCharsets; + import java.util.HashMap; + import java.util.List; + import java.util.Map; +@@ -252,7 +253,7 @@ public class PaperConfig { + } + + public static boolean isProxyOnlineMode() { +- return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); ++ return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode) || (velocitySupport && velocityOnlineMode); + } + + public static int packetInSpamThreshold = 300; +@@ -324,4 +325,18 @@ public class PaperConfig { + } + tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); + } ++ ++ public static boolean velocitySupport; ++ public static boolean velocityOnlineMode; ++ public static byte[] velocitySecretKey; ++ private static void velocitySupport() { ++ velocitySupport = getBoolean("settings.velocity-support.enabled", false); ++ velocityOnlineMode = getBoolean("settings.velocity-support.online-mode", false); ++ String secret = getString("settings.velocity-support.secret", ""); ++ if (velocitySupport && secret.isEmpty()) { ++ fatal("Velocity support is enabled, but no secret key was specified. A secret key is required!"); ++ } else { ++ velocitySecretKey = secret.getBytes(StandardCharsets.UTF_8); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5490ddb9f2ff1fc3e6088e703c246a06594076bc +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +@@ -0,0 +1,66 @@ ++package com.destroystokyo.paper.proxy; ++ ++import com.destroystokyo.paper.PaperConfig; ++import com.google.common.net.InetAddresses; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import java.net.InetAddress; ++import java.security.InvalidKeyException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++ ++import javax.crypto.Mac; ++import javax.crypto.spec.SecretKeySpec; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.resources.ResourceLocation; ++ ++public class VelocityProxy { ++ private static final int SUPPORTED_FORWARDING_VERSION = 1; ++ public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); ++ ++ public static boolean checkIntegrity(final FriendlyByteBuf buf) { ++ final byte[] signature = new byte[32]; ++ buf.readBytes(signature); ++ ++ final byte[] data = new byte[buf.readableBytes()]; ++ buf.getBytes(buf.readerIndex(), data); ++ ++ try { ++ final Mac mac = Mac.getInstance("HmacSHA256"); ++ mac.init(new SecretKeySpec(PaperConfig.velocitySecretKey, "HmacSHA256")); ++ final byte[] mySignature = mac.doFinal(data); ++ if (!MessageDigest.isEqual(signature, mySignature)) { ++ return false; ++ } ++ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { ++ throw new AssertionError(e); ++ } ++ ++ int version = buf.readVarInt(); ++ if (version != SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ return true; ++ } ++ ++ public static InetAddress readAddress(final FriendlyByteBuf buf) { ++ return InetAddresses.forString(buf.readUTF(Short.MAX_VALUE)); ++ } ++ ++ public static GameProfile createProfile(final FriendlyByteBuf buf) { ++ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUTF(16)); ++ readProperties(buf, profile); ++ return profile; ++ } ++ ++ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { ++ final int properties = buf.readVarInt(); ++ for (int i1 = 0; i1 < properties; i1++) { ++ final String name = buf.readUTF(Short.MAX_VALUE); ++ final String value = buf.readUTF(Short.MAX_VALUE); ++ final String signature = buf.readBoolean() ? buf.readUTF(Short.MAX_VALUE) : null; ++ profile.getProperties().put(name, new Property(name, value, signature)); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index 10f1e3d761af83507bf71a00092641e22d0c8049..a295845409824b930992426451ef26856d6e7c36 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -191,6 +191,7 @@ public class FriendlyByteBuf extends ByteBuf { + return this.writeVarInt(oenum.ordinal()); + } + ++ public int readVarInt() { return readVarInt(); } // Paper - OBFHELPER + public int readVarInt() { + int i = 0; + int j = 0; +@@ -231,6 +232,7 @@ public class FriendlyByteBuf extends ByteBuf { + return this; + } + ++ public UUID readUUID() { return readUUID(); } // Paper - OBFHELPER + public UUID readUUID() { + return new UUID(this.readLong(), this.readLong()); + } +@@ -358,6 +360,7 @@ public class FriendlyByteBuf extends ByteBuf { + } + } + ++ public String readUTF(int maxLength) { return this.readUtf(maxLength); } // Paper - OBFHELPER + public String readUtf(int i) { + int j = this.readVarInt(); + +diff --git a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java +index 8a0301cc4a411c4f9384331d68794ca73b797f5f..88a63635d73983afe58406c66f4ea81cd823c627 100644 +--- a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java +@@ -13,6 +13,14 @@ public class ClientboundCustomQueryPacket implements Packet { + +- private int transactionId; +- private FriendlyByteBuf data; ++ private int transactionId; public int getId() { return transactionId; } // Paper - OBFHELPER ++ private FriendlyByteBuf data; public FriendlyByteBuf getBuf() { return data; } // Paper - OBFHELPER + + public ServerboundCustomQueryPacket() {} + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 0aa3a154d68f00edcc09b947a24b2b59b1e135e6..22d6f41001977917ec75046252cbf7157b92396d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -18,9 +18,11 @@ import javax.crypto.Cipher; + import javax.crypto.SecretKey; + import net.minecraft.DefaultUncaughtExceptionHandler; + import net.minecraft.network.Connection; ++import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TextComponent; + import net.minecraft.network.chat.TranslatableComponent; ++import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; + import net.minecraft.network.protocol.login.ClientboundGameProfilePacket; + import net.minecraft.network.protocol.login.ClientboundHelloPacket; + import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket; +@@ -43,6 +45,7 @@ import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; + // CraftBukkit end ++import io.netty.buffer.Unpooled; // Paper + + public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener { + +@@ -59,6 +62,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + private SecretKey secretKey; + private ServerPlayer delayedAcceptPlayer; + public String hostname = ""; // CraftBukkit - add field ++ private int velocityLoginMessageId = -1; // Paper - Velocity support + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -211,6 +215,14 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + this.state = ServerLoginPacketListenerImpl.State.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.nonce)); + } else { ++ // Paper start - Velocity support ++ if (com.destroystokyo.paper.PaperConfig.velocitySupport) { ++ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); ++ ClientboundCustomQueryPacket packet1 = new ClientboundCustomQueryPacket(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, new FriendlyByteBuf(Unpooled.EMPTY_BUFFER)); ++ this.connection.send(packet1); ++ return; ++ } ++ // Paper end + // Spigot start + // Paper start - Cache authenticator threads + authenticatorPool.execute(new Runnable() { +@@ -312,6 +324,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + public class LoginHandler { + + public void fireEvents() throws Exception { ++ // Paper start - Velocity support ++ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && com.destroystokyo.paper.PaperConfig.velocitySupport) { ++ disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ // Paper end + String playerName = gameProfile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress(); + java.util.UUID uniqueId = gameProfile.getId(); +@@ -359,6 +377,40 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + // Spigot end + + public void handleCustomQueryPacket(ServerboundCustomQueryPacket packet) { ++ // Paper start - Velocity support ++ if (com.destroystokyo.paper.PaperConfig.velocitySupport && packet.getId() == this.velocityLoginMessageId) { ++ FriendlyByteBuf buf = packet.getBuf(); ++ if (buf == null) { ++ this.disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ ++ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { ++ this.disconnect("Unable to verify player details"); ++ return; ++ } ++ ++ java.net.SocketAddress listening = this.connection.getRemoteAddress(); ++ int port = 0; ++ if (listening instanceof java.net.InetSocketAddress) { ++ port = ((java.net.InetSocketAddress) listening).getPort(); ++ } ++ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); ++ ++ this.setGameProfile(com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf)); ++ ++ // Proceed with login ++ authenticatorPool.execute(() -> { ++ try { ++ new LoginHandler().fireEvents(); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + gameProfile.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end + this.disconnect(new TranslatableComponent("multiplayer.disconnect.unexpected_query_response")); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 5e36d705c56384f507fd85f704eae634379a27f1..c06b35f114a8d243198b66c44ef57d8c2b201361 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -680,7 +680,7 @@ public final class CraftServer implements Server { + @Override + public long getConnectionThrottle() { + // Spigot Start - Automatically set connection throttle for bungee configurations +- if (org.spigotmc.SpigotConfig.bungee) { ++ if (org.spigotmc.SpigotConfig.bungee || com.destroystokyo.paper.PaperConfig.velocitySupport) { // Paper - Velocity support + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); diff --git a/Remapped-Spigot-Server-Patches/0299-Add-more-Witch-API.patch b/Remapped-Spigot-Server-Patches/0299-Add-more-Witch-API.patch new file mode 100644 index 000000000..099a0f9cf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0299-Add-more-Witch-API.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 12 Oct 2018 14:10:46 -0500 +Subject: [PATCH] Add more Witch API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index a37ee32b46aa87be6e3eeca2892b4e7294fd1aef..bc9380782c2afba359852542837e7154c4c6cf8b 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -1,5 +1,8 @@ + package net.minecraft.world.entity.monster; + ++// Paper start ++import com.destroystokyo.paper.event.entity.WitchReadyPotionEvent; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + import java.util.Iterator; + import java.util.List; + import java.util.UUID; +@@ -49,7 +52,7 @@ public class Witch extends Raider implements RangedAttackMob { + private static final UUID SPEED_MODIFIER_DRINKING_UUID = UUID.fromString("5CD17E52-A79A-43D3-A529-90FDE04B181E"); + private static final AttributeModifier SPEED_MODIFIER_DRINKING = new AttributeModifier(Witch.SPEED_MODIFIER_DRINKING_UUID, "Drinking speed penalty", -0.25D, AttributeModifier.Operation.ADDITION); + private static final EntityDataAccessor DATA_USING_ITEM = SynchedEntityData.defineId(Witch.class, EntityDataSerializers.BOOLEAN); +- private int usingTime; ++ private int usingTime; public int getPotionUseTimeLeft() { return usingTime; } public void setPotionUseTimeLeft(int timeLeft) { usingTime = timeLeft; } // Paper - OBFHELPER + private NearestHealableRaiderTargetGoal healRaidersGoal; + private NearestAttackableWitchTargetGoal attackPlayersGoal; + +@@ -95,10 +98,12 @@ public class Witch extends Raider implements RangedAttackMob { + return SoundEvents.WITCH_DEATH; + } + ++ public void setDrinkingPotion(boolean drinkingPotion) { setUsingItem(drinkingPotion); } // Paper - OBFHELPER + public void setUsingItem(boolean drinking) { + this.getEntityData().set(Witch.DATA_USING_ITEM, drinking); + } + ++ public boolean isDrinkingPotion() { return isDrinkingPotion(); } // Paper - OBFHELPER + public boolean isDrinkingPotion() { + return (Boolean) this.getEntityData().get(Witch.DATA_USING_ITEM); + } +@@ -157,21 +162,24 @@ public class Witch extends Raider implements RangedAttackMob { + } + + if (potionregistry != null) { +- // Paper start + ItemStack potion = PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry); +- org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); +- this.setItemSlot(EquipmentSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper start - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out ++ this.setDrinkingPotion(potion); ++// org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++// this.setSlot(EnumItemSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++// // Paper end ++// this.bq = this.getItemInMainHand().k(); ++// this.v(true); ++// if (!this.isSilent()) { ++// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_WITCH_DRINK, this.getSoundCategory(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++// } ++// ++// AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); ++// ++// attributemodifiable.removeModifier(EntityWitch.bo); ++// attributemodifiable.b(EntityWitch.bo); + // Paper end +- this.usingTime = this.getMainHandItem().getUseDuration(); +- this.setUsingItem(true); +- if (!this.isSilent()) { +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); +- } +- +- AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); + +- attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING); +- attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); + } + } + +@@ -183,6 +191,24 @@ public class Witch extends Raider implements RangedAttackMob { + super.aiStep(); + } + ++ // Paper start - moved to its own method ++ public void setDrinkingPotion(ItemStack potion) { ++ org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ this.setItemSlot(EquipmentSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper end ++ this.usingTime = this.getMainHandItem().getUseDuration(); ++ this.setUsingItem(true); ++ if (!this.isSilent()) { ++ this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++ } ++ ++ AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); ++ ++ attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING); ++ attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); ++ } ++ // Paper end ++ + @Override + public SoundEvent getCelebrateSound() { + return SoundEvents.WITCH_CELEBRATE; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index d4eeb071dbbfca3ecea256228853bcb5c11f49ee..bb40b5af0f2a6a971f78350394099e3a48d5d04a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -3,6 +3,13 @@ package org.bukkit.craftbukkit.entity; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Witch; ++// Paper start ++import com.destroystokyo.paper.entity.CraftRangedEntity; ++import com.google.common.base.Preconditions; ++import org.bukkit.Material; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++// Paper end + + public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftWitch(CraftServer server, net.minecraft.world.entity.monster.Witch entity) { +@@ -23,4 +30,28 @@ public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo. + public EntityType getType() { + return EntityType.WITCH; + } ++ ++ // Paper start ++ public boolean isDrinkingPotion() { ++ return getHandle().isDrinkingPotion(); ++ } ++ ++ public int getPotionUseTimeLeft() { ++ return getHandle().getPotionUseTimeLeft(); ++ } ++ ++ @Override ++ public void setPotionUseTimeLeft(int ticks) { ++ getHandle().setPotionUseTimeLeft(ticks); ++ } ++ ++ public ItemStack getDrinkingPotion() { ++ return CraftItemStack.asCraftMirror(getHandle().getMainHandItem()); ++ } ++ ++ public void setDrinkingPotion(ItemStack potion) { ++ Preconditions.checkArgument(potion == null || potion.getType().isEmpty() || potion.getType() == Material.POTION, "must be potion, air, or null"); ++ getHandle().setDrinkingPotion(CraftItemStack.asNMSCopy(potion)); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0300-Check-Drowned-for-Villager-Aggression-Config.patch b/Remapped-Spigot-Server-Patches/0300-Check-Drowned-for-Villager-Aggression-Config.patch new file mode 100644 index 000000000..cbace85cb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0300-Check-Drowned-for-Villager-Aggression-Config.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 10 Oct 2018 21:22:44 -0500 +Subject: [PATCH] Check Drowned for Villager Aggression Config + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java +index cc9cd34f77e1f568572f312655fb2fe8e83bd733..0addfd6a1421fc99840d9229bad3ba04e524317b 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java +@@ -82,7 +82,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0D)); + this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Drowned.class})).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::okTarget)); +- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); ++ if ( level.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); + } diff --git a/Remapped-Spigot-Server-Patches/0301-Here-s-Johnny.patch b/Remapped-Spigot-Server-Patches/0301-Here-s-Johnny.patch new file mode 100644 index 000000000..e6a6846d1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0301-Here-s-Johnny.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 12 Oct 2018 01:37:22 -0500 +Subject: [PATCH] Here's Johnny! + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +index a41f61daf6cbbb13d0b86cdbad8a4cae00368653..623de661f3b56062792e3a7dbc508637aa58aca5 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +@@ -51,7 +51,7 @@ public class Vindicator extends AbstractIllager { + private static final Predicate DOOR_BREAKING_PREDICATE = (enumdifficulty) -> { + return enumdifficulty == Difficulty.NORMAL || enumdifficulty == Difficulty.HARD; + }; +- private boolean isJohnny; ++ private boolean isJohnny; public boolean isJohnny() { return isJohnny; } public void setJohnny(boolean johnny) { isJohnny = johnny; } // Paper - OBFHELPER + + public Vindicator(EntityType type, Level world) { + super(type, world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +index 3c5994e9862e5caa257ee6a21f8fba2df39c98c5..6c1569340317f7bed39eaf6e858d602234993eb3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +@@ -24,4 +24,14 @@ public class CraftVindicator extends CraftIllager implements Vindicator { + public EntityType getType() { + return EntityType.VINDICATOR; + } ++ ++ // Paper start ++ public boolean isJohnny() { ++ return getHandle().isJohnny(); ++ } ++ ++ public void setJohnny(boolean johnny) { ++ getHandle().setJohnny(johnny); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0302-Add-option-to-prevent-players-from-moving-into-unloa.patch b/Remapped-Spigot-Server-Patches/0302-Add-option-to-prevent-players-from-moving-into-unloa.patch new file mode 100644 index 000000000..565724bd3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0302-Add-option-to-prevent-players-from-moving-into-unloa.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabriele C +Date: Mon, 22 Oct 2018 17:34:10 +0200 +Subject: [PATCH] Add option to prevent players from moving into unloaded + chunks #1551 + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index f280dbff4a09bc611a9ca565c6d697d08801f53b..fbf3ccfb347a5ba6e895339e9576629d940d1aa4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -396,4 +396,9 @@ public class PaperWorldConfig { + waterOverLavaFlowSpeed = getInt("water-over-lava-flow-speed", 5); + log("Water over lava flow speed: " + waterOverLavaFlowSpeed); + } ++ ++ public boolean preventMovingIntoUnloadedChunks = false; ++ private void preventMovingIntoUnloadedChunks() { ++ preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 4fd8d775790c037e82f9b0d29ed0eccf03c2dc66..b5593300516fad767f603084aca4abcda4424db3 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -541,6 +541,13 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + speed *= 2f; // TODO: Get the speed of the vehicle instead of the player + ++ // Paper start - Prevent moving into unloaded chunks ++ if (player.level.paperConfig.preventMovingIntoUnloadedChunks && worldserver.getChunkIfLoadedImmediately((int) Math.floor(packet.getX()) >> 4, (int) Math.floor(packet.getZ()) >> 4) == null) { ++ this.connection.send(new ClientboundMoveVehiclePacket(entity)); ++ return; ++ } ++ // Paper end ++ + if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { + // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", entity.getName().getString(), this.player.getName().getString(), d6, d7, d8); +@@ -1139,9 +1146,9 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + double d1 = this.player.getY(); + double d2 = this.player.getZ(); + double d3 = this.player.getY(); +- double d4 = packet.getX(this.player.getX()); ++ double d4 = packet.getX(this.player.getX());double toX = d4; // Paper - OBFHELPER + double d5 = packet.getY(this.player.getY()); +- double d6 = packet.getZ(this.player.getZ()); ++ double d6 = packet.getZ(this.player.getZ());double toZ = d6; // Paper - OBFHELPER + float f = packet.getYRot(this.player.yRot); + float f1 = packet.getXRot(this.player.xRot); + double d7 = d4 - this.firstGoodX; +@@ -1180,6 +1187,12 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } else { + speed = player.abilities.walkingSpeed * 10f; + } ++ // Paper start - Prevent moving into unloaded chunks ++ if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.hasChunk((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) { ++ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.yRot, this.player.xRot, Collections.emptySet()); ++ return; ++ } ++ // Paper end + + if (!this.player.isChangingDimension() && (!this.player.getLevel().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) { + float f2 = this.player.isFallFlying() ? 300.0F : 100.0F; diff --git a/Remapped-Spigot-Server-Patches/0303-Reset-players-airTicks-on-respawn.patch b/Remapped-Spigot-Server-Patches/0303-Reset-players-airTicks-on-respawn.patch new file mode 100644 index 000000000..b3525e04c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0303-Reset-players-airTicks-on-respawn.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GreenMeanie +Date: Sat, 20 Oct 2018 22:34:02 -0400 +Subject: [PATCH] Reset players airTicks on respawn + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 92139b271eb6c305787662ef8c7d221fb42296f7..b68acf219fc61e2ea811d0c732393824fa44db2d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2159,6 +2159,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + + this.setHealth(this.getMaxHealth()); ++ this.setAirSupply(this.getMaxAirTicks()); // Paper + this.remainingFireTicks = 0; + this.fallDistance = 0; + this.foodData = new FoodData(this); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e7fed1f8bb8ffb164ddcdab51f41c369d6e3103d..460c6fd61bb45247715d99445821e15e98e4c465 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2354,6 +2354,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + } + ++ public final int getMaxAirTicks() { return getMaxAirSupply(); } // Paper - OBFHELPER + public int getMaxAirSupply() { + return 300; + } diff --git a/Remapped-Spigot-Server-Patches/0304-Don-t-sleep-after-profile-lookups-if-not-needed.patch b/Remapped-Spigot-Server-Patches/0304-Don-t-sleep-after-profile-lookups-if-not-needed.patch new file mode 100644 index 000000000..01be147e2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0304-Don-t-sleep-after-profile-lookups-if-not-needed.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 23 Oct 2018 20:25:05 -0400 +Subject: [PATCH] Don't sleep after profile lookups if not needed + +Mojang was sleeping even if we had no more requests to go after +the current one finished, resulting in 100ms lost per profile lookup + +diff --git a/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +index a3ab666b5fa89aad7ee167d9aeff2f62019a4a78..8e182fdd69dba6e1c52e2f6a893534d77fb3bfaa 100644 +--- a/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java ++++ b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +@@ -43,6 +43,7 @@ public class YggdrasilGameProfileRepository implements GameProfileRepository { + } + + final int page = 0; ++ boolean hasRequested = false; // Paper + + for (final List request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) { + int failCount = 0; +@@ -68,6 +69,12 @@ public class YggdrasilGameProfileRepository implements GameProfileRepository { + LOGGER.debug("Couldn't find profile {}", name); + callback.onProfileLookupFailed(new GameProfile(null, name), new ProfileNotFoundException("Server did not find the requested profile")); + } ++ // Paper start ++ if (!hasRequested) { ++ hasRequested = true; ++ continue; ++ } ++ // Paper end + + try { + Thread.sleep(DELAY_BETWEEN_PAGES); diff --git a/Remapped-Spigot-Server-Patches/0305-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/Remapped-Spigot-Server-Patches/0305-Improve-Server-Thread-Pool-and-Thread-Priorities.patch new file mode 100644 index 000000000..04594148f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0305-Improve-Server-Thread-Pool-and-Thread-Priorities.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 23 Oct 2018 23:14:38 -0400 +Subject: [PATCH] Improve Server Thread Pool and Thread Priorities + +Use a simple executor since Fork join is a much more complex pool +type and we are not using its capabilities. + +Set thread priorities so main thread has above normal priority over +server threads + +Allow usage of a single thread executor by not using ForkJoin so single core CPU's. + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 7b36274718b7cce24ac00530697f145648d52590..cec5ad5052c8cf6059e9b117117846bdb217748f 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -45,7 +45,7 @@ import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.Bootstrap; +-import net.minecraft.util.Mth; ++import net.minecraft.server.ServerWorkerThread; + import net.minecraft.util.datafix.DataFixers; + import net.minecraft.world.level.block.state.properties.Property; + import org.apache.logging.log4j.LogManager; +@@ -54,8 +54,8 @@ import org.apache.logging.log4j.Logger; + public class Util { + + private static final AtomicInteger WORKER_COUNT = new AtomicInteger(1); +- private static final ExecutorService BOOTSTRAP_EXECUTOR = makeExecutor("Bootstrap"); +- private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); ++ private static final ExecutorService BOOTSTRAP_EXECUTOR = a("Bootstrap", -2); // Paper - add -2 priority ++ private static final ExecutorService BACKGROUND_EXECUTOR = a("Main", -1); // Paper - add -1 priority + private static final ExecutorService IO_POOL = makeIoExecutor(); + public static LongSupplier timeSource = System::nanoTime; + public static final UUID NIL_UUID = new UUID(0L, 0L); public static final UUID getNullUUID() {return NIL_UUID;} // Paper OBFHELPER +@@ -85,30 +85,34 @@ public class Util { + return Instant.now().toEpochMilli(); + } + +- private static ExecutorService makeExecutor(String name) { +- int i = Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, 7); +- Object object; ++ private static ExecutorService a(String s, int priorityModifier) { // Paper - add priority ++ // Paper start - use simpler thread pool that allows 1 thread ++ int i = Math.min(8, Math.max(Runtime.getRuntime().availableProcessors() - 2, 1)); ++ i = Integer.getInteger("Paper.WorkerThreadCount", i); ++ ExecutorService object; + + if (i <= 0) { + object = MoreExecutors.newDirectExecutorService(); + } else { +- object = new ForkJoinPool(i, (forkjoinpool) -> { +- ForkJoinWorkerThread forkjoinworkerthread = new ForkJoinWorkerThread(forkjoinpool) { ++ object = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue(), target -> new ServerWorkerThread(target, s, priorityModifier)); ++ } ++ /* + protected void onTermination(Throwable throwable) { + if (throwable != null) { +- Util.LOGGER.warn("{} died", this.getName(), throwable); ++ SystemUtils.LOGGER.warn("{} died", this.getName(), throwable); + } else { +- Util.LOGGER.debug("{} shutdown", this.getName()); ++ SystemUtils.LOGGER.debug("{} shutdown", this.getName()); + } + + super.onTermination(throwable); + } + }; + +- forkjoinworkerthread.setName("Worker-" + name + "-" + Util.WORKER_COUNT.getAndIncrement()); ++ forkjoinworkerthread.setName("Worker-" + s + "-" + SystemUtils.c.getAndIncrement()); + return forkjoinworkerthread; +- }, Util::onThreadException, true); ++ }, SystemUtils::a, true); + } ++ }*/ // Paper end + + return (ExecutorService) object; + } +@@ -157,6 +161,7 @@ public class Util { + }); + } + ++ public static void onThreadError(Thread thread, Throwable throwable) { onThreadException(thread, throwable); } // Paper - OBFHELPER + private static void onThreadException(Thread thread, Throwable throwable) { + pauseInIde(throwable); + if (throwable instanceof CompletionException) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5a76ca77b974ff6fe862c9e05a88b507a34b44be..5faa8f3cc251b6687e33e40009db98d2aee48f2c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -284,6 +284,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 2 Nov 2018 23:11:51 -0400 +Subject: [PATCH] Optimize World Time Updates + +Splits time updates into incremental updates as well as does +the updates per world, so that we can re-use the same packet +object for every player unless they have per-player time enabled. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5faa8f3cc251b6687e33e40009db98d2aee48f2c..6ccc0be795e3ac7689de0eff6f9142d13161a29c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1314,12 +1314,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Mon, 5 Nov 2018 04:23:51 +0000 +Subject: [PATCH] Restore custom InventoryHolder support + +Upstream removed the ability to consistently use a custom InventoryHolder, +However, the implementation does not use an InventoryHolder in any form +outside of custom inventories. + +We can take that knowledge and apply some expected behavior, if we're given +an inventory holder, we should use it and return a custom inventory with the +holder, otherwise, create an inventory backed by the intended inventory, as +per upstream behavior. + +This provides a "best of both worlds" scenario: plugins with InventoryHolder's +will always work as intended in the past, those without will create implementation +based inventories. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +index 94d807c5d09f165c6eedd0a1c4026c2b833806a0..3e56de295be0d03dddd3e54fcd7b05d4b9c74dc4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +@@ -40,6 +40,11 @@ public final class CraftInventoryCreator { + } + + public Inventory createInventory(InventoryHolder holder, InventoryType type) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type); ++ } ++ //noinspection ConstantConditions // Paper end + return converterMap.get(type).createInventory(holder, type); + } + +@@ -55,6 +60,11 @@ public final class CraftInventoryCreator { + // Paper end + + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type, title); ++ } ++ //noinspection ConstantConditions // Paper end + return converterMap.get(type).createInventory(holder, type, title); + } + diff --git a/Remapped-Spigot-Server-Patches/0308-Use-Vanilla-Minecart-Speeds.patch b/Remapped-Spigot-Server-Patches/0308-Use-Vanilla-Minecart-Speeds.patch new file mode 100644 index 000000000..1eeb5272c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0308-Use-Vanilla-Minecart-Speeds.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 8 Nov 2018 21:33:09 -0500 +Subject: [PATCH] Use Vanilla Minecart Speeds + +CraftBukkit changed the values on flying speed, restore back to vanilla + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 7ba74b0a9319e29077b5afe3019a463ed3004813..1257a740a4ab79870fe89057782e8ffc6c658c14 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -99,9 +99,9 @@ public abstract class AbstractMinecart extends Entity { + private double derailedX = 0.5; + private double derailedY = 0.5; + private double derailedZ = 0.5; +- private double flyingX = 0.95; +- private double flyingY = 0.95; +- private double flyingZ = 0.95; ++ private double flyingX = 0.949999988079071D; // Paper - restore vanilla precision ++ private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision ++ private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision + public double maxSpeed = 0.4D; + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0309-Fix-SpongeAbsortEvent-handling.patch b/Remapped-Spigot-Server-Patches/0309-Fix-SpongeAbsortEvent-handling.patch new file mode 100644 index 000000000..169c739fa --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0309-Fix-SpongeAbsortEvent-handling.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 10 Nov 2018 05:15:21 +0000 +Subject: [PATCH] Fix SpongeAbsortEvent handling + +Only process drops when the block is actually going to be removed + +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 9d2e4adddae481735053c64eec0ee7259c61f1a4..fca5d175cbef24fb0ee2d0bbedc8d1c0af3eb528 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -228,6 +228,7 @@ public class Block extends BlockBehaviour implements ItemLike { + + } + ++ public static void dropNaturally(BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, @Nullable BlockEntity tileentity) { dropResources(iblockdata, generatoraccess, blockposition, tileentity); } + public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) { + if (world instanceof ServerLevel) { + getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +index c453963ec90cd4eeec845fd0c2137e60dfdd6225..1895c75deee8da40624ba3abbd08ba7cd4f0f503 100644 +--- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +@@ -128,8 +128,11 @@ public class SpongeBlock extends Block { + // NOP + } else if (material == Material.WATER_PLANT || material == Material.REPLACEABLE_WATER_PLANT) { + BlockEntity tileentity = iblockdata.getBlock().isEntityBlock() ? world.getBlockEntity(blockposition2) : null; +- +- dropResources(iblockdata, world, blockposition2, tileentity); ++ // Paper start ++ if (block.getHandle().getMaterial() == Material.AIR) { ++ dropNaturally(iblockdata, world, blockposition2, tileentity); ++ } ++ // Paper end + } + } + world.setBlock(blockposition2, block.getHandle(), block.getFlag()); diff --git a/Remapped-Spigot-Server-Patches/0310-Don-t-allow-digging-into-unloaded-chunks.patch b/Remapped-Spigot-Server-Patches/0310-Don-t-allow-digging-into-unloaded-chunks.patch new file mode 100644 index 000000000..f5420a15f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0310-Don-t-allow-digging-into-unloaded-chunks.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Nov 2018 21:01:09 +0000 +Subject: [PATCH] Don't allow digging into unloaded chunks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index b1a53093eb5b5f359eedb6c252cf6d0d1ae8e409..d97607f2ded4977b253d3afa3bafcbe6d7f98837 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -113,8 +113,8 @@ public class ServerPlayerGameMode { + BlockState iblockdata; + + if (this.hasDelayedDestroy) { +- iblockdata = this.level.getBlockState(this.delayedDestroyPos); +- if (iblockdata.isAir()) { ++ iblockdata = this.level.getTypeIfLoaded(this.delayedDestroyPos); // Paper ++ if (iblockdata == null || iblockdata.isAir()) { // Paper + this.hasDelayedDestroy = false; + } else { + float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); +@@ -125,7 +125,13 @@ public class ServerPlayerGameMode { + } + } + } else if (this.isDestroyingBlock) { +- iblockdata = this.level.getBlockState(this.destroyPos); ++ // Paper start - don't want to do same logic as above, return instead ++ iblockdata = this.level.getTypeIfLoaded(this.destroyPos); ++ if (iblockdata == null) { ++ this.isDestroyingBlock = false; ++ return; ++ } ++ // Paper end + if (iblockdata.isAir()) { + this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); + this.lastSentState = -1; +@@ -289,10 +295,12 @@ public class ServerPlayerGameMode { + this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "stopped destroying")); + } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { + this.isDestroyingBlock = false; +- if (!Objects.equals(this.destroyPos, pos)) { ++ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { + ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: " + this.destroyPos + " " + pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled +- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); +- this.player.connection.send(new ClientboundBlockBreakAckPacket(this.destroyPos, this.level.getBlockState(this.destroyPos), action, true, "aborted mismatched destroying")); ++ BlockState type = this.level.getTypeIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here ++ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); // Paper ++ if (type != null) this.player.connection.send(new ClientboundBlockBreakAckPacket(this.destroyPos, type, action, true, "aborted mismatched destroying")); // Paper ++ this.destroyPos = BlockPos.ZERO; // Paper + } + + this.level.destroyBlockProgress(this.player.getId(), pos, -1); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b5593300516fad767f603084aca4abcda4424db3..a6ad7747396f94def688b4d2783137180dc2bb84 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1518,6 +1518,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: ++ // Paper start - Don't allow digging in unloaded chunks ++ if (this.player.level.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ return; ++ } ++ // Paper end - Don't allow digging in unloaded chunks + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.server.getMaxBuildHeight()); + return; + default: diff --git a/Remapped-Spigot-Server-Patches/0311-Book-Size-Limits.patch b/Remapped-Spigot-Server-Patches/0311-Book-Size-Limits.patch new file mode 100644 index 000000000..ef45bdd8d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0311-Book-Size-Limits.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 16 Nov 2018 23:08:50 -0500 +Subject: [PATCH] Book Size Limits + +Puts some limits on the size of books. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 3139c194f9b1bc3510d51a81f13ae43d00a3dc29..13edb435b3fa65b4980bd7472aa5a5196f4d5b2b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -339,4 +339,11 @@ public class PaperConfig { + velocitySecretKey = secret.getBytes(StandardCharsets.UTF_8); + } + } ++ ++ public static int maxBookPageSize = 2560; ++ public static double maxBookTotalSizeMultiplier = 0.98D; ++ private static void maxBookSize() { ++ maxBookPageSize = getInt("settings.book-size.page-max", maxBookPageSize); ++ maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a6ad7747396f94def688b4d2783137180dc2bb84..b1c505d3fdcc2fb3496f80bee85e4895b9069dcb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -999,6 +999,52 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { ++ // Paper start ++ ItemStack testStack = packet.getBook(); ++ if (!craftServer.isPrimaryThread() && !testStack.isEmpty() && testStack.getTag() != null) { ++ ListTag pageList = testStack.getTag().getList("pages", 8); ++ if (pageList.size() > 100) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with too many pages"); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ long byteTotal = 0; ++ int maxBookPageSize = com.destroystokyo.paper.PaperConfig.maxBookPageSize; ++ double multiplier = Math.max(0.3D, Math.min(1D, com.destroystokyo.paper.PaperConfig.maxBookTotalSizeMultiplier)); ++ long byteAllowed = maxBookPageSize; ++ for (int i = 0; i < pageList.size(); ++i) { ++ String testString = pageList.getString(i); ++ int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; ++ if (byteLength > 256 * 4) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ byteTotal += byteLength; ++ int length = testString.length(); ++ int multibytes = 0; ++ if (byteLength != length) { ++ for (char c : testString.toCharArray()) { ++ if (c > 127) { ++ multibytes++; ++ } ++ } ++ } ++ byteAllowed += (maxBookPageSize * Math.min(1, Math.max(0.1D, (double) length / 255D))) * multiplier; ++ ++ if (multibytes > 1) { ++ // penalize MB ++ byteAllowed -= multibytes; ++ } ++ } ++ ++ if (byteTotal > byteAllowed) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ } ++ // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { + this.disconnect("Book edited too quickly!"); diff --git a/Remapped-Spigot-Server-Patches/0312-Make-the-default-permission-message-configurable.patch b/Remapped-Spigot-Server-Patches/0312-Make-the-default-permission-message-configurable.patch new file mode 100644 index 000000000..8d9bfec2a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0312-Make-the-default-permission-message-configurable.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Nov 2018 19:49:56 +0000 +Subject: [PATCH] Make the default permission message configurable + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 13edb435b3fa65b4980bd7472aa5a5196f4d5b2b..469f78775b03cf363d88e35c69c0dc185c22547c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -19,6 +19,7 @@ import java.util.regex.Pattern; + import com.google.common.collect.Lists; + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; + import org.bukkit.command.Command; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; +@@ -287,6 +288,11 @@ public class PaperConfig { + connectionThrottleKickMessage = getString("messages.kick.connection-throttle", connectionThrottleKickMessage); + } + ++ public static String noPermissionMessage = "&cI'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error."; ++ private static void noPermissionMessage() { ++ noPermissionMessage = ChatColor.translateAlternateColorCodes('&', getString("messages.no-permission", noPermissionMessage)); ++ } ++ + private static void savePlayerData() { + Object val = config.get("settings.save-player-data"); + if (val instanceof Boolean) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c06b35f114a8d243198b66c44ef57d8c2b201361..590687d5941cbed3a330bcd749f8d52cd4b5e3ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2340,6 +2340,11 @@ public final class CraftServer implements Server { + return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; + } + ++ @Override ++ public String getPermissionMessage() { ++ return com.destroystokyo.paper.PaperConfig.noPermissionMessage; ++ } ++ + @Override + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return createProfile(uuid, null); diff --git a/Remapped-Spigot-Server-Patches/0313-Prevent-rayTrace-from-loading-chunks.patch b/Remapped-Spigot-Server-Patches/0313-Prevent-rayTrace-from-loading-chunks.patch new file mode 100644 index 000000000..d300ff7c9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0313-Prevent-rayTrace-from-loading-chunks.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 26 Nov 2018 19:21:58 -0500 +Subject: [PATCH] Prevent rayTrace from loading chunks + +ray tracing into an unloaded chunk should be treated as a miss +this saves a ton of lag for when AI tries to raytrace near unloaded chunks. + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 2feb187f62be5cf5d354a1e806087417cc189ab1..07fe8e72a7ff01d872dd5b04ccbc435e82ebe990 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -58,7 +58,15 @@ public interface BlockGetter { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult rayTraceBlock(ClipContext raytrace1, BlockPos blockposition) { +- BlockState iblockdata = this.getBlockState(blockposition); ++ // Paper start - Prevent raytrace from loading chunks ++ BlockState iblockdata = this.getTypeIfLoaded(blockposition); ++ if (iblockdata == null) { ++ // copied the last function parameter (listed below) ++ Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); ++ ++ return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); ++ } ++ // Paper end + FluidState fluid = this.getFluidState(blockposition); + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); diff --git a/Remapped-Spigot-Server-Patches/0314-Handle-Large-Packets-disconnecting-client.patch b/Remapped-Spigot-Server-Patches/0314-Handle-Large-Packets-disconnecting-client.patch new file mode 100644 index 000000000..3b9703f20 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0314-Handle-Large-Packets-disconnecting-client.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 27 Nov 2018 21:18:06 -0500 +Subject: [PATCH] Handle Large Packets disconnecting client + +If a players inventory is too big to send in a single packet, +split the inventory set into multiple packets instead. + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 7f4681910751047a26fdfc6b59bc460449c02001..35191d968fd30db16213540ef7121f4dede68e68 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -12,6 +12,7 @@ import io.netty.channel.epoll.EpollEventLoopGroup; + import io.netty.channel.local.LocalChannel; + import io.netty.channel.local.LocalServerChannel; + import io.netty.channel.nio.NioEventLoopGroup; ++import io.netty.handler.codec.EncoderException; // Paper + import io.netty.handler.timeout.TimeoutException; + import io.netty.util.AttributeKey; + import io.netty.util.concurrent.Future; +@@ -107,6 +108,15 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { ++ // Paper start ++ if (throwable instanceof EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException) { ++ if (((PacketEncoder.PacketTooLargeException) throwable.getCause()).getPacket().packetTooLarge(this)) { ++ return; ++ } else { ++ throwable = throwable.getCause(); ++ } ++ } ++ // Paper end + if (throwable instanceof SkipPacketException) { + Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); + } else { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index d36d0424bcd4811af892f5f76fdcefda2af1ad33..a58c4fa8be7193b8acae5ea18a9780866312d768 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -53,7 +53,31 @@ public class PacketEncoder extends MessageToByteEncoder> { + throw throwable; + } + } ++ ++ // Paper start ++ int packetLength = bytebuf.readableBytes(); ++ if (packetLength > MAX_PACKET_SIZE) { ++ throw new PacketTooLargeException(packet, packetLength); ++ } ++ // Paper end + } + } + } ++ ++ // Paper start ++ private static int MAX_PACKET_SIZE = 2097152; ++ ++ public static class PacketTooLargeException extends RuntimeException { ++ private final Packet packet; ++ ++ PacketTooLargeException(Packet packet, int packetLength) { ++ super("PacketTooLarge - " + packet.getClass().getSimpleName() + " is " + packetLength + ". Max is " + MAX_PACKET_SIZE); ++ this.packet = packet; ++ } ++ ++ public Packet getPacket() { ++ return packet; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index 29f10af7feabe2765f576586db4e3dba320dceda..9914a82ba0ec146ab13fe94c4dbf0ebf64926536 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -12,6 +12,12 @@ public interface Packet { + + void handle(T listener); + ++ // Paper start ++ default boolean packetTooLarge(NetworkManager manager) { ++ return false; ++ } ++ // Paper end ++ + default boolean isSkippable() { + return false; + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +index 70684318c562a9c3ce566b16cd0e68cfe95cbd50..64a15dcaef40c4e16458ab71d648f9fff169a3b2 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +@@ -4,6 +4,7 @@ import java.io.IOException; + import java.util.Iterator; + import java.util.List; + import net.minecraft.core.NonNullList; ++import net.minecraft.network.Connection; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.protocol.Packet; + import net.minecraft.world.item.ItemStack; +@@ -13,6 +14,15 @@ public class ClientboundContainerSetContentPacket implements Packet items; + ++ //Paper start ++ @Override ++ public boolean packetTooLarge(Connection manager) { ++ for (int i = 0 ; i < this.items.size() ; i++) { ++ manager.send(new ClientboundContainerSetSlotPacket(this.containerId, i, this.items.get(i))); ++ } ++ return true; ++ } ++ // Paper end + public ClientboundContainerSetContentPacket() {} + + public ClientboundContainerSetContentPacket(int syncId, NonNullList contents) { +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +index 4fe15aa331ca18319ca46d1b426f0d6fd24341f0..b7d303b5f51a35504888933efef74564fa01e59d 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -91,7 +91,7 @@ public class ClientboundLevelChunkPacket implements Packet 2097152) { ++ if (i > 2097152) { // Paper - if this changes, update PacketEncoder + throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); + } else { + this.buffer = new byte[i]; diff --git a/Remapped-Spigot-Server-Patches/0315-force-entity-dismount-during-teleportation.patch b/Remapped-Spigot-Server-Patches/0315-force-entity-dismount-during-teleportation.patch new file mode 100644 index 000000000..abee5b71e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0315-force-entity-dismount-during-teleportation.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 15 Nov 2018 13:38:37 +0000 +Subject: [PATCH] force entity dismount during teleportation + +Entities must be dismounted before teleportation in order to avoid +multiple issues in the server with regards to teleportation, shamefully, +too many plugins rely on the events firing, which means that not firing +these events caues more issues than it solves; + +In order to counteract this, Entity dismount/exit vehicle events have +been modified to supress cancellation (and has a method to allow plugins +to check if this has been set), noting that cancellation will be silently +surpressed given that plugins are not expecting this event to not be cancellable. + +This is a far from ideal scenario, however: given the current state of this +event and other alternatives causing issues elsewhere, I believe that +this is going to be the best soultion all around. + +Improvements/suggestions welcome! + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index b68acf219fc61e2ea811d0c732393824fa44db2d..416c21f0a6be8d71a654e18f7ea0fa074f8fc5ff 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1258,11 +1258,13 @@ public class ServerPlayer extends Player implements ContainerListener { + } + } + +- @Override +- public void stopRiding() { ++ // Paper start ++ @Override public void stopRiding() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // paper end + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper + Entity entity1 = this.getVehicle(); + + if (entity1 != entity && this.connection != null) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 460c6fd61bb45247715d99445821e15e98e4c465..c5d6235a132818dfc78105e9d03d0687f697bb00 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2044,12 +2044,15 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + } + +- public void removeVehicle() { ++ // Paper start ++ public void removeVehicle() { stopRiding(false); } ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end + if (this.vehicle != null) { + Entity entity = this.vehicle; + + this.vehicle = null; +- if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit ++ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper + } + + } +@@ -2104,7 +2107,10 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return true; // CraftBukkit + } + +- protected boolean removePassenger(Entity entity) { // CraftBukkit ++ // Paper start ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end + if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +@@ -2114,7 +2120,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + if (getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleExitEvent event = new VehicleExitEvent( + (Vehicle) getBukkitEntity(), +- (LivingEntity) entity.getBukkitEntity() ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper + ); + // Suppress during worldgen + if (this.valid) { +@@ -2128,7 +2134,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + // CraftBukkit end + // Spigot start +- org.spigotmc.event.entity.EntityDismountEvent event = new org.spigotmc.event.entity.EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); ++ org.spigotmc.event.entity.EntityDismountEvent event = new org.spigotmc.event.entity.EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 7a2292e6907a2ae2026bd7243e864bd8300ecafa..29d4ed42e5d763639a50d849ef274c4d848bc9c9 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3012,11 +3012,13 @@ public abstract class LivingEntity extends Entity { + return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 4) != 0; + } + +- @Override +- public void stopRiding() { ++ // Paper start ++ @Override public void stopRiding() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // Paper end + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - suppress + if (entity != null && entity != this.getVehicle() && !this.level.isClientSide) { + this.dismountVehicle(entity); + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 3aadc4ab5fe7b2ee9e20e0789ddcfe750599972f..0685920073a6a2b2c6a80018d0c9009b2ef860c4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1036,9 +1036,11 @@ public abstract class Player extends LivingEntity { + return -0.35D; + } + +- @Override +- public void removeVehicle() { +- super.removeVehicle(); ++ // Paper start ++ @Override public void removeVehicle() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // Paper end ++ super.stopRiding(suppressCancellation); // Paper - suppress + this.boardingCooldown = 0; + } + diff --git a/Remapped-Spigot-Server-Patches/0316-Add-more-Zombie-API.patch b/Remapped-Spigot-Server-Patches/0316-Add-more-Zombie-API.patch new file mode 100644 index 000000000..324b10106 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0316-Add-more-Zombie-API.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 Oct 2018 04:29:59 -0500 +Subject: [PATCH] Add more Zombie API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 94e2a8f74e74d68d4a9b82b667fbff24b7e9e629..ad4eeb15771750193a28116117992270c72a3644 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -91,6 +91,7 @@ public class Zombie extends Monster { + private int inWaterTime; + public int conversionTime; + private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field ++ private boolean shouldBurnInDay = true; // Paper + + public Zombie(EntityType type, Level world) { + super(type, world); +@@ -259,6 +260,12 @@ public class Zombie extends Monster { + super.aiStep(); + } + ++ // Paper start ++ public void stopDrowning() { ++ this.conversionTime = -1; ++ this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false); ++ } ++ // Paper end + public void startUnderWaterConversion(int ticksUntilWaterConversion) { + this.lastTick = MinecraftServer.currentTick; // CraftBukkit + this.conversionTime = ticksUntilWaterConversion; +@@ -287,9 +294,16 @@ public class Zombie extends Monster { + + } + ++ public boolean shouldBurnInDay() { return isSunSensitive(); } // Paper - OBFHELPER + protected boolean isSunSensitive() { +- return true; ++ return this.shouldBurnInDay; // Paper - use api value instead ++ } ++ ++ // Paper start ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ this.shouldBurnInDay = shouldBurnInDay; + } ++ // Paper end + + @Override + public boolean hurt(DamageSource source, float amount) { +@@ -410,6 +424,7 @@ public class Zombie extends Monster { + tag.putBoolean("CanBreakDoors", this.canBreakDoors()); + tag.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); + tag.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); ++ tag.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Paper + } + + @Override +@@ -421,7 +436,11 @@ public class Zombie extends Monster { + if (tag.contains("DrownedConversionTime", 99) && tag.getInt("DrownedConversionTime") > -1) { + this.startUnderWaterConversion(tag.getInt("DrownedConversionTime")); + } +- ++ // Paper start ++ if (tag.contains("Paper.ShouldBurnInDay")) { ++ shouldBurnInDay = tag.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 038793987338c2e4b73784a10836f85c7061175a..86f65c07806a118c49e900c59be86c2bd2eb124c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -93,6 +93,42 @@ public class CraftZombie extends CraftMonster implements Zombie { + @Override + public void setAgeLock(boolean b) { + } ++ // Paper start ++ @Override ++ public boolean isDrowning() { ++ return getHandle().isUnderWaterConverting(); ++ } ++ ++ @Override ++ public void startDrowning(int drownedConversionTime) { ++ getHandle().startUnderWaterConversion(drownedConversionTime); ++ } ++ ++ @Override ++ public void stopDrowning() { ++ getHandle().stopDrowning(); ++ } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public boolean isArmsRaised() { ++ return getHandle().isAggressive(); ++ } ++ ++ @Override ++ public void setArmsRaised(final boolean raised) { ++ getHandle().setAggressive(raised); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + + @Override + public boolean getAgeLock() { diff --git a/Remapped-Spigot-Server-Patches/0317-Add-PlayerConnectionCloseEvent.patch b/Remapped-Spigot-Server-Patches/0317-Add-PlayerConnectionCloseEvent.patch new file mode 100644 index 000000000..12d0a9084 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0317-Add-PlayerConnectionCloseEvent.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 7 Oct 2018 12:05:28 -0700 +Subject: [PATCH] Add PlayerConnectionCloseEvent + +This event is invoked when a player has disconnected. It is guaranteed that, +if the server is in online-mode, that the provided uuid and username have been +validated. + +The event is invoked for players who have not yet logged into the world, whereas +PlayerQuitEvent is only invoked on players who have logged into the world. + +The event is invoked for players who have already logged into the world, +although whether or not the player exists in the world at the time of +firing is undefined. (That is, whether the plugin can retrieve a Player object +using the event parameters is undefined). However, it is guaranteed that this +event is invoked AFTER PlayerQuitEvent, if the player has already logged into +the world. + +This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has +been called beforehand, and this event may not be called in parallel with +AsyncPlayerPreLoginEvent for the same connection. + +Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding +PlayerConnectionCloseEvent is never called. + +The event may be invoked asynchronously or synchronously. As it stands, +it is never invoked asynchronously. However, plugins should check +Event#isAsynchronous to be future-proof. + +On purpose, the deprecated PlayerPreLoginEvent event is left out of the +API spec for this event. Plugins should not be using that event, and +how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent +is undefined. + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 35191d968fd30db16213540ef7121f4dede68e68..3247ec5d6cf329ba0b7e6d5a6c3294dec2e34db4 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -350,6 +350,26 @@ public class Connection extends SimpleChannelInboundHandler> { + this.getPacketListener().a(new TranslatableComponent("multiplayer.disconnect.generic")); + } + this.queue.clear(); // Free up packet queue. ++ // Paper start - Add PlayerConnectionCloseEvent ++ final PacketListener packetListener = this.getPacketListener(); ++ if (packetListener instanceof ServerGamePacketListenerImpl) { ++ /* Player was logged in */ ++ final ServerGamePacketListenerImpl playerConnection = (ServerGamePacketListenerImpl) packetListener; ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(playerConnection.player.getUUID(), ++ playerConnection.player.getScoreboardName(), ((java.net.InetSocketAddress)address).getAddress(), false).callEvent(); ++ } else if (packetListener instanceof ServerLoginPacketListenerImpl) { ++ /* Player is login stage */ ++ final ServerLoginPacketListenerImpl loginListener = (ServerLoginPacketListenerImpl) packetListener; ++ switch (loginListener.getLoginState()) { ++ case READY_TO_ACCEPT: ++ case DELAY_ACCEPT: ++ case ACCEPTED: ++ final com.mojang.authlib.GameProfile profile = loginListener.getGameProfile(); /* Should be non-null at this stage */ ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(), ++ ((java.net.InetSocketAddress)address).getAddress(), false).callEvent(); ++ } ++ } ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 22d6f41001977917ec75046252cbf7157b92396d..9631fa93b821c7f6bc6dc707c2c82cce2ae8291e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -55,9 +55,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + private final byte[] nonce = new byte[4]; + private final MinecraftServer server; + public final Connection connection; +- private ServerLoginPacketListenerImpl.State state; ++ private ServerLoginPacketListenerImpl.State state; public final ServerLoginPacketListenerImpl.State getLoginState() { return this.state; }; // Paper - OBFHELPER + private int tick; +- private GameProfile gameProfile; private void setGameProfile(final GameProfile profile) { this.gameProfile = profile; } private GameProfile getGameProfile() { return this.gameProfile; } // Paper - OBFHELPER ++ private GameProfile gameProfile; private void setGameProfile(final GameProfile profile) { this.gameProfile = profile; } public GameProfile getGameProfile() { return this.gameProfile; } // Paper - OBFHELPER + private final String serverId; + private SecretKey secretKey; + private ServerPlayer delayedAcceptPlayer; diff --git a/Remapped-Spigot-Server-Patches/0318-Prevent-Enderman-from-loading-chunks.patch b/Remapped-Spigot-Server-Patches/0318-Prevent-Enderman-from-loading-chunks.patch new file mode 100644 index 000000000..b1074d223 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0318-Prevent-Enderman-from-loading-chunks.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 18 Dec 2018 02:15:08 +0000 +Subject: [PATCH] Prevent Enderman from loading chunks + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index d190b58bea310f4006ea3deaf0d42c502d441284..dae35d3f758e40c1edf31b11c6e11f1b7bb2dfae 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -434,7 +434,8 @@ public class EnderMan extends Monster implements NeutralMob { + int j = Mth.floor(this.enderman.getY() + random.nextDouble() * 3.0D); + int k = Mth.floor(this.enderman.getZ() - 2.0D + random.nextDouble() * 4.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return; // Paper + Block block = iblockdata.getBlock(); + Vec3 vec3d = new Vec3((double) Mth.floor(this.enderman.getX()) + 0.5D, (double) j + 0.5D, (double) Mth.floor(this.enderman.getZ()) + 0.5D); + Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D); +@@ -474,7 +475,8 @@ public class EnderMan extends Monster implements NeutralMob { + int j = Mth.floor(this.enderman.getY() + random.nextDouble() * 2.0D); + int k = Mth.floor(this.enderman.getZ() - 1.0D + random.nextDouble() * 2.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return; // Paper + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata1 = world.getBlockState(blockposition1); + BlockState iblockdata2 = this.enderman.getCarriedBlock(); diff --git a/Remapped-Spigot-Server-Patches/0319-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch b/Remapped-Spigot-Server-Patches/0319-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch new file mode 100644 index 000000000..491b5bd0f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0319-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Jan 2019 00:35:43 -0600 +Subject: [PATCH] Add APIs to replace OfflinePlayer#getLastPlayed + +Currently OfflinePlayer#getLastPlayed could more accurately be described +as "OfflinePlayer#getLastTimeTheirDataWasSaved". + +The API doc says it should return the last time the server "witnessed" +the player, whilst also saying it should return the last time they +logged in. The current implementation does neither. + +Given this interesting contradiction in the API documentation and the +current defacto implementation, I've elected to deprecate (with no +intent to remove) and replace it with two new methods, clearly named and +documented as to their purpose. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 416c21f0a6be8d71a654e18f7ea0fa074f8fc5ff..1fa4f58658ff98396eb5abfc27e19e5832d56f5a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -211,6 +211,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public int latency; + public boolean wonGame; + private int containerUpdateDelay; // Paper ++ public long loginTime; // Paper + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7c307a16ca3962db65be09a0ddd058a4ce81c7be..5b71b487836cdd2ddc75b2039f4dc0177719d345 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -164,6 +164,7 @@ public abstract class PlayerList { + } + + public void placeNewPlayer(Connection connection, ServerPlayer player) { ++ player.loginTime = System.currentTimeMillis(); // Paper + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); + GameProfile gameprofile1 = usercache.get(gameprofile.getId()); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 1eeb1d1fe54eba68652be8dba52dce8ca91d948d..19aee8c6d0989bcf263e27adab42b3e6e411b66f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -244,6 +244,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return getData() != null; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastLogin(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastLogin")) { ++ return data.getLong("LastLogin"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ @Override ++ public long getLastSeen() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastSeen(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastSeen")) { ++ return data.getLong("LastSeen"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ private CompoundTag getPaperData() { ++ CompoundTag result = getData(); ++ ++ if (result != null) { ++ if (!result.contains("Paper")) { ++ result.put("Paper", new CompoundTag()); ++ } ++ result = result.getCompound("Paper"); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public Location getBedSpawnLocation() { + CompoundTag data = getData(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a3e65028d3e0c09a65cd9c28b037fe01a2ed1d76..ba03a1cdac68dc08ed878e311adeebc531e8f2f1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -144,6 +144,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; + private String resourcePackHash; + private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit ++ private long lastSaveTime; + // Paper end + + public CraftPlayer(CraftServer server, ServerPlayer entity) { +@@ -1480,6 +1481,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.firstPlayed = firstPlayed; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ return getHandle().loginTime; ++ } ++ ++ @Override ++ public long getLastSeen() { ++ return isOnline() ? System.currentTimeMillis() : this.lastSaveTime; ++ } ++ // Paper end ++ + public void readExtraData(CompoundTag nbttagcompound) { + hasPlayedBefore = true; + if (nbttagcompound.contains("bukkit")) { +@@ -1502,6 +1515,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setExtraData(CompoundTag nbttagcompound) { ++ this.lastSaveTime = System.currentTimeMillis(); // Paper ++ + if (!nbttagcompound.contains("bukkit")) { + nbttagcompound.put("bukkit", new CompoundTag()); + } +@@ -1516,6 +1531,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + data.putLong("firstPlayed", getFirstPlayed()); + data.putLong("lastPlayed", System.currentTimeMillis()); + data.putString("lastKnownName", handle.getScoreboardName()); ++ ++ // Paper start - persist for use in offline save data ++ if (!nbttagcompound.contains("Paper")) { ++ nbttagcompound.put("Paper", new CompoundTag()); ++ } ++ ++ CompoundTag paper = nbttagcompound.getCompound("Paper"); ++ paper.putLong("LastLogin", handle.loginTime); ++ paper.putLong("LastSeen", System.currentTimeMillis()); ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0320-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/Remapped-Spigot-Server-Patches/0320-Workaround-for-vehicle-tracking-issue-on-disconnect.patch new file mode 100644 index 000000000..464520524 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0320-Workaround-for-vehicle-tracking-issue-on-disconnect.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: connorhartley +Date: Mon, 7 Jan 2019 14:43:48 -0600 +Subject: [PATCH] Workaround for vehicle tracking issue on disconnect + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 1fa4f58658ff98396eb5abfc27e19e5832d56f5a..fd2717a00a85f91ee23a1c0f929f856972892a9b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1524,6 +1524,13 @@ public class ServerPlayer extends Player implements ContainerListener { + public void disconnect() { + this.disconnected = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround an issue where the vehicle doesn't track the passenger disconnection dismount. ++ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end ++ + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } diff --git a/Remapped-Spigot-Server-Patches/0321-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch b/Remapped-Spigot-Server-Patches/0321-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch new file mode 100644 index 000000000..7b22e8625 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0321-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 31 Jan 2019 16:33:36 -0500 +Subject: [PATCH] Fire BlockPistonRetractEvent for all empty pistons + +There is an explicit check in the handling code for empty pistons that +prevents sticky pistons from firing the event. However when we look back +at the history we see that this check was originally added so that ONLY +sticky pistons would fire the retract event. I'm not sure why. +https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1092acbddf07edfa4100bc6824504ac75088e913 + +Over the course of several updates, the meaning of that field appears to +have changed from "is NOT sticky" to "is sticky". So now its having the +opposite effect. Only normal pistons fire the retraction event. And like +all things in CB, it's just been carried around since. + +If we are to believe the history, the correct fix for this issue is to +flip it so it only fires for sticky pistons, but that puts us in a +bind. It's already firing for non-sticky pistons, changing it now would +likely result in breakage. Furthermore, there is little documentation as +to WHY that was ever intended to be the case. + +Instead we opt to remove the check entirely so that the event fires for +all piston types. + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 878cec21b52fb62369310c2f85001e859a270dd8..dc9584a30c18d964afd9cc118c81c24a80beba63 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -140,7 +140,7 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + // CraftBukkit start +- if (!this.isSticky) { ++ //if (!this.sticky) { // Paper - Prevents empty sticky pistons from firing retract - history behind is odd + org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); + world.getCraftServer().getPluginManager().callEvent(event); +@@ -148,7 +148,7 @@ public class PistonBaseBlock extends DirectionalBlock { + if (event.isCancelled()) { + return; + } +- } ++ //} // Paper + // PAIL: checkME - what happened to setTypeAndData? + // CraftBukkit end + world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); diff --git a/Remapped-Spigot-Server-Patches/0322-Block-Entity-remove-from-being-called-on-Players.patch b/Remapped-Spigot-Server-Patches/0322-Block-Entity-remove-from-being-called-on-Players.patch new file mode 100644 index 000000000..8ac3978e9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0322-Block-Entity-remove-from-being-called-on-Players.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Feb 2019 23:33:24 -0500 +Subject: [PATCH] Block Entity#remove from being called on Players + +This doesn't result in the same behavior as other entities and causes +several problems. Anyone ever complain about the "Cannot send chat +message" thing? That's one of the issues this causes, among others. + +If a plugin developer can come up with a valid reason to call this on a +Player we will look at limiting the scope of this change. It appears to +be unintentional in the few cases we've seen so far. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ba03a1cdac68dc08ed878e311adeebc531e8f2f1..c2c6eb54096ef85b01c0b700cbe6a8054b62729f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2231,6 +2231,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void resetCooldown() { + getHandle().resetAttackStrengthTicker(); + } ++ ++ @Override ++ public void remove() { ++ if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting ++ throw new UnsupportedOperationException("Calling Entity#remove on players produces undefined (bad) behavior"); ++ } else { ++ super.remove(); ++ } ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0323-BlockDestroyEvent.patch b/Remapped-Spigot-Server-Patches/0323-BlockDestroyEvent.patch new file mode 100644 index 000000000..497abbdeb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0323-BlockDestroyEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 Feb 2019 00:20:33 -0500 +Subject: [PATCH] BlockDestroyEvent + +Adds an event for when the server is going to destroy a current block, +potentially causing it to drop. This event can be cancelled to avoid +the block destruction, such as preventing signs from popping when +floating in the air. + +This can replace many uses of BlockPhysicsEvent + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b75ffafb6840b6acab6e5b0ef5e222c4fa130977..c8542636e89748699d608eb29569cacb6321d334 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -27,6 +27,7 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundSetBorderPacket; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerLevel; +@@ -559,8 +560,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return false; + } else { + FluidState fluid = this.getFluidState(pos); ++ // Paper start - while the above setAir method is named same and looks very similar ++ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, ++ // it doesn't imply destruction of a block that plays a sound effect / drops an item. ++ boolean playEffect = true; ++ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(MCUtil.toBukkitBlock(this, pos), fluid.createLegacyBlock().createCraftBlockData(), drop); ++ if (!event.callEvent()) { ++ return false; ++ } ++ playEffect = event.playEffect(); ++ } ++ // Paper end + +- if (!(iblockdata.getBlock() instanceof BaseFireBlock)) { ++ if (playEffect && !(iblockdata.getBlock() instanceof BaseFireBlock)) { // Paper + this.levelEvent(2001, pos, Block.getId(iblockdata)); + } + diff --git a/Remapped-Spigot-Server-Patches/0324-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch b/Remapped-Spigot-Server-Patches/0324-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch new file mode 100644 index 000000000..941fce8c3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0324-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Jan 2019 00:08:15 -0500 +Subject: [PATCH] Fix Custom Shapeless Custom Crafting Recipes + +Mojang implemented Shapeless different than Shaped + +This made the Bukkit RecipeChoice API not work for Shapeless. + +This reimplements vanilla logic using the same test logic as Shaped + +diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java +index 7bd62f598f3fff7520c276bdc45e538bd3260bc9..a8d0a90d36d58515bb6f6128de1ef15b72c20c17 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java ++++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java +@@ -71,16 +71,49 @@ public class ShapelessRecipe implements CraftingRecipe { + StackedContents autorecipestackmanager = new StackedContents(); + int i = 0; + ++ // Paper start ++ java.util.List providedItems = new java.util.ArrayList<>(); ++ co.aikar.util.Counter matchedProvided = new co.aikar.util.Counter<>(); ++ co.aikar.util.Counter matchedIngredients = new co.aikar.util.Counter<>(); ++ // Paper end + for (int j = 0; j < inv.getContainerSize(); ++j) { + ItemStack itemstack = inv.getItem(j); + + if (!itemstack.isEmpty()) { +- ++i; +- autorecipestackmanager.accountStack(itemstack, 1); ++ // Paper start ++ itemstack = itemstack.copy(); ++ providedItems.add(itemstack); ++ for (Ingredient ingredient : ingredients) { ++ if (ingredient.test(itemstack)) { ++ matchedProvided.increment(itemstack); ++ matchedIngredients.increment(ingredient); ++ } ++ } ++ // Paper end + } + } + +- return i == this.ingredients.size() && autorecipestackmanager.canCraft(this, (IntList) null); ++ // Paper start ++ if (matchedProvided.isEmpty() || matchedIngredients.isEmpty()) { ++ return false; ++ } ++ java.util.List ingredients = new java.util.ArrayList<>(this.ingredients); ++ providedItems.sort(java.util.Comparator.comparingInt((ItemStack c) -> (int) matchedProvided.getCount(c)).reversed()); ++ ingredients.sort(java.util.Comparator.comparingInt((Ingredient c) -> (int) matchedIngredients.getCount(c))); ++ ++ PROVIDED: ++ for (ItemStack provided : providedItems) { ++ for (Iterator itIngredient = ingredients.iterator(); itIngredient.hasNext(); ) { ++ Ingredient ingredient = itIngredient.next(); ++ if (ingredient.test(provided)) { ++ itIngredient.remove(); ++ continue PROVIDED; ++ } ++ } ++ return false; ++ } ++ return ingredients.isEmpty(); ++ // Paper end + } + + public ItemStack assemble(CraftingContainer inv) { diff --git a/Remapped-Spigot-Server-Patches/0325-Fix-sign-edit-memory-leak.patch b/Remapped-Spigot-Server-Patches/0325-Fix-sign-edit-memory-leak.patch new file mode 100644 index 000000000..cdc4ad71f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0325-Fix-sign-edit-memory-leak.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 Feb 2019 00:15:28 -0500 +Subject: [PATCH] Fix sign edit memory leak + +when a player edits a sign, a reference to their Entity is never cleand up. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b1c505d3fdcc2fb3496f80bee85e4895b9069dcb..276773e17149f57038cd21485fd9d9061670ff2d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2850,7 +2850,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + SignBlockEntity tileentitysign = (SignBlockEntity) tileentity; + +- if (!tileentitysign.isEditable() || tileentitysign.getPlayerWhoMayEdit() != this.player) { ++ if (!tileentitysign.isEditable() || tileentitysign.signEditor == null || !tileentitysign.signEditor.equals(this.player.getUUID())) { + ServerGamePacketListenerImpl.LOGGER.warn("Player {} just tried to change non-editable sign", this.player.getName().getString()); + this.send(tileentity.getUpdatePacket()); // CraftBukkit + return; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index e4eab82855649fec654c60b2e94ba7b71c2ac5a2..0b26d3ab2db66d6baaa95d1d5f6c756595d31495 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -30,6 +30,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + private Player playerWhoMayEdit; + private final FormattedCharSequence[] renderMessages; + private DyeColor color; ++ public java.util.UUID signEditor; // Paper + + public SignBlockEntity() { + super(BlockEntityType.SIGN); +@@ -131,7 +132,10 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + } + + public void setAllowedPlayerEditor(Player player) { +- this.playerWhoMayEdit = player; ++ // Paper start ++ //this.c = entityhuman; ++ signEditor = player != null ? player.getUUID() : null; ++ // Paper end + } + + public Player getPlayerWhoMayEdit() { diff --git a/Remapped-Spigot-Server-Patches/0326-Limit-Client-Sign-length-more.patch b/Remapped-Spigot-Server-Patches/0326-Limit-Client-Sign-length-more.patch new file mode 100644 index 000000000..da033509a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0326-Limit-Client-Sign-length-more.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Feb 2019 22:18:40 -0500 +Subject: [PATCH] Limit Client Sign length more + +modified clients can send more data from the client +to the server and it would get stored on the sign as sent. + +Mojang has a limit of 384 which is much higher than reasonable. + +the client can barely render around 16 characters as-is, but formatting +codes can get it to be more than 16 actual length. + +Set a limit of 80 which should give an average of 16 characters 2 +sets of legacy formatting codes which should be plenty for all uses. + +This does not strip any existing data from the NBT as plugins +may use this for storing data out of the rendered area. + +it only impacts data sent from the client. + +Set -DPaper.maxSignLength=XX to change limit or -1 to disable + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 276773e17149f57038cd21485fd9d9061670ff2d..d6d8d83bc16572474d56a278dd119eacc2c52476 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -254,6 +254,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private int aboveGroundVehicleTickCount; + private int receivedMovePacketCount; + private int knownMovePacketCount; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { +@@ -2860,7 +2861,17 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(list.get(i)))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. ++ // Paper start - cap line length - modified clients can send longer data than normal ++ String currentLine = list.get(i); ++ if (MAX_SIGN_LINE_LENGTH > 0 && currentLine.length() > MAX_SIGN_LINE_LENGTH) { ++ // This handles multibyte characters as 1 ++ int offset = currentLine.codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); ++ if (offset < currentLine.length()) { ++ list.set(i, currentLine = currentLine.substring(0, offset)); ++ } ++ } ++ // Paper end ++ lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(currentLine))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. + } + SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.craftServer.getPluginManager().callEvent(event); diff --git a/Remapped-Spigot-Server-Patches/0327-Don-t-check-ConvertSigns-boolean-every-sign-save.patch b/Remapped-Spigot-Server-Patches/0327-Don-t-check-ConvertSigns-boolean-every-sign-save.patch new file mode 100644 index 000000000..e847f6959 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0327-Don-t-check-ConvertSigns-boolean-every-sign-save.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 Mar 2019 11:11:29 -0500 +Subject: [PATCH] Don't check ConvertSigns boolean every sign save + +property lookups arent super cheap. they synchronize, validate +and check security managers. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index 0b26d3ab2db66d6baaa95d1d5f6c756595d31495..8d619e0bf8bcf7d6d4e7f23f11d648ccfb65cac4 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -31,6 +31,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + private final FormattedCharSequence[] renderMessages; + private DyeColor color; + public java.util.UUID signEditor; // Paper ++ private static final boolean CONVERT_LEGACY_SIGNS = Boolean.getBoolean("convertLegacySigns"); // Paper + + public SignBlockEntity() { + super(BlockEntityType.SIGN); +@@ -51,7 +52,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + } + + // CraftBukkit start +- if (Boolean.getBoolean("convertLegacySigns")) { ++ if (CONVERT_LEGACY_SIGNS) { // Paper + tag.putBoolean("Bukkit.isConverted", true); + } + // CraftBukkit end diff --git a/Remapped-Spigot-Server-Patches/0328-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/Remapped-Spigot-Server-Patches/0328-Optimize-Network-Manager-and-add-advanced-packet-sup.patch new file mode 100644 index 000000000..0dc4b2e08 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0328-Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 04:53:35 -0400 +Subject: [PATCH] Optimize Network Manager and add advanced packet support + +Adds ability for 1 packet to bundle other packets to follow it +Adds ability for a packet to delay sending more packets until a state is ready. + +Removes synchronization from sending packets +Removes processing packet queue off of main thread + - for the few cases where it is allowed, order is not necessary nor + should it even be happening concurrently in first place (handshaking/login/status) + +Ensures packets sent asynchronously are dispatched on main thread + +This helps ensure safety for ProtocolLib as packet listeners +are commonly accessing world state. This will allow you to schedule +a packet to be sent async, but itll be dispatched sync for packet +listeners to process. + +This should solve some deadlock risks + +Also adds Netty Channel Flush Consolidation to reduce the amount of flushing + +Also avoids spamming closed channel exception by rechecking closed state in dispatch +and then catch exceptions and close if they fire. + +Part of this commit was authored by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 3247ec5d6cf329ba0b7e6d5a6c3294dec2e34db4..fc63df21aecd4721efdb45d4744666ed0b562c1b 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -25,8 +25,15 @@ import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.PacketFlow; ++import net.minecraft.network.protocol.game.ClientboundBossEventPacket; ++import net.minecraft.network.protocol.game.ClientboundChatPacket; ++import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; + import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; ++import net.minecraft.network.protocol.game.ClientboundKeepAlivePacket; ++import net.minecraft.network.protocol.game.ClientboundSetTitlesPacket; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.RunningOnDifferentThreadException; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import net.minecraft.server.network.ServerLoginPacketListenerImpl; + import net.minecraft.util.LazyLoadedValue; +@@ -75,6 +82,10 @@ public class Connection extends SimpleChannelInboundHandler> { + public int protocolVersion; + public java.net.InetSocketAddress virtualHost; + private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); ++ // Optimize network ++ public boolean isPending = true; ++ public boolean queueImmunity = false; ++ public ConnectionProtocol protocol; + // Paper end + + public Connection(PacketFlow side) { +@@ -98,6 +109,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void setProtocol(ConnectionProtocol state) { ++ protocol = state; // Paper + this.channel.attr(Connection.ATTRIBUTE_PROTOCOL).set(state); + this.channel.config().setAutoRead(true); + Connection.LOGGER.debug("Enabled auto read"); +@@ -168,19 +180,84 @@ public class Connection extends SimpleChannelInboundHandler> { + Validate.notNull(listener, "packetListener", new Object[0]); + this.packetListener = listener; + } ++ // Paper start ++ public ServerPlayer getPlayer() { ++ if (packetListener instanceof ServerGamePacketListenerImpl) { ++ return ((ServerGamePacketListenerImpl) packetListener).player; ++ } else { ++ return null; ++ } ++ } ++ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up. ++ private static java.util.List buildExtraPackets(Packet packet) { ++ java.util.List extra = packet.getExtraPackets(); ++ if (extra == null || extra.isEmpty()) { ++ return null; ++ } ++ java.util.List ret = new java.util.ArrayList<>(1 + extra.size()); ++ buildExtraPackets0(extra, ret); ++ return ret; ++ } ++ ++ private static void buildExtraPackets0(java.util.List extraPackets, java.util.List into) { ++ for (Packet extra : extraPackets) { ++ into.add(extra); ++ java.util.List extraExtra = extra.getExtraPackets(); ++ if (extraExtra != null && !extraExtra.isEmpty()) { ++ buildExtraPackets0(extraExtra, into); ++ } ++ } ++ } ++ // Paper start ++ private static boolean canSendImmediate(Connection networkManager, Packet packet) { ++ return networkManager.isPending || networkManager.protocol != ConnectionProtocol.PLAY || ++ packet instanceof ClientboundKeepAlivePacket || ++ packet instanceof ClientboundChatPacket || ++ packet instanceof ClientboundCommandSuggestionsPacket || ++ packet instanceof ClientboundSetTitlesPacket || ++ packet instanceof ClientboundBossEventPacket; ++ } ++ // Paper end ++ } ++ // Paper end + + public void send(Packet packet) { + this.send(packet, (GenericFutureListener) null); + } + + public void send(Packet packet, @Nullable GenericFutureListener> callback) { +- if (this.isConnected()) { +- this.flushQueue(); +- this.sendPacket(packet, callback); +- } else { +- this.queue.add(new Connection.PacketHolder(packet, callback)); ++ // Paper start - handle oversized packets better ++ boolean connected = this.isConnected(); ++ if (!connected && !preparing) { ++ return; // Do nothing ++ } ++ packet.onPacketDispatch(getPlayer()); ++ if (connected && (InnerUtil.canSendImmediate(this, packet) || ( ++ MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() && ++ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) ++ ))) { ++ this.dispatchPacket(packet, callback); ++ return; + } ++ // write the packets to the queue, then flush - antixray hooks there already ++ java.util.List extraPackets = InnerUtil.buildExtraPackets(packet); ++ boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty(); ++ if (!hasExtraPackets) { ++ this.queue.add(new Connection.PacketHolder(packet, callback)); ++ } else { ++ java.util.List packets = new java.util.ArrayList<>(1 + extraPackets.size()); ++ packets.add(new Connection.PacketHolder(packet, null)); // delay the future listener until the end of the extra packets ++ ++ for (int i = 0, len = extraPackets.size(); i < len;) { ++ Packet extra = extraPackets.get(i); ++ boolean end = ++i == len; ++ packets.add(new Connection.PacketHolder(extra, end ? callback : null)); // append listener to the end ++ } + ++ this.queue.addAll(packets); // atomic ++ } ++ this.sendPacketQueue(); ++ // Paper end + } + + private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.sendPacket(packet, genericFutureListener); } // Paper - OBFHELPER +@@ -194,54 +271,119 @@ public class Connection extends SimpleChannelInboundHandler> { + this.channel.config().setAutoRead(false); + } + ++ ServerPlayer player = getPlayer(); // Paper + if (this.channel.eventLoop().inEventLoop()) { + if (enumprotocol != enumprotocol1) { + this.setProtocol(enumprotocol); + } ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end + + ChannelFuture channelfuture = this.channel.writeAndFlush(packet); + + if (callback != null) { + channelfuture.addListener(callback); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end + + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end + } else { + this.channel.eventLoop().execute(() -> { + if (enumprotocol != enumprotocol1) { + this.setProtocol(enumprotocol); + } + ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end + ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); + ++ + if (callback != null) { + channelfuture1.addListener(callback); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end + + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end + }); + } + + } + +- private void sendPacketQueue() { this.flushQueue(); } // Paper - OBFHELPER +- private void flushQueue() { +- if (this.channel != null && this.channel.isOpen()) { +- Queue queue = this.queue; +- ++ // Paper start - rewrite this to be safer if ran off main thread ++ private boolean sendPacketQueue() { return this.p(); } // OBFHELPER // void -> boolean ++ private boolean p() { // void -> boolean ++ if (!isConnected()) { ++ return true; ++ } ++ if (MCUtil.isMainThread()) { ++ return processQueue(); ++ } else if (isPending) { ++ // Should only happen during login/status stages + synchronized (this.queue) { +- Connection.PacketHolder networkmanager_queuedpacket; +- +- while ((networkmanager_queuedpacket = (Connection.PacketHolder) this.queue.poll()) != null) { +- this.sendPacket(networkmanager_queuedpacket.packet, networkmanager_queuedpacket.listener); +- } ++ return this.processQueue(); ++ } ++ } ++ return false; ++ } ++ private boolean processQueue() { ++ if (this.queue.isEmpty()) return true; ++ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore ++ // But if we are not on main due to login/status, the parent is synchronized on packetQueue ++ java.util.Iterator iterator = this.queue.iterator(); ++ while (iterator.hasNext()) { ++ Connection.PacketHolder queued = iterator.next(); // poll -> peek ++ ++ // Fix NPE (Spigot bug caused by handleDisconnection()) ++ if (queued == null) { ++ return true; ++ } + ++ Packet packet = queued.getPacket(); ++ if (!packet.isReady()) { ++ return false; ++ } else { ++ iterator.remove(); ++ this.dispatchPacket(packet, queued.getGenericFutureListener()); + } + } ++ return true; + } ++ // Paper end + + public void tick() { +- this.flushQueue(); ++ this.p(); + if (this.packetListener instanceof ServerLoginPacketListenerImpl) { + ((ServerLoginPacketListenerImpl) this.packetListener).tick(); + } +@@ -271,9 +413,21 @@ public class Connection extends SimpleChannelInboundHandler> { + return this.address; + } + ++ // Paper start ++ public void clearPacketQueue() { ++ ServerPlayer player = getPlayer(); ++ queue.forEach(queuedPacket -> { ++ Packet packet = queuedPacket.getPacket(); ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(player, null); ++ } ++ }); ++ queue.clear(); ++ } // Paper end + public void disconnect(Component disconnectReason) { + // Spigot Start + this.preparing = false; ++ clearPacketQueue(); // Paper + // Spigot End + if (this.channel.isOpen()) { + this.channel.close(); // We can't wait as this may be called from an event loop. +@@ -341,7 +495,7 @@ public class Connection extends SimpleChannelInboundHandler> { + public void handleDisconnection() { + if (this.channel != null && !this.channel.isOpen()) { + if (this.disconnectionHandled) { +- Connection.LOGGER.warn("handleDisconnection() called twice"); ++ //NetworkManager.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message + } else { + this.disconnectionHandled = true; + if (this.getDisconnectedReason() != null) { +@@ -349,7 +503,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } else if (this.getPacketListener() != null) { + this.getPacketListener().a(new TranslatableComponent("multiplayer.disconnect.generic")); + } +- this.queue.clear(); // Free up packet queue. ++ clearPacketQueue(); // Paper + // Paper start - Add PlayerConnectionCloseEvent + final PacketListener packetListener = this.getPacketListener(); + if (packetListener instanceof ServerGamePacketListenerImpl) { +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index 9914a82ba0ec146ab13fe94c4dbf0ebf64926536..22db5d0d2cc33498ca40162c66aa3b5fbf2f569f 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.protocol; + ++import io.netty.channel.ChannelFuture; // Paper + import java.io.IOException; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.PacketListener; +@@ -13,6 +14,20 @@ public interface Packet { + void handle(T listener); + + // Paper start ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ */ ++ default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {} ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ * @param future Can be null if packet was cancelled ++ */ ++ default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable ChannelFuture future) {} ++ default boolean hasFinishListener() { return false; } ++ default boolean isReady() { return true; } ++ default java.util.List getExtraPackets() { return null; } + default boolean packetTooLarge(NetworkManager manager) { + return false; + } +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 6cb51a4fe3c11f53fbb556ce6b0d64b735254d51..d46910cfdc0aef046a0c79731a85d381953c328a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -16,6 +16,7 @@ import io.netty.channel.epoll.EpollServerSocketChannel; + import io.netty.channel.nio.NioEventLoopGroup; + import io.netty.channel.socket.ServerSocketChannel; + import io.netty.channel.socket.nio.NioServerSocketChannel; ++import io.netty.handler.flush.FlushConsolidationHandler; // Paper + import io.netty.handler.timeout.ReadTimeoutHandler; + import java.io.IOException; + import java.net.InetAddress; +@@ -54,10 +55,12 @@ public class ServerConnectionListener { + private final List connections = Collections.synchronizedList(Lists.newArrayList()); + // Paper start - prevent blocking on adding a new network manager while the server is ticking + private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper + private void addPending() { + Connection manager = null; + while ((manager = pending.poll()) != null) { + connections.add(manager); ++ manager.isPending = false; + } + } + // Paper end +@@ -92,6 +95,7 @@ public class ServerConnectionListener { + ; + } + ++ if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper + channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this)).addLast("splitter", new Varint21FrameDecoder()).addLast("decoder", new PacketDecoder(PacketFlow.SERVERBOUND)).addLast("prepender", new Varint21LengthFieldPrepender()).addLast("encoder", new PacketEncoder(PacketFlow.CLIENTBOUND)); + int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); + Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); diff --git a/Remapped-Spigot-Server-Patches/0329-Handle-Oversized-Tile-Entities-in-chunks.patch b/Remapped-Spigot-Server-Patches/0329-Handle-Oversized-Tile-Entities-in-chunks.patch new file mode 100644 index 000000000..83303e9b3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0329-Handle-Oversized-Tile-Entities-in-chunks.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 05:00:57 -0400 +Subject: [PATCH] Handle Oversized Tile Entities in chunks + +Splits out Extra Packets if too many TE's are encountered to prevent +creating too large of a packet to sed. + +Co authored by Spottedleaf + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +index b7d303b5f51a35504888933efef74564fa01e59d..b587f774c8f88f2a1c3ea489f7e4fe0bbdeb5a41 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -34,7 +34,15 @@ public class ClientboundLevelChunkPacket implements Packet extraPackets = new java.util.ArrayList<>(); ++ private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); + ++ @Override ++ public java.util.List getExtraPackets() { ++ return extraPackets; ++ } ++ // Paper end + public ClientboundLevelChunkPacket(LevelChunk chunk, int includedSectionsMask) { + ChunkPos chunkcoordintpair = chunk.getPos(); + +@@ -61,6 +69,7 @@ public class ClientboundLevelChunkPacket implements Packet> 4; + + if (this.isFullChunk() || (includedSectionsMask & 1 << j) != 0) { ++ // Paper start - improve oversized chunk data packet handling ++ if (++totalTileEntities > TE_LIMIT) { ++ ClientboundBlockEntityDataPacket updatePacket = tileentity.getUpdatePacket(); ++ if (updatePacket != null) { ++ this.extraPackets.add(updatePacket); ++ continue; ++ } ++ } ++ // Paper end + CompoundTag nbttagcompound = tileentity.getUpdateTag(); + if (tileentity instanceof SkullBlockEntity) { SkullBlockEntity.sanitizeTileEntityUUID(nbttagcompound); } // Paper + diff --git a/Remapped-Spigot-Server-Patches/0330-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch b/Remapped-Spigot-Server-Patches/0330-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch new file mode 100644 index 000000000..1209aa99e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0330-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 Mar 2019 16:12:35 -0500 +Subject: [PATCH] MC-145260: Fix Whitelist On/Off inconsistency + +mojang stored whitelist state in 2 places (Whitelist Object, PlayerList) + +some things checked PlayerList, some checked object. This moves +everything to the Whitelist object. + +https://github.com/PaperMC/Paper/issues/1880 + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 5b71b487836cdd2ddc75b2039f4dc0177719d345..8d133d3c825f7747081de99ee67d4556e5c19cdd 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -55,6 +55,7 @@ import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket; + import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; + import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.PlayerAdvancements; + import net.minecraft.server.ServerScoreboard; +@@ -1012,9 +1013,9 @@ public abstract class PlayerList { + } + public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) { + boolean isOp = this.ops.contains(gameprofile); +- boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile); ++ boolean isWhitelisted = !this.isUsingWhitelist() || isOp || this.whitelist.contains(gameprofile); + final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; +- event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); ++ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.isUsingWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); + event.callEvent(); + if (!event.isWhitelisted()) { + if (loginEvent != null) { +@@ -1062,7 +1063,7 @@ public abstract class PlayerList { + MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper + for (int i = 0; i < this.players.size(); ++i) { +- this.savePlayerFile((EntityPlayer) this.players.get(i)); ++ this.save((ServerPlayer) this.players.get(i)); + } + MinecraftTimings.savePlayers.stopTiming(); // Paper + return null; }); // Paper - ensure main diff --git a/Remapped-Spigot-Server-Patches/0331-Set-Zombie-last-tick-at-start-of-drowning-process.patch b/Remapped-Spigot-Server-Patches/0331-Set-Zombie-last-tick-at-start-of-drowning-process.patch new file mode 100644 index 000000000..eb4f5ff56 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0331-Set-Zombie-last-tick-at-start-of-drowning-process.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Mar 2019 02:23:28 -0500 +Subject: [PATCH] Set Zombie last tick at start of drowning process + +Fixes GH-1887 + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index ad4eeb15771750193a28116117992270c72a3644..6fecf4188fc0247143edc688c03e645376960687 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -220,6 +220,7 @@ public class Zombie extends Monster { + ++this.inWaterTime; + if (this.inWaterTime >= 600) { + this.startUnderWaterConversion(300); ++ this.lastTick = MinecraftServer.currentTick; // Paper - Make sure this is set at start of process - GH-1887 + } + } else { + this.inWaterTime = -1; diff --git a/Remapped-Spigot-Server-Patches/0332-Allow-Saving-of-Oversized-Chunks.patch b/Remapped-Spigot-Server-Patches/0332-Allow-Saving-of-Oversized-Chunks.patch new file mode 100644 index 000000000..c23cb450e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0332-Allow-Saving-of-Oversized-Chunks.patch @@ -0,0 +1,271 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Feb 2019 01:08:19 -0500 +Subject: [PATCH] Allow Saving of Oversized Chunks + +The Minecraft World Region File format has a hard cap of 1MB per chunk. +This is due to the fact that the header of the file format only allocates +a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector. + +This limit can be reached fairly easily with books, resulting in the chunk being unable +to save to the world. Worse off, is that nothing printed when this occured, and silently +performed a chunk rollback on next load. + +This leads to security risk with duplication and is being actively exploited. + +This patch catches the too large scenario, falls back and moves any large Entity +or Tile Entity into a new compound, and this compound is saved into a different file. + +On Chunk Load, we check for oversized status, and if so, we load the extra file and +merge the Entities and Tile Entities from the oversized chunk back into the level to +then be loaded as normal. + +Once a chunk is returned back to normal size, the oversized flag will clear, and no +extra data file will exist. + +This fix maintains compatability with all existing Anvil Region Format tools as it +does not alter the save format. They will just not know about the extra entities. + +This fix also maintains compatability if someone switches server jars to one without +this fix, as the data will remain in the oversized file. Once the server returns +to a jar with this fix, the data will be restored. + +diff --git a/src/main/java/net/minecraft/nbt/NbtIo.java b/src/main/java/net/minecraft/nbt/NbtIo.java +index c4fbc0bc8b80d16f09d3c6642acc7476a0817868..b3838e709c1581c25da7738c9a03a827761845b1 100644 +--- a/src/main/java/net/minecraft/nbt/NbtIo.java ++++ b/src/main/java/net/minecraft/nbt/NbtIo.java +@@ -132,6 +132,7 @@ public class NbtIo { + + } + ++ public static CompoundTag readNBT(DataInput datainput) throws IOException { return read(datainput); } // Paper - OBFHELPER + public static CompoundTag read(DataInput input) throws IOException { + return read(input, NbtAccounter.UNLIMITED); + } +@@ -152,6 +153,7 @@ public class NbtIo { + } + } + ++ public static void writeNBT(CompoundTag nbttagcompound, DataOutput dataoutput) throws IOException { write(nbttagcompound, dataoutput); } // Paper - OBFHELPER + public static void write(CompoundTag tag, DataOutput output) throws IOException { + writeUnnamedTag((Tag) tag, output); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 1aa4d342b97f8be71c108194a6f1e0e2828aa364..424628c9588c02454558bc7e7c5bad3a3e75ec9f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -20,8 +20,12 @@ import java.nio.file.LinkOption; + import java.nio.file.Path; + import java.nio.file.StandardCopyOption; + import java.nio.file.StandardOpenOption; ++import java.util.zip.InflaterInputStream; // Paper ++ + import javax.annotation.Nullable; + import net.minecraft.Util; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtIo; + import net.minecraft.world.level.ChunkPos; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -38,6 +42,7 @@ public class RegionFile implements AutoCloseable { + private final IntBuffer timestamps; + @VisibleForTesting + protected final RegionBitmap usedSectors; ++ public final File file; // Paper + + public RegionFile(File file, File directory, boolean dsync) throws IOException { + this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync); +@@ -45,6 +50,8 @@ public class RegionFile implements AutoCloseable { + + public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { + this.header = ByteBuffer.allocateDirect(8192); ++ this.file = file.toFile(); // Paper ++ initOversizedState(); // Paper + this.usedSectors = new RegionBitmap(); + this.version = outputChunkStreamVersion; + if (!Files.isDirectory(directory, new LinkOption[0])) { +@@ -408,6 +415,74 @@ public class RegionFile implements AutoCloseable { + void run() throws IOException; + } + ++ // Paper start ++ private final byte[] oversized = new byte[1024]; ++ private int oversizedCount = 0; ++ ++ private synchronized void initOversizedState() throws IOException { ++ File metaFile = getOversizedMetaFile(); ++ if (metaFile.exists()) { ++ final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath()); ++ System.arraycopy(read, 0, oversized, 0, oversized.length); ++ for (byte temp : oversized) { ++ oversizedCount += temp; ++ } ++ } ++ } ++ ++ private static int getChunkIndex(int x, int z) { ++ return (x & 31) + (z & 31) * 32; ++ } ++ synchronized boolean isOversized(int x, int z) { ++ return this.oversized[getChunkIndex(x, z)] == 1; ++ } ++ synchronized void setOversized(int x, int z, boolean oversized) throws IOException { ++ final int offset = getChunkIndex(x, z); ++ boolean previous = this.oversized[offset] == 1; ++ this.oversized[offset] = (byte) (oversized ? 1 : 0); ++ if (!previous && oversized) { ++ oversizedCount++; ++ } else if (!oversized && previous) { ++ oversizedCount--; ++ } ++ if (previous && !oversized) { ++ File oversizedFile = getOversizedFile(x, z); ++ if (oversizedFile.exists()) { ++ oversizedFile.delete(); ++ } ++ } ++ if (oversizedCount > 0) { ++ if (previous != oversized) { ++ writeOversizedMeta(); ++ } ++ } else if (previous) { ++ File oversizedMetaFile = getOversizedMetaFile(); ++ if (oversizedMetaFile.exists()) { ++ oversizedMetaFile.delete(); ++ } ++ } ++ } ++ ++ private void writeOversizedMeta() throws IOException { ++ java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized); ++ } ++ ++ private File getOversizedMetaFile() { ++ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + ".oversized.nbt"); ++ } ++ ++ private File getOversizedFile(int x, int z) { ++ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); ++ } ++ ++ synchronized CompoundTag getOversizedData(int x, int z) throws IOException { ++ File file = getOversizedFile(x, z); ++ try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) { ++ return NbtIo.readNBT((java.io.DataInput) out); ++ } ++ ++ } ++ // Paper end + class ChunkBuffer extends ByteArrayOutputStream { + + private final ChunkPos pos; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index f33a5fc725d1d5e895f8878d82ebc4172237ad29..6d3e1bb20d1ab8ce5c9ea613322042d80550761a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -10,7 +10,9 @@ import java.io.File; + import java.io.IOException; + import javax.annotation.Nullable; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtIo; ++import net.minecraft.nbt.Tag; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionCollector; + import net.minecraft.world.level.ChunkPos; +@@ -50,6 +52,74 @@ public final class RegionFileStorage implements AutoCloseable { + } + } + ++ // Paper start ++ private static void printOversizedLog(String msg, File file, int x, int z) { ++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); ++ } ++ ++ private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8; ++ private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64; ++ private static final int OVERZEALOUS_THRESHOLD = 1024; ++ private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD; ++ private static void resetFilterThresholds() { ++ SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD)); ++ } ++ static { ++ resetFilterThresholds(); ++ } ++ ++ static boolean isOverzealous() { ++ return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD; ++ } ++ ++ ++ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { ++ synchronized (regionfile) { ++ try (DataInputStream datainputstream = regionfile.getReadStream(chunkCoordinate)) { ++ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); ++ CompoundTag chunk = NbtIo.readNBT((DataInput) datainputstream); ++ if (oversizedData == null) { ++ return chunk; ++ } ++ CompoundTag oversizedLevel = oversizedData.getCompound("Level"); ++ CompoundTag level = chunk.getCompound("Level"); ++ ++ mergeChunkList(level, oversizedLevel, "Entities"); ++ mergeChunkList(level, oversizedLevel, "TileEntities"); ++ ++ chunk.put("Level", level); ++ ++ return chunk; ++ } catch (Throwable throwable) { ++ throwable.printStackTrace(); ++ throw throwable; ++ } ++ } ++ } ++ ++ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key) { ++ ListTag levelList = level.getList(key, 10); ++ ListTag oversizedList = oversizedLevel.getList(key, 10); ++ ++ if (!oversizedList.isEmpty()) { ++ levelList.addAll(oversizedList); ++ level.put(key, levelList); ++ } ++ } ++ ++ private static int getNBTSize(Tag nbtBase) { ++ DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream()); ++ try { ++ nbtBase.write(test); ++ return test.size(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return 0; ++ } ++ } ++ ++ // Paper End ++ + @Nullable + public CompoundTag read(ChunkPos pos) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +@@ -59,6 +129,12 @@ public final class RegionFileStorage implements AutoCloseable { + } + // CraftBukkit end + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); ++ // Paper start ++ if (regionfile.isOversized(pos.x, pos.z)) { ++ printOversizedLog("Loading Oversized Chunk!", regionfile.file, pos.x, pos.z); ++ return readOversizedChunk(regionfile, pos); ++ } ++ // Paper end + Throwable throwable = null; + + CompoundTag nbttagcompound; +@@ -99,6 +175,7 @@ public final class RegionFileStorage implements AutoCloseable { + + try { + NbtIo.write(tag, (DataOutput) dataoutputstream); ++ regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; diff --git a/Remapped-Spigot-Server-Patches/0333-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/Remapped-Spigot-Server-Patches/0333-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch new file mode 100644 index 000000000..5fcbc4df8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0333-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Wed, 13 Mar 2019 20:08:09 +0200 +Subject: [PATCH] Call WhitelistToggleEvent when whitelist is toggled + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 8d133d3c825f7747081de99ee67d4556e5c19cdd..728eaadd3dc619e414ec30feb38c7d4a84b2e539 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1131,6 +1131,7 @@ public abstract class PlayerList { + } + + public void setUsingWhiteList(boolean whitelistEnabled) { ++ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); + this.doWhiteList = whitelistEnabled; + } + diff --git a/Remapped-Spigot-Server-Patches/0334-Add-LivingEntity-getTargetEntity.patch b/Remapped-Spigot-Server-Patches/0334-Add-LivingEntity-getTargetEntity.patch new file mode 100644 index 000000000..d02aab104 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0334-Add-LivingEntity-getTargetEntity.patch @@ -0,0 +1,187 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 22 Sep 2018 00:33:08 -0500 +Subject: [PATCH] Add LivingEntity#getTargetEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c5d6235a132818dfc78105e9d03d0687f697bb00..d106118dbf4fb270f8526e40a767dd4c563a333f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1503,6 +1503,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.calculateViewVector(pitch - 90.0F, yaw); + } + ++ public final Vec3 getEyePosition(float partialTicks) { return getEyePosition(partialTicks); } // Paper - OBFHELPER + public final Vec3 getEyePosition(float tickDelta) { + if (tickDelta == 1.0F) { + return new Vec3(this.getX(), this.getEyeY(), this.getZ()); +@@ -2153,6 +2154,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.getPassengers().size() < 1; + } + ++ public final float getCollisionBorderSize() { return getPickRadius(); } // Paper - OBFHELPER + public float getPickRadius() { + return 0.0F; + } +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index e92a8c4c49c452e1f3f0c06398f2a74e3432262f..d3640975c5a33b4911428760691215905b987385 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -22,6 +22,7 @@ public final class EntitySelector { + public static final Predicate NO_CREATIVE_OR_SPECTATOR = (entity) -> { + return !(entity instanceof Player) || !entity.isSpectator() && !((Player) entity).isCreative(); + }; ++ public static Predicate canAITarget() { return ATTACK_ALLOWED; } // Paper - OBFHELPER + public static final Predicate ATTACK_ALLOWED = (entity) -> { + return !(entity instanceof Player) || !entity.isSpectator() && !((Player) entity).isCreative() && entity.level.getDifficulty() != Difficulty.PEACEFUL; + }; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 29d4ed42e5d763639a50d849ef274c4d848bc9c9..046a05925739005080af35c4be984303b575bf68 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -108,6 +108,7 @@ import net.minecraft.world.level.storage.loot.LootTable; + import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.PlayerTeam; +@@ -3657,6 +3658,37 @@ public abstract class LivingEntity extends Entity { + return level.clip(raytrace); + } + ++ public EntityHitResult getTargetEntity(int maxDistance) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = this.getEyePosition(1.0F); ++ Vec3 direction = this.getLookAngle(); ++ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); ++ ++ List entityList = level.getEntities(this, getBoundingBox().expand(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.canAITarget().and(Entity::isPickable)); ++ ++ double distance = 0.0D; ++ EntityHitResult result = null; ++ ++ for (Entity entity : entityList) { ++ AABB aabb = entity.getBoundingBox().grow((double) entity.getCollisionBorderSize()); ++ Optional rayTraceResult = aabb.calculateIntercept(start, end); ++ ++ if (rayTraceResult.isPresent()) { ++ Vec3 rayTrace = rayTraceResult.get(); ++ double distanceTo = start.distanceToSqr(rayTrace); ++ if (distanceTo < distance || distance == 0.0D) { ++ result = new EntityHitResult(entity, rayTrace); ++ distance = distanceTo; ++ } ++ } ++ } ++ ++ return result; ++ } ++ + public int shieldBlockingDelay = level.paperConfig.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index 983d0495ec35128ca3ef68566ada065bc4b21efc..143a160a7577e9e34d34a9f3b900db03d3f297af 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -116,6 +116,7 @@ public class AABB { + return this.expandTowards(scale.x, scale.y, scale.z); + } + ++ public final AABB expand(double x, double y, double z) { return expandTowards(x, y, z); } // Paper - OBFHELPER + public AABB expandTowards(double x, double y, double z) { + double d3 = this.minX; + double d4 = this.minY; +@@ -145,6 +146,12 @@ public class AABB { + return new AABB(d3, d4, d5, d6, d7, d8); + } + ++ // Paper start ++ public AABB grow(double d0) { ++ return inflate(d0, d0, d0); ++ } ++ // Paper end ++ + public AABB inflate(double x, double y, double z) { + double d3 = this.minX - x; + double d4 = this.minY - y; +@@ -204,6 +211,7 @@ public class AABB { + return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ; + } + ++ public final boolean contains(Vec3 vec3d) { return contains(vec3d); } // Paper - OBFHELPER + public boolean contains(Vec3 vec) { + return this.contains(vec.x, vec.y, vec.z); + } +@@ -237,6 +245,7 @@ public class AABB { + return this.inflate(-value); + } + ++ public final Optional calculateIntercept(Vec3 vec3d, Vec3 vec3d1) { return clip(vec3d, vec3d1); } // Paper - OBFHELPER + public Optional clip(Vec3 min, Vec3 max) { + double[] adouble = new double[]{1.0D}; + double d0 = max.x - min.x; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 7e3a215f1592bed9f35e22076d9e35a5a49a430e..a01bd035846df0e2e28dc55e2ef2f5f35b83f905 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.entity; + + import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.destroystokyo.paper.entity.TargetEntityInfo; + import com.google.common.base.Preconditions; + import com.google.common.collect.Sets; + import java.util.ArrayList; +@@ -30,8 +31,11 @@ import net.minecraft.world.entity.projectile.ThrownEgg; + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.entity.projectile.ThrownExperienceBottle; + import net.minecraft.world.entity.projectile.ThrownTrident; ++import net.minecraft.world.level.ClipContext; + import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang.Validate; + import org.bukkit.FluidCollisionMode; + import org.bukkit.Location; +@@ -215,6 +219,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + new TargetBlockInfo(CraftBlock.at(getHandle().level, ((BlockHitResult)rayTrace).getBlockPos()), + MCUtil.toBukkitBlockFace(((BlockHitResult)rayTrace).getDirection())); + } ++ ++ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { ++ EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); ++ } ++ ++ public TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { ++ EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z)); ++ } ++ ++ public EntityHitResult rayTraceEntity(int maxDistance, boolean ignoreBlocks) { ++ EntityHitResult rayTrace = getHandle().getTargetEntity(maxDistance); ++ if (rayTrace == null) { ++ return null; ++ } ++ if (!ignoreBlocks) { ++ HitResult rayTraceBlocks = getHandle().getRayTrace(maxDistance, ClipContext.Fluid.NONE); ++ if (rayTraceBlocks != null) { ++ Vec3 eye = getHandle().getEyePosition(1.0F); ++ if (eye.distanceToSqr(rayTraceBlocks.getLocation()) <= eye.distanceToSqr(rayTrace.getLocation())) { ++ return null; ++ } ++ } ++ } ++ return rayTrace; ++ } + // Paper end + + @Override diff --git a/Remapped-Spigot-Server-Patches/0335-Use-proper-max-length-when-serialising-BungeeCord-te.patch b/Remapped-Spigot-Server-Patches/0335-Use-proper-max-length-when-serialising-BungeeCord-te.patch new file mode 100644 index 000000000..c6e42c264 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0335-Use-proper-max-length-when-serialising-BungeeCord-te.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 20 Mar 2019 21:19:29 -0700 +Subject: [PATCH] Use proper max length when serialising BungeeCord text + component + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +index f13da9e7d014bc00fbabf0a495b548bba2f59468..002a6c7933f64405707d7d34d3e5c17584539623 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +@@ -9,7 +9,7 @@ import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.Packet; + + public class ClientboundChatPacket implements Packet { +- ++ private static final int MAX_LENGTH = Short.MAX_VALUE * 8 + 8; // Paper + private Component message; + public net.kyori.adventure.text.Component adventure$message; // Paper + public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot +@@ -43,9 +43,9 @@ public class ClientboundChatPacket implements Packet { + //packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below + // Paper start - don't nest if we don't need to so that we can preserve formatting + if (this.components.length == 1) { +- buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0])); ++ buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]), MAX_LENGTH); // Paper - use proper max length + } else { +- buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components)); ++ buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components), MAX_LENGTH); // Paper - use proper max length + } + // Paper end + } else { diff --git a/Remapped-Spigot-Server-Patches/0336-Entity-getEntitySpawnReason.patch b/Remapped-Spigot-Server-Patches/0336-Entity-getEntitySpawnReason.patch new file mode 100644 index 000000000..18095e343 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0336-Entity-getEntitySpawnReason.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 00:24:52 -0400 +Subject: [PATCH] Entity#getEntitySpawnReason + +Allows you to return the SpawnReason for why an Entity Spawned + +Pre existing entities will return NATURAL if it was a non +persistenting Living Entity, SPAWNER for spawners, +or DEFAULT since data was not stored. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 2ab221e5315dde4e556ee49a6962ae0091ccf616..d03b4f97102dfb88927a94ee5a5d397ac493eaa1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1035,6 +1035,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper + // Paper start + if (entity.valid) { + MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 728eaadd3dc619e414ec30feb38c7d4a84b2e539..4d813b6556030354f51c1ee5f18eac2166b44576 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -331,7 +331,7 @@ public abstract class PlayerList { + // CraftBukkit start + ServerLevel finalWorldServer = worldserver1; + Entity entity = EntityType.loadEntityRecursive(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> { +- return !finalWorldServer.addWithUUID(entity1) ? null : entity1; ++ return !finalWorldServer.addEntitySerialized(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // Paper + // CraftBukkit end + }); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d106118dbf4fb270f8526e40a767dd4c563a333f..6faac8773136412ca129dfa884178f311e197f6e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -63,6 +63,8 @@ import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.Nameable; + import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.animal.AbstractFish; ++import net.minecraft.world.entity.animal.Animal; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.vehicle.Boat; +@@ -157,6 +159,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + }; + public List entitySlice = null; ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; + // Paper end + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper +@@ -1673,6 +1676,9 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + tag.setUUID("Paper.OriginWorld", origin.getWorld().getUID()); + tag.put("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); + } ++ if (spawnReason != null) { ++ tag.putString("Paper.SpawnReason", spawnReason.name()); ++ } + // Save entity's from mob spawner status + if (spawnedViaMobSpawner) { + tag.putBoolean("Paper.FromMobSpawner", true); +@@ -1811,6 +1817,26 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + spawnedViaMobSpawner = tag.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ if (tag.contains("Paper.SpawnReason")) { ++ String spawnReasonName = tag.getString("Paper.SpawnReason"); ++ try { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); ++ } catch (Exception ignored) { ++ LogManager.getLogger().error("Unknown SpawnReason " + spawnReasonName + " for " + this); ++ } ++ } ++ if (spawnReason == null) { ++ if (spawnedViaMobSpawner) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; ++ } else if (this instanceof Mob && (this instanceof Animal || this instanceof AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { ++ if (!tag.getBoolean("PersistenceRequired")) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; ++ } ++ } ++ } ++ if (spawnReason == null) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 1ce675d0d24ceb5724f5ac2d8f671e38f2735f74..3a7aec9bd2f3fd1b4a1981fb6a8c64b69e4875f8 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -183,6 +183,7 @@ public abstract class BaseSpawner { + // Spigot End + } + entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper + flag = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index e1bbaf620f3ed2a6cb9ce8007a78c4cee47b653e..7ad4fb57af32cc1b8278688381e1b058ed8437db 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1106,5 +1106,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean fromMobSpawner() { + return getHandle().spawnedViaMobSpawner; + } ++ ++ @Override ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { ++ return getHandle().spawnReason; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0337-Update-entity-Metadata-for-all-tracked-players.patch b/Remapped-Spigot-Server-Patches/0337-Update-entity-Metadata-for-all-tracked-players.patch new file mode 100644 index 000000000..8f0ddb740 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0337-Update-entity-Metadata-for-all-tracked-players.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AgentTroll +Date: Fri, 22 Mar 2019 22:24:03 -0700 +Subject: [PATCH] Update entity Metadata for all tracked players + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 49c71b21b6b88bc41ca6ddf4c76186ce522ee456..1609ab94c86e964421f996d4d46aef30f8b8e696 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -420,6 +420,12 @@ public class ServerEntity { + return ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp); + } + ++ // Paper start - Add broadcast method ++ void broadcast(Packet packet) { ++ this.getPacketConsumer().accept(packet); ++ } ++ // Paper end ++ + private void broadcastAndSend(Packet packet) { + this.broadcast.accept(packet); + if (this.entity instanceof ServerPlayer) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index d6d8d83bc16572474d56a278dd119eacc2c52476..ed4129a51351aff16455960d71a0add1b8209c02 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2286,7 +2286,14 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + if (event.isCancelled() || this.player.inventory.getSelected() == null || this.player.inventory.getSelected().getItem() != origItem) { + // Refresh the current entity metadata +- this.send(new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true)); ++ // Paper start - update entity for all players ++ ClientboundSetEntityDataPacket packet1 = new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true); ++ if (entity.tracker != null) { ++ entity.tracker.broadcast(packet1); ++ } else { ++ this.send(packet1); ++ } ++ // Paper end + } + + if (event.isCancelled()) { diff --git a/Remapped-Spigot-Server-Patches/0338-Implement-PlayerPostRespawnEvent.patch b/Remapped-Spigot-Server-Patches/0338-Implement-PlayerPostRespawnEvent.patch new file mode 100644 index 000000000..dbfcbf2e3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0338-Implement-PlayerPostRespawnEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Fri, 26 Oct 2018 21:31:00 -0700 +Subject: [PATCH] Implement PlayerPostRespawnEvent + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4d813b6556030354f51c1ee5f18eac2166b44576..7412765020854caabd32fb6f4fffcf7f4bf6dba7 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -737,9 +737,14 @@ public abstract class PlayerList { + // this.a(entityplayer1, entityplayer, worldserver1); // CraftBukkit - removed + boolean flag2 = false; + ++ // Paper start ++ boolean isBedSpawn = false; ++ boolean isRespawn = false; ++ // Paper end ++ + // CraftBukkit start - fire PlayerRespawnEvent + if (location == null) { +- boolean isBedSpawn = false; ++ // boolean isBedSpawn = false; // Paper - moved up + ServerLevel worldserver1 = this.server.getLevel(entityplayer.getRespawnDimension()); + if (worldserver1 != null) { + Optional optional; +@@ -790,6 +795,7 @@ public abstract class PlayerList { + + location = respawnEvent.getRespawnLocation(); + if (!flag) entityplayer.reset(); // SPIGOT-4785 ++ isRespawn = true; // Paper + } else { + location.setWorld(worldserver.getWorld()); + } +@@ -847,6 +853,13 @@ public abstract class PlayerList { + if (entityplayer.connection.isDisconnected()) { + this.save(entityplayer); + } ++ ++ // Paper start ++ if (isRespawn) { ++ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn)); ++ } ++ // Paper end ++ + // CraftBukkit end + return entityplayer1; + } diff --git a/Remapped-Spigot-Server-Patches/0339-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch b/Remapped-Spigot-Server-Patches/0339-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch new file mode 100644 index 000000000..df8e4e2e9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0339-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 18:09:20 -0400 +Subject: [PATCH] don't go below 0 for pickupDelay, breaks picking up items + +vanilla checks for == 0 + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 70f719ba3c68c8e9414e6b4bc68002f7c962e2b9..281f5646980afc70890bdafd358ff9b20d32420d 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -86,6 +86,7 @@ public class ItemEntity extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end +@@ -178,6 +179,7 @@ public class ItemEntity extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end diff --git a/Remapped-Spigot-Server-Patches/0340-Server-Tick-Events.patch b/Remapped-Spigot-Server-Patches/0340-Server-Tick-Events.patch new file mode 100644 index 000000000..9fb3057ff --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0340-Server-Tick-Events.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Mar 2019 22:48:45 -0400 +Subject: [PATCH] Server Tick Events + +Fires event at start and end of a server tick + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 6ccc0be795e3ac7689de0eff6f9142d13161a29c..35984c7e994570a909ed4ffaabe64ae941b15e71 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1237,6 +1237,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 27 Mar 2019 23:01:33 -0400 +Subject: [PATCH] PlayerDeathEvent#getItemsToKeep + +Exposes a mutable array on items a player should keep on death + +Example Usage: https://gist.github.com/aikar/5bb202de6057a051a950ce1f29feb0b4 + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index fd2717a00a85f91ee23a1c0f929f856972892a9b..d6cfe68be1a944ff5d5780666467f5fd8e2794e3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -692,6 +692,46 @@ public class ServerPlayer extends Player implements ContainerListener { + }); + } + ++ // Paper start - process inventory ++ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { ++ List itemsToKeep = event.getItemsToKeep(); ++ if (inv == null) { ++ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? ++ if (!itemsToKeep.isEmpty()) { ++ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { ++ event.getEntity().getInventory().addItem(itemStack); ++ } ++ } ++ ++ return; ++ } ++ ++ for (int i = 0; i < inv.size(); ++i) { ++ ItemStack item = inv.get(i); ++ if (EnchantmentHelper.hasVanishingCurse(item) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.NULL_ITEM); ++ continue; ++ } ++ ++ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); ++ boolean keep = false; ++ final Iterator iterator = itemsToKeep.iterator(); ++ while (iterator.hasNext()) { ++ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); ++ if (bukkitStack.equals(itemStack)) { ++ iterator.remove(); ++ keep = true; ++ break; ++ } ++ } ++ ++ if (!keep) { ++ inv.set(i, ItemStack.NULL_ITEM); ++ } ++ } ++ } ++ // Paper end ++ + @Override + public void die(DamageSource source) { + boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); +@@ -775,7 +815,12 @@ public class ServerPlayer extends Player implements ContainerListener { + this.dropExperience(); + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { +- this.inventory.clearContent(); ++ // Paper start - replace logic ++ for (NonNullList inv : this.inventory.getComponents()) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end + } + + this.setCamera(this); // Remove spectated target diff --git a/Remapped-Spigot-Server-Patches/0342-Optimize-Captured-TileEntity-Lookup.patch b/Remapped-Spigot-Server-Patches/0342-Optimize-Captured-TileEntity-Lookup.patch new file mode 100644 index 000000000..5c22ff496 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0342-Optimize-Captured-TileEntity-Lookup.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 6 Apr 2019 10:16:48 -0400 +Subject: [PATCH] Optimize Captured TileEntity Lookup + +upstream was doing a containsKey/get pattern, and always doing it at that. +that scenario is only even valid if were in the middle of a block place. + +Optimize to check if the captured list even has values in it, and also to +just do a get call since the value can never be null. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index c8542636e89748699d608eb29569cacb6321d334..5193271bc257248e0d2bc9d9a477e999a97deada 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -968,12 +968,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return null; + } else { + // CraftBukkit start +- if (capturedTileEntities.containsKey(blockposition)) { +- return capturedTileEntities.get(blockposition); ++ BlockEntity tileentity = null; // Paper ++ if (!capturedTileEntities.isEmpty() && (tileentity = capturedTileEntities.get(blockposition)) != null) { // Paper ++ return tileentity; // Paper + } + // CraftBukkit end + +- BlockEntity tileentity = null; ++ //TileEntity tileentity = null; // Paper - move up + + if (this.updatingBlockEntities) { + tileentity = this.getPendingBlockEntityAt(blockposition); diff --git a/Remapped-Spigot-Server-Patches/0343-Add-Heightmap-API.patch b/Remapped-Spigot-Server-Patches/0343-Add-Heightmap-API.patch new file mode 100644 index 000000000..2926bb8ee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0343-Add-Heightmap-API.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 1 Jan 2019 02:22:01 -0800 +Subject: [PATCH] Add Heightmap API + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5193271bc257248e0d2bc9d9a477e999a97deada..eb88d830fb45a6b8c990e8bdc1943d80f63c8b93 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -670,8 +670,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + } + +- @Override +- public int getHeight(Heightmap.Types heightmap, int x, int z) { ++ public final int getHighestBlockY(final Heightmap.Types heightmap, final int x, final int z) { return this.getHeight(heightmap, x, z); } // Paper - OBFHELPER ++ @Override public int getHeight(Heightmap.Types heightmap, int x, int z) { // Paper - OBFHELPER + int k; + + if (x >= -30000000 && z >= -30000000 && x < 30000000 && z < 30000000) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 7b5abccac9793811bd56340c8f9d23806e832365..a4231e1c3d468355c0b55ac9d2c239f1b4c54594 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -325,6 +325,29 @@ public class CraftWorld implements World { + return getHighestBlockYAt(x, z, org.bukkit.HeightMap.MOTION_BLOCKING); + } + ++ // Paper start - Implement heightmap api ++ @Override ++ public int getHighestBlockYAt(final int x, final int z, final com.destroystokyo.paper.HeightmapType heightmap) throws UnsupportedOperationException { ++ this.getChunkAt(x >> 4, z >> 4); // heightmap will ret 0 on unloaded areas ++ ++ switch (heightmap) { ++ case LIGHT_BLOCKING: ++ throw new UnsupportedOperationException(); // TODO ++ //return this.world.getHighestBlockY(HeightMap.Type.LIGHT_BLOCKING, x, z); ++ case ANY: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE, x, z); ++ case SOLID: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.Heightmap.Types.OCEAN_FLOOR, x, z); ++ case SOLID_OR_LIQUID: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, x, z); ++ case SOLID_OR_LIQUID_NO_LEAVES: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); ++ default: ++ throw new UnsupportedOperationException(); ++ } ++ } ++ // Paper end ++ + @Override + public Location getSpawnLocation() { + BlockPos spawn = world.getSpawn(); diff --git a/Remapped-Spigot-Server-Patches/0344-Mob-Spawner-API-Enhancements.patch b/Remapped-Spigot-Server-Patches/0344-Mob-Spawner-API-Enhancements.patch new file mode 100644 index 000000000..b3ee86473 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0344-Mob-Spawner-API-Enhancements.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 19 Apr 2019 12:41:13 -0500 +Subject: [PATCH] Mob Spawner API Enhancements + + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 3a7aec9bd2f3fd1b4a1981fb6a8c64b69e4875f8..6ca378ec7868b855d46c749910c656f82ddb009f 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -65,6 +65,7 @@ public abstract class BaseSpawner { + this.spawnPotentials.clear(); // CraftBukkit - SPIGOT-3496, MC-92282 + } + ++ public boolean isActivated() { return isNearPlayer(); } // Paper - OBFHELPER + private boolean isNearPlayer() { + BlockPos blockposition = this.getPos(); + +@@ -221,6 +222,7 @@ public abstract class BaseSpawner { + } + } + ++ public void resetTimer() { delay(); } // Paper - OBFHELPER + private void delay() { + if (this.maxSpawnDelay <= this.minSpawnDelay) { + this.spawnDelay = this.minSpawnDelay; +@@ -238,7 +240,13 @@ public abstract class BaseSpawner { + } + + public void load(CompoundTag tag) { ++ // Paper start - use larger int if set ++ if (tag.contains("Paper.Delay")) { ++ this.spawnDelay = tag.getInt("Paper.Delay"); ++ } else { + this.spawnDelay = tag.getShort("Delay"); ++ } ++ // Paper end + this.spawnPotentials.clear(); + if (tag.contains("SpawnPotentials", 9)) { + ListTag nbttaglist = tag.getList("SpawnPotentials", 10); +@@ -253,10 +261,15 @@ public abstract class BaseSpawner { + } else if (!this.spawnPotentials.isEmpty()) { + this.setNextSpawnData((SpawnData) WeighedRandom.getRandomItem(this.getLevel().random, this.spawnPotentials)); + } +- ++ // Paper start - use ints if set ++ if (tag.contains("Paper.MinSpawnDelay", 99)) { ++ this.minSpawnDelay = tag.getInt("Paper.MinSpawnDelay"); ++ this.maxSpawnDelay = tag.getInt("Paper.MaxSpawnDelay"); ++ this.spawnCount = tag.getShort("SpawnCount"); ++ } else // Paper end + if (tag.contains("MinSpawnDelay", 99)) { +- this.minSpawnDelay = tag.getShort("MinSpawnDelay"); +- this.maxSpawnDelay = tag.getShort("MaxSpawnDelay"); ++ this.minSpawnDelay = tag.getInt("MinSpawnDelay"); ++ this.maxSpawnDelay = tag.getInt("MaxSpawnDelay"); + this.spawnCount = tag.getShort("SpawnCount"); + } + +@@ -281,9 +294,20 @@ public abstract class BaseSpawner { + if (minecraftkey == null) { + return tag; + } else { +- tag.putShort("Delay", (short) this.spawnDelay); +- tag.putShort("MinSpawnDelay", (short) this.minSpawnDelay); +- tag.putShort("MaxSpawnDelay", (short) this.maxSpawnDelay); ++ // Paper start ++ if (spawnDelay > Short.MAX_VALUE) { ++ tag.putInt("Paper.Delay", this.spawnDelay); ++ } ++ tag.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay)); ++ ++ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) { ++ tag.putInt("Paper.MinSpawnDelay", this.minSpawnDelay); ++ tag.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay); ++ } ++ ++ tag.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay)); ++ tag.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay)); ++ // Paper end + tag.putShort("SpawnCount", (short) this.spawnCount); + tag.putShort("MaxNearbyEntities", (short) this.maxNearbyEntities); + tag.putShort("RequiredPlayerRange", (short) this.requiredPlayerRange); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +index daaf861041cf7c8f59c85535ecb99e402ab4f658..a5b88b545e08eaabf894305a9bee31c55c5b1b87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +@@ -1,12 +1,20 @@ + package org.bukkit.craftbukkit.block; + + import com.google.common.base.Preconditions; ++import net.minecraft.core.Registry; ++import net.minecraft.nbt.CompoundTag; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.SpawnData; + import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + import org.bukkit.Material; + import org.bukkit.block.Block; + import org.bukkit.block.CreatureSpawner; + import org.bukkit.entity.EntityType; ++// Paper start ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.inventory.ItemStack; ++// Paper end + + public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner { + +@@ -120,4 +128,30 @@ public class CraftCreatureSpawner extends CraftBlockEntityState +Date: Mon, 6 May 2019 01:29:25 -0400 +Subject: [PATCH] Per-Player View Distance API placeholders + +I hope to look at this more in-depth soon. It appears doable. +However this should not block the update. + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 37a9e9df7f7f816c214c37e545288bf9329626ed..ec9436005a3a6fdfb4783d1092bb361224eb6414 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -625,9 +625,10 @@ public class EnderDragon extends Mob implements Enemy { + if (this.dragonDeathTime == 1 && !this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1028, this.getChunkCoordinates(), 0); +- //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (net.minecraft.server.level.ServerPlayer player : (List) ((ServerLevel)level).players()) { +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // Paper end + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 3a80869dc3c16cb81ac87100f28d63eee722067f..edd231568b75330d0cffbecb03a7e9dbc55d5f94 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -46,6 +46,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerBossEvent; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; +@@ -255,9 +256,9 @@ public class WitherBoss extends Monster implements RangedAttackMob { + if (!this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1023, new BlockPosition(this), 0); +- //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (ServerPlayer player : (List)this.level.players()) { +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index c2c6eb54096ef85b01c0b700cbe6a8054b62729f..20de8e358789d05bb5ac15e4cdd7dda85b61b7f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2240,6 +2240,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + super.remove(); + } + } ++ ++ @Override ++ public int getViewDistance() { ++ throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ } ++ ++ @Override ++ public void setViewDistance(int viewDistance) { ++ throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0346-Fix-CB-call-to-changed-postToMainThread-method.patch b/Remapped-Spigot-Server-Patches/0346-Fix-CB-call-to-changed-postToMainThread-method.patch new file mode 100644 index 000000000..5853bd79e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0346-Fix-CB-call-to-changed-postToMainThread-method.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Fri, 10 May 2019 18:38:19 +0100 +Subject: [PATCH] Fix CB call to changed postToMainThread method + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ed4129a51351aff16455960d71a0add1b8209c02..4f99c3d06e3b994708c699395adf481a6828e097 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -443,7 +443,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + this.connection.getClass(); + // CraftBukkit - Don't wait +- minecraftserver.wrapRunnable(networkmanager::handleDisconnection); ++ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper + } + + private void filterTextPacket(T text, Consumer consumer, BiFunction>> backingFilterer) { diff --git a/Remapped-Spigot-Server-Patches/0347-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/Remapped-Spigot-Server-Patches/0347-Fix-sounds-when-item-frames-are-modified-MC-123450.patch new file mode 100644 index 000000000..262820c22 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0347-Fix-sounds-when-item-frames-are-modified-MC-123450.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 27 Apr 2019 20:00:43 +0100 +Subject: [PATCH] Fix sounds when item frames are modified (MC-123450) + +This also fixes the adding sound playing when the item frame direction is changed. + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index d6e6846a12a889222ced67937c09d184a64c60b9..429d8a50a35f07bfc16dbedf28560fa6df817644 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -277,7 +277,7 @@ public class ItemFrame extends HangingEntity { + } + + this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack); +- if (!itemstack.isEmpty() && playSound) { // CraftBukkit ++ if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set + this.playSound(SoundEvents.ITEM_FRAME_ADD_ITEM, 1.0F, 1.0F); + } + diff --git a/Remapped-Spigot-Server-Patches/0348-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch b/Remapped-Spigot-Server-Patches/0348-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch new file mode 100644 index 000000000..367d2e18c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0348-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 13 May 2019 21:10:59 -0700 +Subject: [PATCH] Fix CraftServer#isPrimaryThread and MinecraftServer + isMainThread + +md_5 changed it so he could shut down the server asynchronously +from watchdog, although we have patches that prevent that type +of behavior for this exact reason. + +md_5 also placed code in PlayerConnectionUtils that would have +solved https://bugs.mojang.com/browse/MC-142590, making the change +to MinecraftServer#isMainThread irrelevant. +By reverting his change to MinecraftServer#isMainThread packet +handling that should have been handled synchronously will be handled +synchronously when the server gets shut down. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 35984c7e994570a909ed4ffaabe64ae941b15e71..9beda5e429b5c520a41d9c7f536dc48dbb6f6f9e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2190,7 +2190,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 28 Sep 2018 21:49:53 -0400 +Subject: [PATCH] Fix issues with entity loss due to unloaded chunks + +Vanilla has risk of losing entities by causing them to be +removed from all chunks if they try to move into an unloaded chunk. + +This pretty much means high chance this entity will be lost in this +scenario. + +There is another case that adding an entity to the world can fail if +the chunk isn't loaded. + +Lots of the server is designed around addEntity never expecting to fail +for these reasons, nor is it really logical. + +This change ensures the chunks are always loaded when entities are +added to the world, or a valid entity moves between chunks. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index d03b4f97102dfb88927a94ee5a5d397ac493eaa1..99883c83c126405fc93becefed8a1d0727b94aa7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -848,11 +848,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + int k = Mth.floor(entity.getZ() / 16.0D); + + if (!entity.inChunk || entity.xChunk != i || entity.yChunk != j || entity.zChunk != k) { ++ // Paper start - remove entity if its in a chunk more correctly. ++ LevelChunk currentChunk = entity.getCurrentChunk(); ++ if (currentChunk != null) { ++ currentChunk.removeEntity(entity); ++ } ++ // Paper end ++ + if (entity.inChunk && this.hasChunk(entity.xChunk, entity.zChunk)) { + this.getChunk(entity.xChunk, entity.zChunk).removeEntity(entity, entity.yChunk); + } + +- if (!entity.checkAndResetForcedChunkAdditionFlag() && !this.hasChunk(i, k)) { ++ if (!entity.valid && !entity.checkAndResetForcedChunkAdditionFlag() && !this.hasChunk(i, k)) { // Paper - always load chunks to register valid entities location + if (entity.inChunk) { + ServerLevel.LOGGER.warn("Entity {} left loaded chunk area", entity); + } +@@ -1067,7 +1074,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return false; + } + // CraftBukkit end +- ChunkAccess ichunkaccess = this.getChunk(Mth.floor(entity.getX() / 16.0D), Mth.floor(entity.getZ() / 16.0D), ChunkStatus.FULL, entity.forcedLoading); ++ ChunkAccess ichunkaccess = this.getChunk(Mth.floor(entity.getX() / 16.0D), Mth.floor(entity.getZ() / 16.0D), ChunkStatus.FULL, true); // Paper - always load chunks for entity adds + + if (!(ichunkaccess instanceof LevelChunk)) { + return false; diff --git a/Remapped-Spigot-Server-Patches/0350-Duplicate-UUID-Resolve-Option.patch b/Remapped-Spigot-Server-Patches/0350-Duplicate-UUID-Resolve-Option.patch new file mode 100644 index 000000000..29914b85b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0350-Duplicate-UUID-Resolve-Option.patch @@ -0,0 +1,249 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 14:27:34 -0400 +Subject: [PATCH] Duplicate UUID Resolve Option + +Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 +which was added all the way back in March of 2016, it was unknown (potentially not at the time) +that an entity might actually change the seed of the random object. + +At some point, EntitySquid did start setting the seed. Due to this shared random, this caused +every entity to use a Random object with a predictable seed. + +This has caused entities to potentially generate with the same UUID.... + +Over the years, servers have had entities disappear, but no sign of trouble +because CraftBukkit removed the log lines indicating that something was wrong. + +We have fixed the root issue causing duplicate UUID's, however we now have chunk +files full of entities that have the same UUID as another entity! + +When these chunks load, the 2nd entity will not be added to the world correctly. + +If that chunk loads in a different order in the future, then it will reverse and the +missing one is now the one added to the world and not the other. This results in very +inconsistent entity behavior. + +This change allows you to recover any duplicate entity by generating a new UUID for it. +This also lets you delete them instead if you don't want to risk having new entities added to +the world that you previously did not see. + +But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. + +It is recommended you regenerate the entities, as these were legit entities, and deserve your love. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fbf3ccfb347a5ba6e895339e9576629d940d1aa4..38d25a12c6a52d8a83214e2a0f43a218cf15ceac 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -401,4 +401,43 @@ public class PaperWorldConfig { + private void preventMovingIntoUnloadedChunks() { + preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); + } ++ ++ public enum DuplicateUUIDMode { ++ SAFE_REGEN, DELETE, NOTHING, WARN ++ } ++ public DuplicateUUIDMode duplicateUUIDMode = DuplicateUUIDMode.SAFE_REGEN; ++ public int duplicateUUIDDeleteRange = 32; ++ private void repairDuplicateUUID() { ++ String desiredMode = getString("duplicate-uuid-resolver", "saferegen").toLowerCase().trim(); ++ duplicateUUIDDeleteRange = getInt("duplicate-uuid-saferegen-delete-range", duplicateUUIDDeleteRange); ++ switch (desiredMode.toLowerCase()) { ++ case "regen": ++ case "regenerate": ++ case "saferegen": ++ case "saferegenerate": ++ duplicateUUIDMode = DuplicateUUIDMode.SAFE_REGEN; ++ log("Duplicate UUID Resolve: Regenerate New UUID if distant (Delete likely duplicates within " + duplicateUUIDDeleteRange + " blocks)"); ++ break; ++ case "remove": ++ case "delete": ++ duplicateUUIDMode = DuplicateUUIDMode.DELETE; ++ log("Duplicate UUID Resolve: Delete Entity"); ++ break; ++ case "silent": ++ case "nothing": ++ duplicateUUIDMode = DuplicateUUIDMode.NOTHING; ++ logError("Duplicate UUID Resolve: Do Nothing (no logs) - Warning, may lose indication of bad things happening"); ++ break; ++ case "log": ++ case "warn": ++ duplicateUUIDMode = DuplicateUUIDMode.WARN; ++ log("Duplicate UUID Resolve: Warn (do nothing but log it happened, may be spammy)"); ++ break; ++ default: ++ duplicateUUIDMode = DuplicateUUIDMode.WARN; ++ logError("Warning: Invalid duplicate-uuid-resolver config " + desiredMode + " - must be one of: regen, delete, nothing, warn"); ++ log("Duplicate UUID Resolve: Warn (do nothing but log it happened, may be spammy)"); ++ break; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 083db6c1899b5391231b6d5d5044a334212f148c..5d87a282042d7112415b7d7175031f734219f2c9 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import co.aikar.timings.Timing; // Paper ++import com.destroystokyo.paper.PaperWorldConfig; // Paper + import com.google.common.collect.ImmutableList; + import com.google.common.collect.Iterables; + import com.google.common.collect.ComparisonChain; // Paper +@@ -23,14 +24,17 @@ import it.unimi.dsi.fastutil.objects.ObjectIterator; + import java.io.File; + import java.io.IOException; + import java.io.Writer; ++import java.util.HashMap; // Paper + import java.util.Collection; + import java.util.Iterator; + import java.util.List; ++import java.util.Map; // Paper + import java.util.Objects; + import java.util.Optional; + import java.util.Queue; + import java.util.Set; + import java.util.concurrent.CancellationException; ++import java.util.UUID; // Paper + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.CompletionException; + import java.util.concurrent.Executor; +@@ -71,6 +75,7 @@ import net.minecraft.world.entity.ai.village.poi.PoiManager; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.chunk.ChunkStatus; +@@ -697,18 +702,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (chunk.needsDecoration) { + net.minecraft.server.dedicated.DedicatedServer server = this.level.getCraftServer().getServer(); + if (!server.areNpcsEnabled() && entity instanceof net.minecraft.world.entity.npc.Npc) { +- entity.remove(); ++ entity.removed = true; // Paper + needsRemoval = true; + } + + if (!server.isSpawningAnimals() && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) { +- entity.remove(); ++ entity.removed = true; // Paper + needsRemoval = true; + } + } +- +- if (!(entity instanceof net.minecraft.world.entity.player.Player) && (needsRemoval || !this.level.loadFromChunk(entity))) { +- // CraftBukkit end ++ // CraftBukkit end ++ checkDupeUUID(entity); // Paper ++ if (!(entity instanceof net.minecraft.world.entity.player.Player) && (entity.removed || !this.level.loadFromChunk(entity))) { // Paper + if (list == null) { + list = Lists.newArrayList(new Entity[]{entity}); + } else { +@@ -735,6 +740,44 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + ++ // Paper start ++ private void checkDupeUUID(Entity entity) { ++ PaperWorldConfig.DuplicateUUIDMode mode = level.paperConfig.duplicateUUIDMode; ++ if (mode != PaperWorldConfig.DuplicateUUIDMode.WARN ++ && mode != PaperWorldConfig.DuplicateUUIDMode.DELETE ++ && mode != PaperWorldConfig.DuplicateUUIDMode.SAFE_REGEN) { ++ return; ++ } ++ Entity other = level.getEntity(entity.getUUID()); ++ ++ if (mode == PaperWorldConfig.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.removed ++ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig.duplicateUUIDDeleteRange ++ ) { ++ if (Level.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ entity.removed = true; ++ return; ++ } ++ if (other != null && !other.removed) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(UUID.randomUUID()); ++ if (Level.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ break; ++ } ++ case DELETE: { ++ if (Level.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ entity.removed = true; ++ break; ++ } ++ default: ++ if (Level.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ break; ++ } ++ } ++ } ++ // Paper end ++ + public CompletableFuture> postProcess(ChunkHolder holder) { + ChunkPos chunkcoordintpair = holder.getPos(); + CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(chunkcoordintpair, 1, (i) -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 99883c83c126405fc93becefed8a1d0727b94aa7..20b74fc8e1273fcd07ea4417eaedc8bd9aba93b3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -4,6 +4,8 @@ import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Iterables; + import co.aikar.timings.TimingHistory; // Paper + import co.aikar.timings.Timings; // Paper ++ ++import com.destroystokyo.paper.PaperWorldConfig; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Queues; +@@ -1102,7 +1104,22 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (entity1 == null) { + return false; + } else { ++ // Paper start ++ if (entity1.removed) { ++ onEntityRemoved(entity1); // remove the existing entity ++ return false; ++ } ++ // Paper end + ServerLevel.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityType.getKey(entity1.getType()), entity1.getId(), EntityType.getKey(entity.getType()), entity.getId()); // CraftBukkit // Paper ++ // Paper start ++ if (DEBUG_ENTITIES && entity.level.paperConfig.duplicateUUIDMode != PaperWorldConfig.DuplicateUUIDMode.NOTHING) { ++ if (entity1.addedToWorldStack != null) { ++ entity1.addedToWorldStack.printStackTrace(); ++ } ++ ++ getAddToWorldStackTrace(entity).printStackTrace(); ++ } ++ // Paper end + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 6faac8773136412ca129dfa884178f311e197f6e..af86c370c6f834514115a8e40659f5e1aaabec75 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2803,6 +2803,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + }); + } + ++ public final void setUUID(UUID uuid) { setUUID(uuid); } // Paper - OBFHELPER + public void setUUID(UUID uuid) { + this.uuid = uuid; + this.stringUUID = this.uuid.toString(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index db28bfe95c885cdefa855c7aaa3bcf92bc52df26..55872a17060a35b727a597bc414fecec3ada3515 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -541,6 +541,7 @@ public class LevelChunk implements ChunkAccess { + if (i != this.chunkPos.x || j != this.chunkPos.z) { + LevelChunk.LOGGER.warn("Wrong location! ({}, {}) should be ({}, {}), {}", i, j, this.chunkPos.x, this.chunkPos.z, entity); + entity.removed = true; ++ return; // Paper + } + + int k = Mth.floor(entity.getY() / 16.0D); diff --git a/Remapped-Spigot-Server-Patches/0351-improve-CraftWorld-isChunkLoaded.patch b/Remapped-Spigot-Server-Patches/0351-improve-CraftWorld-isChunkLoaded.patch new file mode 100644 index 000000000..40d4c9c49 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0351-improve-CraftWorld-isChunkLoaded.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 21 May 2019 02:34:04 +0100 +Subject: [PATCH] improve CraftWorld#isChunkLoaded + +getChunkAt will request the chunk using vanillas chunk loading system, +which while we're not going to load the chunk, does involve the server +waiting for the execution queue to get to our request; We can just query +the chunk status and get a response now, vs having to wait + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a4231e1c3d468355c0b55ac9d2c239f1b4c54594..6e9e2149d854f26826d030ee6e655ca8fa7b5141 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -396,13 +396,13 @@ public class CraftWorld implements World { + + @Override + public boolean isChunkLoaded(int x, int z) { +- return world.getChunkSource().isChunkLoaded(x, z); ++ return world.getChunkSource().getChunkAtIfLoadedImmediately(x, z) != null; // Paper + } + + @Override + public boolean isChunkGenerated(int x, int z) { + try { +- return isChunkLoaded(x, z) || world.getChunkSource().chunkMap.read(new ChunkPos(x, z)) != null; ++ return world.getChunkSource().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkSource().chunkMap.read(new ChunkPos(x, z)) != null; // Paper (TODO check if the first part can be removed) + } catch (IOException ex) { + throw new RuntimeException(ex); + } diff --git a/Remapped-Spigot-Server-Patches/0352-Configurable-Keep-Spawn-Loaded-range-per-world.patch b/Remapped-Spigot-Server-Patches/0352-Configurable-Keep-Spawn-Loaded-range-per-world.patch new file mode 100644 index 000000000..ad0345687 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0352-Configurable-Keep-Spawn-Loaded-range-per-world.patch @@ -0,0 +1,260 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 13 Sep 2014 23:14:43 -0400 +Subject: [PATCH] Configurable Keep Spawn Loaded range per world + +This lets you disable it for some worlds and lower it for others. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 38d25a12c6a52d8a83214e2a0f43a218cf15ceac..ffe9b1a63d78925e1d77b9e730aef42fed6d58fa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -440,4 +440,10 @@ public class PaperWorldConfig { + break; + } + } ++ ++ public short keepLoadedRange; ++ private void keepLoadedRange() { ++ keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16); ++ log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16)); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9beda5e429b5c520a41d9c7f536dc48dbb6f6f9e..0efe7024493f96bb54e7d8c1ea7b233a1b481a04 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -716,35 +716,36 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> 4).forEach(pair -> { ++ getChunkSource().getChunkAtMainThread(pair.x, pair.z); ++ }); ++ } ++ public void removeTicketsForSpawn(int radiusInBlocks, BlockPos spawn) { ++ // In order to respect vanilla behavior, which is ensuring everything but the spawn border can tick, we added tickets ++ // with level 31 for the non-border spawn chunks ++ ServerChunkCache chunkproviderserver = this.getChunkSource(); ++ int tickRadius = radiusInBlocks - 16; ++ ++ // remove ticking chunks ++ for (int x = -tickRadius; x <= tickRadius; x += 16) { ++ for (int z = -tickRadius; z <= tickRadius; z += 16) { ++ // radius of 2 will have the current chunk be level 31 ++ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.add(x, 0, z)), 2, Unit.INSTANCE); ++ } ++ } ++ ++ // remove border chunks ++ ++ // remove border along x axis (including corner chunks) ++ for (int x = -radiusInBlocks; x <= radiusInBlocks; x += 16) { ++ // top ++ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.add(x, 0, radiusInBlocks)), 1, Unit.INSTANCE); // level 32 ++ // bottom ++ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.add(x, 0, -radiusInBlocks)), 1, Unit.INSTANCE); // level 32 ++ } ++ ++ // remove border along z axis (excluding corner chunks) ++ for (int z = -radiusInBlocks + 16; z < radiusInBlocks; z += 16) { ++ // right ++ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.add(radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32 ++ // left ++ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.add(-radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32 ++ } ++ } ++ // Paper end ++ + public void setDefaultSpawnPos(BlockPos pos, float angle) { +- ChunkPos chunkcoordintpair = new ChunkPos(new BlockPos(this.levelData.getXSpawn(), 0, this.levelData.getZSpawn())); ++ // Paper - configurable spawn radius ++ BlockPos prevSpawn = this.getSpawn(); ++ //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.levelData.setSpawn(pos, angle); +- this.getChunkSource().removeRegionTicket(TicketType.START, chunkcoordintpair, 11, Unit.INSTANCE); +- this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), 11, Unit.INSTANCE); ++ if (this.keepSpawnInMemory) { ++ // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add ++ this.removeTicketsForSpawn(this.paperConfig.keepLoadedRange, prevSpawn); ++ this.addTicketsForSpawn(this.paperConfig.keepLoadedRange, pos); ++ } + this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); + } + +diff --git a/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java b/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java +index 2860a16c80e56edb333115f6f64f65d0e56feb11..85863211a666b299cae8a3791c182ae5094b94d9 100644 +--- a/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java ++++ b/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java +@@ -11,4 +11,6 @@ public interface ChunkProgressListener { + void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status); + + void stop(); ++ ++ void setChunkRadius(int radius); // Paper - allow changing chunk radius + } +diff --git a/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java b/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java +index 4a541f0b2582430abda6e5ff8f492e37fc903483..e810843fae13d3e83e8f509810b781859217c48b 100644 +--- a/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java ++++ b/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java +@@ -12,16 +12,24 @@ import org.apache.logging.log4j.Logger; + public class LoggerChunkProgressListener implements ChunkProgressListener { + + private static final Logger LOGGER = LogManager.getLogger(); +- private final int maxCount; ++ private int maxCount; // Paper - remove final + private int count; + private long startTime; + private long nextTickTime = Long.MAX_VALUE; + + public LoggerChunkProgressListener(int radius) { ++ // Paper start - Allow changing radius later for configurable spawn patch ++ this.setChunkRadius(radius); // Move to method ++ } ++ ++ @Override ++ public void setChunkRadius(int radius) { ++ // Paper - copied from above + int j = radius * 2 + 1; + + this.maxCount = j * j; + } ++ // Paper end + + @Override + public void updateSpawnPos(ChunkPos spawnPos) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 6e9e2149d854f26826d030ee6e655ca8fa7b5141..0cb0021fac211996c5bdbb2cfc8f54addc3b49f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1954,15 +1954,21 @@ public class CraftWorld implements World { + + @Override + public void setKeepSpawnInMemory(boolean keepLoaded) { ++ // Paper start - Configurable spawn radius ++ if (keepLoaded == world.keepSpawnInMemory) { ++ // do nothing, nothing has changed ++ return; ++ } + world.keepSpawnInMemory = keepLoaded; + // Grab the worlds spawn chunk +- BlockPos chunkcoordinates = this.world.getSpawn(); ++ BlockPos prevSpawn = this.world.getSpawn(); + if (keepLoaded) { +- world.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(chunkcoordinates), 11, Unit.INSTANCE); ++ world.addTicketsForSpawn(world.paperConfig.keepLoadedRange, prevSpawn); + } else { +- // TODO: doesn't work well if spawn changed.... +- world.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(chunkcoordinates), 11, Unit.INSTANCE); ++ // TODO: doesn't work well if spawn changed.... // paper - resolved ++ world.removeTicketsForSpawn(world.paperConfig.keepLoadedRange, prevSpawn); + } ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0353-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch b/Remapped-Spigot-Server-Patches/0353-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch new file mode 100644 index 000000000..3475c0127 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0353-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 27 May 2019 17:35:39 -0500 +Subject: [PATCH] MC-114618 - Fix EntityAreaEffectCloud from going negative + size + + +diff --git a/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java b/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java +index 949553229a55f8c8b9a5c0141409d1520eff22c7..fe4312a58b0b2ffd63db14068d99c5391e0eb0a0 100644 +--- a/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java ++++ b/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java +@@ -194,6 +194,12 @@ public class AreaEffectCloud extends Entity { + super.tick(); + boolean flag = this.isWaiting(); + float f = this.getRadius(); ++ // Paper start - fix MC-114618 ++ if (f < 0.0F) { ++ this.remove(); ++ return; ++ } ++ // Paper end + + if (this.level.isClientSide) { + ParticleOptions particleparam = this.getParticle(); diff --git a/Remapped-Spigot-Server-Patches/0354-ChunkMapDistance-CME.patch b/Remapped-Spigot-Server-Patches/0354-ChunkMapDistance-CME.patch new file mode 100644 index 000000000..09a7ae7ee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0354-ChunkMapDistance-CME.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 29 May 2019 04:01:22 +0100 +Subject: [PATCH] ChunkMapDistance CME + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 0b8cbf75ff01b9825141be00d63679f7bcc58a9f..89e90806b78d94d5c1d781113da420dafa47930a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -44,6 +44,7 @@ public class ChunkHolder { + private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); + private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); + private static final ChunkHolder.FullChunkStatus[] FULL_CHUNK_STATUSES = ChunkHolder.FullChunkStatus.values(); ++ boolean isUpdateQueued = false; // Paper + private final AtomicReferenceArray>> futures; + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 8f993f15ae02c2e4af9cc732cd1b040cce0a67e8..71a51cc99e26579e765f88340588e23956888929 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -39,7 +39,16 @@ public abstract class DistanceManager { + private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); + private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); +- private final Set chunksToUpdateFutures = Sets.newHashSet(); ++ // Paper start use a queue, but still keep unique requirement ++ public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { ++ @Override ++ public boolean add(ChunkHolder o) { ++ if (o.isUpdateQueued) return true; ++ o.isUpdateQueued = true; ++ return super.add(o); ++ } ++ }; ++ // Paper end + private final ChunkTaskPriorityQueueSorter ticketThrottler; + private final ProcessorHandle> ticketThrottlerInput; + private final ProcessorHandle ticketThrottlerReleaser; +@@ -100,26 +109,14 @@ public abstract class DistanceManager { + ; + } + +- if (!this.chunksToUpdateFutures.isEmpty()) { +- // CraftBukkit start +- // Iterate pending chunk updates with protection against concurrent modification exceptions +- java.util.Iterator iter = this.chunksToUpdateFutures.iterator(); +- int expectedSize = this.chunksToUpdateFutures.size(); +- do { +- ChunkHolder playerchunk = iter.next(); +- iter.remove(); +- expectedSize--; +- +- playerchunk.updateFutures(chunkStorage); +- +- // Reset iterator if set was modified using add() +- if (this.chunksToUpdateFutures.size() != expectedSize) { +- expectedSize = this.chunksToUpdateFutures.size(); +- iter = this.chunksToUpdateFutures.iterator(); +- } +- } while (iter.hasNext()); +- // CraftBukkit end +- ++ // Paper start ++ if (!this.pendingChunkUpdates.isEmpty()) { ++ while(!this.pendingChunkUpdates.isEmpty()) { ++ ChunkHolder remove = this.pendingChunkUpdates.remove(); ++ remove.isUpdateQueued = false; ++ remove.updateFutures(chunkStorage); ++ } ++ // Paper end + return true; + } else { + if (!this.ticketsToRelease.isEmpty()) { +@@ -342,7 +339,7 @@ public abstract class DistanceManager { + if (k != level) { + playerchunk = DistanceManager.this.updateChunkScheduling(id, level, playerchunk, k); + if (playerchunk != null) { +- DistanceManager.this.chunksToUpdateFutures.add(playerchunk); ++ DistanceManager.this.pendingChunkUpdates.add(playerchunk); + } + + } +@@ -373,7 +370,7 @@ public abstract class DistanceManager { + ObjectIterator objectiterator = this.chunks.long2ByteEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next(); ++ Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (Long2ByteMap.Entry) objectiterator.next(); // Paper - decompile fix + byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); + long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); + diff --git a/Remapped-Spigot-Server-Patches/0355-Implement-CraftBlockSoundGroup.patch b/Remapped-Spigot-Server-Patches/0355-Implement-CraftBlockSoundGroup.patch new file mode 100644 index 000000000..0fd2f20b1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0355-Implement-CraftBlockSoundGroup.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: simpleauthority +Date: Tue, 28 May 2019 03:48:51 -0700 +Subject: [PATCH] Implement CraftBlockSoundGroup + + +diff --git a/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a516520d975f52169e346adc4ec6d9db843db2f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +@@ -0,0 +1,38 @@ ++package com.destroystokyo.paper.block; ++ ++import net.minecraft.world.level.block.SoundType; ++import org.bukkit.Sound; ++import org.bukkit.craftbukkit.CraftSound; ++ ++public class CraftBlockSoundGroup implements BlockSoundGroup { ++ private final SoundType soundEffectType; ++ ++ public CraftBlockSoundGroup(SoundType soundEffectType) { ++ this.soundEffectType = soundEffectType; ++ } ++ ++ @Override ++ public Sound getBreakSound() { ++ return CraftSound.getBukkit(soundEffectType.getBreakSound()); ++ } ++ ++ @Override ++ public Sound getStepSound() { ++ return CraftSound.getBukkit(soundEffectType.getStepSound()); ++ } ++ ++ @Override ++ public Sound getPlaceSound() { ++ return CraftSound.getBukkit(soundEffectType.getPlaceSound()); ++ } ++ ++ @Override ++ public Sound getHitSound() { ++ return CraftSound.getBukkit(soundEffectType.getHitSound()); ++ } ++ ++ @Override ++ public Sound getFallSound() { ++ return CraftSound.getBukkit(soundEffectType.getFallSound()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/SoundType.java b/src/main/java/net/minecraft/world/level/block/SoundType.java +index 1d3acbbc80a38998fb38e0ce37af52103f677721..44394adbe60b5e9c4654ee2f437d465bef5909a8 100644 +--- a/src/main/java/net/minecraft/world/level/block/SoundType.java ++++ b/src/main/java/net/minecraft/world/level/block/SoundType.java +@@ -54,10 +54,10 @@ public class SoundType { + public static final SoundType GILDED_BLACKSTONE = new SoundType(1.0F, 1.0F, SoundEvents.GILDED_BLACKSTONE_BREAK, SoundEvents.GILDED_BLACKSTONE_STEP, SoundEvents.GILDED_BLACKSTONE_PLACE, SoundEvents.GILDED_BLACKSTONE_HIT, SoundEvents.GILDED_BLACKSTONE_FALL); + public final float volume; + public final float pitch; +- public final SoundEvent breakSound; ++ public final SoundEvent breakSound; public final SoundEvent getBreakSound() { return this.breakSound; } // Paper - OBFHELPER // PAIL private -> public, rename breakSound + private final SoundEvent stepSound; + private final SoundEvent placeSound; +- public final SoundEvent hitSound; ++ public final SoundEvent hitSound; public final SoundEvent getHitSound() { return this.hitSound; } // Paper - OBFHELPER // PAIL private -> public, rename hitSound + private final SoundEvent fallSound; + + public SoundType(float volume, float pitch, SoundEvent breakSound, SoundEvent stepSound, SoundEvent placeSound, SoundEvent hitSound, SoundEvent fallSound) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index d73086970db19531db66c2e8af52da91d0b1ea28..5bff313dbbb3049105874846d995883e827fbc00 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -747,4 +747,11 @@ public class CraftBlock implements Block { + AABB aabb = shape.bounds(); + return new BoundingBox(getX() + aabb.minX, getY() + aabb.minY, getZ() + aabb.minZ, getX() + aabb.maxX, getY() + aabb.maxY, getZ() + aabb.maxZ); + } ++ ++ // Paper start ++ @Override ++ public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { ++ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMSBlock().defaultBlockState().getSoundType()); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0356-Chunk-debug-command.patch b/Remapped-Spigot-Server-Patches/0356-Chunk-debug-command.patch new file mode 100644 index 000000000..a8a34d2b8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0356-Chunk-debug-command.patch @@ -0,0 +1,490 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jun 2019 13:00:55 -0700 +Subject: [PATCH] Chunk debug command + +Prints all chunk information to a text file into the debug +folder in the root server folder. The format is in JSON, and +the data format is described in MCUtil#dumpChunks(File) + +The command will output server version and all online players to the +file as well. We do not log anything but the location, world and +username of the player. + +Also logs the value of these config values (note not all are paper's): +- keep spawn loaded value +- spawn radius +- view distance + +Each chunk has the following logged: +- Coordinate +- Ticket level & its corresponding state +- Whether it is queued for unload +- Chunk status (may be unloaded) +- All tickets on the chunk + +Example log: +https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt + +For references on certain keywords (ticket, status, etc), please see: + +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273 +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577 + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 8fd716bf2e1402694798b8be03fd85821153be44..53dd6c18de8e80378852bbb141016d9574d42162 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -6,13 +6,15 @@ import com.google.common.collect.ImmutableSet; + import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; +-import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import org.apache.commons.lang3.tuple.MutablePair; + import org.apache.commons.lang3.tuple.Pair; + import org.bukkit.Bukkit; +@@ -41,7 +43,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); + + public PaperCommand(String name) { + super(name); +@@ -69,6 +71,21 @@ public class PaperCommand extends Command { + if (args.length == 3) + return getListMatchingLast(sender, args, EntityType.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); + break; ++ case "debug": ++ if (args.length == 2) { ++ return getListMatchingLast(sender, args, "help", "chunks"); ++ } ++ break; ++ case "chunkinfo": ++ List worldNames = new ArrayList<>(); ++ worldNames.add("*"); ++ for (org.bukkit.World world : Bukkit.getWorlds()) { ++ worldNames.add(world.getName()); ++ } ++ if (args.length == 2) { ++ return getListMatchingLast(sender, args, worldNames); ++ } ++ break; + } + return Collections.emptyList(); + } +@@ -135,6 +152,12 @@ public class PaperCommand extends Command { + case "reload": + doReload(sender); + break; ++ case "debug": ++ doDebug(sender, args); ++ break; ++ case "chunkinfo": ++ doChunkInfo(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -152,6 +175,114 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doChunkInfo(CommandSender sender, String[] args) { ++ List worlds; ++ if (args.length < 2 || args[1].equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ worlds = new ArrayList<>(args.length - 1); ++ for (int i = 1; i < args.length; ++i) { ++ org.bukkit.World world = Bukkit.getWorld(args[i]); ++ if (world == null) { ++ sender.sendMessage(ChatColor.RED + "World '" + args[i] + "' is invalid"); ++ return; ++ } ++ worlds.add(world); ++ } ++ } ++ ++ int accumulatedTotal = 0; ++ int accumulatedInactive = 0; ++ int accumulatedBorder = 0; ++ int accumulatedTicking = 0; ++ int accumulatedEntityTicking = 0; ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle(); ++ ++ int total = 0; ++ int inactive = 0; ++ int border = 0; ++ int ticking = 0; ++ int entityTicking = 0; ++ ++ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { ++ if (chunk.getFullChunkIfCached() == null) { ++ continue; ++ } ++ ++ ++total; ++ ++ ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel()); ++ ++ switch (state) { ++ case INACCESSIBLE: ++ ++inactive; ++ continue; ++ case BORDER: ++ ++border; ++ continue; ++ case TICKING: ++ ++ticking; ++ continue; ++ case ENTITY_TICKING: ++ ++entityTicking; ++ continue; ++ } ++ } ++ ++ accumulatedTotal += total; ++ accumulatedInactive += inactive; ++ accumulatedBorder += border; ++ accumulatedTicking += ticking; ++ accumulatedEntityTicking += entityTicking; ++ ++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + bukkitWorld.getName() + ChatColor.DARK_AQUA + ":"); ++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + total + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA ++ + inactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + border + ChatColor.BLUE + " Ticking: " ++ + ChatColor.DARK_AQUA + ticking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + entityTicking); ++ } ++ if (worlds.size() > 1) { ++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + "all listed worlds" + ChatColor.DARK_AQUA + ":"); ++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + accumulatedTotal + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA ++ + accumulatedInactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + accumulatedBorder + ChatColor.BLUE + " Ticking: " ++ + ChatColor.DARK_AQUA + accumulatedTicking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + accumulatedEntityTicking); ++ } ++ } ++ ++ private void doDebug(CommandSender sender, String[] args) { ++ if (args.length < 2) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ ++ String debugType = args[1].toLowerCase(Locale.ENGLISH); ++ switch (debugType) { ++ case "chunks": ++ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file"); ++ break; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ sender.sendMessage(ChatColor.GREEN + "Writing chunk information dump to " + file.toString()); ++ try { ++ MCUtil.dumpChunks(file); ++ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!"); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console"); ++ } ++ ++ break; ++ case "help": ++ // fall through to default ++ default: ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ } ++ + /* + * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 + */ +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 1fecc81b25109592907623741225a6222a8c5ccc..a16551c81a444685f6337a65b6d7862b8c0dc684 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -9,13 +9,27 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.DistanceManager; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.Ticket; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import org.apache.commons.lang.exception.ExceptionUtils; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; + import com.mojang.authlib.GameProfile; ++import com.mojang.datafixers.util.Either; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; + import org.bukkit.craftbukkit.CraftWorld; +@@ -24,8 +38,11 @@ import org.spigotmc.AsyncCatcher; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import java.io.*; ++import java.util.ArrayList; + import java.util.List; + import java.util.Queue; ++import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.LinkedBlockingQueue; +@@ -531,4 +548,170 @@ public final class MCUtil { + + return null; + } ++ ++ public static ChunkStatus getChunkStatus(ChunkHolder chunk) { ++ List statuses = ServerChunkCache.getPossibleChunkStatuses(); ++ for (int i = statuses.size() - 1; i >= 0; --i) { ++ ChunkStatus curr = statuses.get(i); ++ CompletableFuture> future = chunk.getFutureIfPresentUnchecked(curr); ++ if (future != ChunkHolder.UNLOADED_CHUNK_FUTURE) { ++ return curr; ++ } ++ } ++ return null; // unloaded ++ } ++ ++ public static void dumpChunks(File file) throws IOException { ++ file.getParentFile().mkdirs(); ++ file.createNewFile(); ++ /* ++ * Json format: ++ * ++ * Main data format: ++ * -server-version: ++ * -data-version: ++ * -worlds: ++ * -name: ++ * -view-distance: ++ * -keep-spawn-loaded: ++ * -keep-spawn-loaded-range: ++ * -visible-chunk-count: ++ * -loaded-chunk-count: ++ * -verified-fully-loaded-chunks: ++ * -players: ++ * -chunk-data: ++ * ++ * Player format: ++ * -name: ++ * -x: ++ * -y: ++ * -z: ++ * ++ * Chunk Format: ++ * -x: ++ * -z: ++ * -ticket-level: ++ * -state: ++ * -queued-for-unload: ++ * -status: ++ * -tickets: ++ * ++ * ++ * Ticket format: ++ * -ticket-type: ++ * -ticket-level: ++ * -add-tick: ++ * -object-reason: // This depends on the type of ticket. ie POST_TELEPORT -> entity id ++ */ ++ List worlds = org.bukkit.Bukkit.getWorlds(); ++ JsonObject data = new JsonObject(); ++ ++ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); ++ data.addProperty("data-version", 0); ++ ++ JsonArray worldsData = new JsonArray(); ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ JsonObject worldData = new JsonObject(); ++ ++ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); ++ ChunkMap chunkMap = world.getChunkSource().chunkMap; ++ Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunkMap; ++ DistanceManager chunkMapDistance = chunkMap.distanceManager; ++ List allChunks = new ArrayList<>(visibleChunks.values()); ++ List players = world.players; ++ ++ int fullLoadedChunks = 0; ++ ++ for (ChunkHolder chunk : allChunks) { ++ if (chunk.getFullChunkIfCached() != null) { ++ ++fullLoadedChunks; ++ } ++ } ++ ++ // sorting by coordinate makes the log easier to read ++ allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> { ++ if (v1.location.x != v2.location.x) { ++ return Integer.compare(v1.location.x, v2.location.x); ++ } ++ return Integer.compare(v1.location.z, v2.location.z); ++ }); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); ++ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); ++ worldData.addProperty("visible-chunk-count", visibleChunks.size()); ++ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size()); ++ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks); ++ ++ JsonArray playersData = new JsonArray(); ++ ++ for (ServerPlayer player : players) { ++ JsonObject playerData = new JsonObject(); ++ ++ playerData.addProperty("name", player.getScoreboardName()); ++ playerData.addProperty("x", player.getX()); ++ playerData.addProperty("y", player.getY()); ++ playerData.addProperty("z", player.getZ()); ++ ++ playersData.add(playerData); ++ ++ } ++ ++ worldData.add("players", playersData); ++ ++ JsonArray chunksData = new JsonArray(); ++ ++ for (ChunkHolder playerChunk : allChunks) { ++ JsonObject chunkData = new JsonObject(); ++ ++ Set> tickets = chunkMapDistance.tickets.get(playerChunk.pos.pair()); ++ ChunkStatus status = getChunkStatus(playerChunk); ++ ++ chunkData.addProperty("x", playerChunk.location.x); ++ chunkData.addProperty("z", playerChunk.location.z); ++ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString()); ++ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.pair())); ++ chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); ++ ++ JsonArray ticketsData = new JsonArray(); ++ ++ if (tickets != null) { ++ for (Ticket ticket : tickets) { ++ JsonObject ticketData = new JsonObject(); ++ ++ ticketData.addProperty("ticket-type", ticket.getType().toString()); ++ ticketData.addProperty("ticket-level", ticket.getTicketLevel()); ++ ticketData.addProperty("object-reason", String.valueOf(ticket.getObjectReason())); ++ ticketData.addProperty("add-tick", ticket.getCreationTick()); ++ ++ ticketsData.add(ticketData); ++ } ++ } ++ ++ chunkData.add("tickets", ticketsData); ++ chunksData.add(chunkData); ++ } ++ ++ ++ worldData.add("chunk-data", chunksData); ++ worldsData.add(worldData); ++ } ++ ++ data.add("worlds", worldsData); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { ++ out.print(fileData); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 89e90806b78d94d5c1d781113da420dafa47930a..a89b9dab043ad4536014141d5a942670b4152a95 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -53,7 +53,7 @@ public class ChunkHolder { + public int oldTicketLevel; + private int ticketLevel; + private int queueLevel; +- private final ChunkPos pos; ++ final ChunkPos pos; // Paper - private -> package + private boolean hasChangedSections; + private final ShortSet[] changedBlocksPerSection; + private int blockChangedLightSectionFilter; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5d87a282042d7112415b7d7175031f734219f2c9..7585b6f87b72f53deccbcb8627a13503921fc682 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -104,7 +104,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); + public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; + private final Long2ObjectLinkedOpenHashMap pendingUnloads; +- private final LongSet entitiesInLevel; ++ public final LongSet entitiesInLevel; // Paper - private -> public + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; + private final BlockableEventLoop mainThreadExecutor; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index a7122a0411f4a8656efd4facde3403c8093bc8a6..6d33c1ee44bc732b58d18a8f6b0fd4bbdcb2dcd6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -46,7 +46,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + + public class ServerChunkCache extends ChunkSource { + +- private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); ++ private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); public static final List getPossibleChunkStatuses() { return ServerChunkCache.CHUNK_STATUSES; } // Paper - OBFHELPER + private final DistanceManager distanceManager; + public final ChunkGenerator generator; + private final ServerLevel level; +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index 0c118d482e304c567fe7fe778c6ff386f960bdde..c6b5f32153b63ac92df9c4b31b8de168481f79f2 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -6,8 +6,8 @@ public final class Ticket implements Comparable> { + + private final TicketType type; + private final int ticketLevel; +- public final T key; +- private long createdTick; ++ public final T key; public final T getObjectReason() { return this.key; } // Paper - OBFHELPER ++ private long createdTick; public final long getCreationTick() { return this.createdTick; } // Paper - OBFHELPER + + protected Ticket(TicketType type, int level, T argument) { + this.type = type; +@@ -51,6 +51,7 @@ public final class Ticket implements Comparable> { + return this.type; + } + ++ public final int getTicketLevel() { return this.getTicketLevel(); } // Paper - OBFHELPER + public int getTicketLevel() { + return this.ticketLevel; + } diff --git a/Remapped-Spigot-Server-Patches/0357-Catch-exceptions-from-dispenser-entity-spawns.patch b/Remapped-Spigot-Server-Patches/0357-Catch-exceptions-from-dispenser-entity-spawns.patch new file mode 100644 index 000000000..0a15b5e43 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0357-Catch-exceptions-from-dispenser-entity-spawns.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 10 Jun 2019 09:36:40 +0100 +Subject: [PATCH] Catch exceptions from dispenser entity spawns + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index dccf689d17bb5a77abf97779663413d01e840c23..67a894a185a3d4a53b3c7f90174b2604dff18257 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -8,6 +8,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.BlockSource; + import net.minecraft.core.Direction; + import net.minecraft.core.Position; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; +@@ -235,7 +236,14 @@ public interface DispenseItemBehavior { + } + } + ++ try { // Paper + entitytypes.spawn(pointer.getLevel(), stack, (Player) null, pointer.getPos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false); ++ // Paper start ++ } catch (Exception ex){ ++ MinecraftServer.LOGGER.warn("An exception occurred dispensing entity at {}[{}]", worldserver.getWorld().getName(), pointer.getPos(), ex); ++ } ++ // Paper end ++ + // itemstack.subtract(1); // Handled during event processing + // CraftBukkit end + return stack; diff --git a/Remapped-Spigot-Server-Patches/0358-Fix-World-isChunkGenerated-calls.patch b/Remapped-Spigot-Server-Patches/0358-Fix-World-isChunkGenerated-calls.patch new file mode 100644 index 000000000..6f00b2b7a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0358-Fix-World-isChunkGenerated-calls.patch @@ -0,0 +1,390 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 15 Jun 2019 08:54:33 -0700 +Subject: [PATCH] Fix World#isChunkGenerated calls + +Optimize World#loadChunk() too +This patch also adds a chunk status cache on region files (note that +its only purpose is to cache the status on DISK) + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index a89b9dab043ad4536014141d5a942670b4152a95..7010e0a970462d2b2e1b5696a1a49dba9ea60935 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -141,6 +141,19 @@ public class ChunkHolder { + Either either = (Either) statusFuture.getNow(null); + return either == null ? null : (LevelChunk) either.left().orElse(null); + } ++ ++ public ChunkAccess getAvailableChunkNow() { ++ // TODO can we just getStatusFuture(EMPTY)? ++ for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) { ++ CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); ++ Either either = future.getNow(null); ++ if (either == null || !either.left().isPresent()) { ++ continue; ++ } ++ return either.left().get(); ++ } ++ return null; ++ } + // Paper end + + public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7585b6f87b72f53deccbcb8627a13503921fc682..0aac29de933c84c34cb24e204e8fcc7010060d8f 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -991,12 +991,61 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + @Nullable +- private CompoundTag readChunk(ChunkPos pos) throws IOException { ++ public CompoundTag readChunk(ChunkPos pos) throws IOException { // Paper - private -> public + CompoundTag nbttagcompound = this.read(pos); ++ // Paper start - Cache chunk status on disk ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ nbttagcompound = this.getChunkData(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, pos, level); // CraftBukkit ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ this.updateChunkStatusOnDisk(pos, nbttagcompound); ++ ++ return nbttagcompound; ++ // Paper end ++ } ++ ++ // Paper start - chunk status cache "api" ++ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getRegionFileIfLoaded(chunkPos); ++ ++ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, true); ++ ++ if (regionFile == null || !regionFile.chunkExists(chunkPos)) { ++ return null; ++ } ++ ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ++ if (status != null) { ++ return status; ++ } ++ ++ this.readChunk(chunkPos); + +- return nbttagcompound == null ? null : this.getChunkData(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, pos, level); // CraftBukkit ++ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); + } + ++ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); ++ } ++ ++ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { ++ ChunkHolder chunkHolder = this.pendingUnloads.get(ChunkPos.asLong(chunkX, chunkZ)); ++ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); ++ } ++ // Paper end ++ + boolean noPlayersCloseForSpawning(ChunkPos chunkcoordintpair) { + // Spigot start + return isOutsideOfRange(chunkcoordintpair, false); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 6d33c1ee44bc732b58d18a8f6b0fd4bbdcb2dcd6..1e8ac0110badbf2d1c2336168c3e11991667c782 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -52,7 +52,7 @@ public class ServerChunkCache extends ChunkSource { + private final ServerLevel level; + public final Thread mainThread; // Paper - private -> public + private final ThreadedLevelLightEngine lightEngine; +- private final ServerChunkCache.MainThreadExecutor mainThreadProcessor; ++ public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; // Paper private -> public + public final ChunkMap chunkMap; + private final DimensionDataStorage dataStorage; + private long lastInhabitedUpdate; +@@ -317,6 +317,21 @@ public class ServerChunkCache extends ChunkSource { + + return ret; + } ++ ++ @Nullable ++ public ChunkAccess getChunkAtImmediately(int x, int z) { ++ long k = ChunkPos.asLong(x, z); ++ ++ // Note: Bypass cache to make this MT-Safe ++ ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k); ++ if (playerChunk == null) { ++ return null; ++ } ++ ++ return playerChunk.getAvailableChunkNow(); ++ ++ } + // Paper end + + @Nullable +@@ -771,7 +786,7 @@ public class ServerChunkCache extends ChunkSource { + return this.lastSpawnState; + } + +- final class MainThreadExecutor extends BlockableEventLoop { ++ public final class MainThreadExecutor extends BlockableEventLoop { // Paper - package -> public + + private MainThreadExecutor(Level world) { + super("Chunk source main thread executor for " + world.dimension().location()); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index ae84dc310c076e3212d3cdbca77a1ab06a11d479..46d5a24332c1fd3c164b760ec2a2d5bf859b1ab6 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -193,6 +193,7 @@ public class ChunkStatus { + return this.name; + } + ++ public ChunkStatus getPreviousStatus() { return this.getParent(); } // Paper - OBFHELPER + public ChunkStatus getParent() { + return this.parent; + } +@@ -213,6 +214,17 @@ public class ChunkStatus { + return this.chunkType; + } + ++ // Paper start ++ public static ChunkStatus getStatus(String name) { ++ try { ++ // We need this otherwise we return EMPTY for invalid names ++ ResourceLocation key = new ResourceLocation(name); ++ return Registry.CHUNK_STATUS.getOptional(key).orElse(null); ++ } catch (Exception ex) { ++ return null; // invalid name ++ } ++ } ++ // Paper end + public static ChunkStatus byName(String id) { + return (ChunkStatus) Registry.CHUNK_STATUS.get(ResourceLocation.tryParse(id)); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 542d6f322df5f44ad9f504c8e14c88e3fa540657..969130442b529eaac6f708107ff129f89cc0af90 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -462,6 +462,17 @@ public class ChunkSerializer { + } + // Paper end + ++ // Paper start ++ public static ChunkStatus getStatus(CompoundTag compound) { ++ if (compound == null) { ++ return null; ++ } ++ ++ // Note: Copied from below ++ return ChunkStatus.getStatus(compound.getCompound("Level").getString("Status")); ++ } ++ // Paper end ++ + public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag tag) { + if (tag != null) { + ChunkStatus chunkstatus = ChunkStatus.byName(tag.getCompound("Level").getString("Status")); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 637274532b01bb7b4cdb7d7b1b58181b98ac7e98..9cffef2098fbfba89ddd88a45bde33c07660497a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -21,7 +21,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage; + + public class ChunkStorage implements AutoCloseable { + +- private final IOWorker worker; ++ private final IOWorker worker; public IOWorker getIOWorker() { return worker; } // Paper - OBFHELPER + protected final DataFixer fixerUpper; + @Nullable + private LegacyStructureDataHandler legacyStructureHandler; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 424628c9588c02454558bc7e7c5bad3a3e75ec9f..4d96e5ed28c910387c0a4238c9036c7a12458f57 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -27,6 +27,7 @@ import net.minecraft.Util; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtIo; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkStatus; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +@@ -44,6 +45,30 @@ public class RegionFile implements AutoCloseable { + protected final RegionBitmap usedSectors; + public final File file; // Paper + ++ // Paper start - Cache chunk status ++ private final ChunkStatus[] statuses = new ChunkStatus[32 * 32]; ++ ++ private boolean closed; ++ ++ // invoked on write/read ++ public void setStatus(int x, int z, ChunkStatus status) { ++ if (this.closed) { ++ // We've used an invalid region file. ++ throw new IllegalStateException("RegionFile is closed"); ++ } ++ this.statuses[getChunkLocation(x, z)] = status; ++ } ++ ++ public ChunkStatus getStatusIfCached(int x, int z) { ++ if (this.closed) { ++ // We've used an invalid region file. ++ throw new IllegalStateException("RegionFile is closed"); ++ } ++ final int location = getChunkLocation(x, z); ++ return this.statuses[location]; ++ } ++ // Paper end ++ + public RegionFile(File file, File directory, boolean dsync) throws IOException { + this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync); + } +@@ -380,11 +405,13 @@ public class RegionFile implements AutoCloseable { + return this.getOffset(pos) != 0; + } + ++ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - OBFHELPER - sort of, mirror of logic below + private static int getOffsetIndex(ChunkPos pos) { + return pos.getRegionLocalX() + pos.getRegionLocalZ() * 32; + } + + public void close() throws IOException { ++ this.closed = true; // Paper + try { + this.padToFullSector(); + } finally { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 6d3e1bb20d1ab8ce5c9ea613322042d80550761a..6f1c96e4325caf6b4762700ad2286d9ea41515c9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -28,7 +28,14 @@ public final class RegionFileStorage implements AutoCloseable { + this.sync = dsync; + } + +- private RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit ++ ++ // Paper start ++ public RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { ++ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); ++ } ++ ++ // Paper end ++ public RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public + long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); + +@@ -175,6 +182,7 @@ public final class RegionFileStorage implements AutoCloseable { + + try { + NbtIo.write(tag, (DataOutput) dataoutputstream); ++ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(tag)); // Paper - cache status on disk + regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone + } catch (Throwable throwable1) { + throwable = throwable1; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 0cb0021fac211996c5bdbb2cfc8f54addc3b49f6..a0615e4ba015cca4fe074de63b87d0bff84b1a14 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -19,6 +19,7 @@ import java.util.Objects; + import java.util.Random; + import java.util.Set; + import java.util.UUID; ++import java.util.concurrent.CompletableFuture; + import java.util.function.Predicate; + import java.util.stream.Collectors; + import net.minecraft.core.BlockPos; +@@ -401,8 +402,22 @@ public class CraftWorld implements World { + + @Override + public boolean isChunkGenerated(int x, int z) { ++ // Paper start - Fix this method ++ if (!Bukkit.isPrimaryThread()) { ++ return CompletableFuture.supplyAsync(() -> { ++ return CraftWorld.this.isChunkGenerated(x, z); ++ }, world.getChunkSource().mainThreadProcessor).join(); ++ } ++ ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); ++ if (chunk == null) { ++ chunk = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); ++ } ++ if (chunk != null) { ++ return chunk instanceof ImposterProtoChunk || chunk instanceof net.minecraft.world.level.chunk.LevelChunk; ++ } + try { +- return world.getChunkSource().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkSource().chunkMap.read(new ChunkPos(x, z)) != null; // Paper (TODO check if the first part can be removed) ++ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL; ++ // Paper end + } catch (IOException ex) { + throw new RuntimeException(ex); + } +@@ -513,20 +528,48 @@ public class CraftWorld implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- ChunkAccess chunk = world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper ++ // Paper start - Optimize this method ++ ChunkPos chunkPos = new ChunkPos(x, z); + +- // If generate = false, but the chunk already exists, we will get this back. +- if (chunk instanceof ImposterProtoChunk) { +- // We then cycle through again to get the full chunk immediately, rather than after the ticket addition +- chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); +- } ++ if (!generate) { ++ ChunkAccess immediate = world.getChunkSource().getChunkAtImmediately(x, z); ++ if (immediate == null) { ++ immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); ++ } ++ if (immediate != null) { ++ if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { ++ return false; // not full status ++ } ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunk(x, z); // make sure we're at ticket level 32 or lower ++ return true; ++ } + +- if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { +- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); +- return true; ++ net.minecraft.world.level.chunk.storage.RegionFile file; ++ try { ++ file = world.getChunkSource().chunkMap.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ ChunkStatus status = file.getStatusIfCached(x, z); ++ if (!file.hasChunk(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ return false; ++ } ++ ++ ChunkAccess chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.EMPTY, true); ++ if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof net.minecraft.world.level.chunk.LevelChunk)) { ++ return false; ++ } ++ ++ // fall through to load ++ // we do this so we do not re-read the chunk data on disk + } + +- return false; ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); ++ return true; ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0359-Show-blockstate-location-if-we-failed-to-read-it.patch b/Remapped-Spigot-Server-Patches/0359-Show-blockstate-location-if-we-failed-to-read-it.patch new file mode 100644 index 000000000..873d34715 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0359-Show-blockstate-location-if-we-failed-to-read-it.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 15 Jun 2019 10:28:25 -0700 +Subject: [PATCH] Show blockstate location if we failed to read it + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 730fda7f0bf02400d349959e9cc2aafaed000b21..66aee7635cd9260d97ae9dd2e9a2a0590fe3c433 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -19,6 +19,8 @@ public class CraftBlockEntityState extends CraftBlockStat + public CraftBlockEntityState(Block block, Class tileEntityClass) { + super(block); + ++ try {// Paper - show location on failure ++ + this.tileEntityClass = tileEntityClass; + + // get tile entity from block: +@@ -38,6 +40,14 @@ public class CraftBlockEntityState extends CraftBlockStat + this.load(this.snapshot); + } + // Paper end ++ // Paper start - show location on failure ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ throw new RuntimeException("Failed to read BlockState at: world: " + block.getWorld().getName() + " location: (" + block.getX() + ", " + block.getY() + ", " + block.getZ() + ")", thr); ++ } ++ // Paper end + } + + public final boolean snapshotDisabled; // Paper diff --git a/Remapped-Spigot-Server-Patches/0360-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch b/Remapped-Spigot-Server-Patches/0360-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch new file mode 100644 index 000000000..9769725fb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0360-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize DataPaletteBlock instead of ReentrantLock + +Mojang has flaws in their logic about chunks being concurrently +wrote to. So we constantly see crashes around multiple threads writing. + +Additionally, java has optimized synchronization so well that its +in many times faster than trying to manage read wrote locks for low +contention situations. + +And this is extremely a low contention situation. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 6d3dcd19ce1abc9d502903b8008949b5174a13c3..917b0a64083ebbe24321089b784b91f3af4918b9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -8,7 +8,6 @@ import java.util.function.Function; + import java.util.function.Predicate; + import java.util.stream.Collectors; + import net.minecraft.CrashReport; +-import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; + import net.minecraft.core.IdMapper; + import net.minecraft.nbt.CompoundTag; +@@ -32,23 +31,23 @@ public class PalettedContainer implements PaletteResize { + private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER + private final ReentrantLock lock = new ReentrantLock(); + +- public void acquire() { +- if (this.lock.isLocked() && !this.lock.isHeldByCurrentThread()) { ++ public void acquire() { /* // Paper start - disable this - use proper synchronization ++ if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) { + String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> { + return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat ")); + }).collect(Collectors.joining("\n")); + CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException()); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Thread dumps"); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps"); + +- crashreportsystemdetails.setDetail("Thread dumps", (Object) s); ++ crashreportsystemdetails.a("Thread dumps", (Object) s); + throw new ReportedException(crashreport); + } else { +- this.lock.lock(); +- } ++ this.j.lock(); ++ } */ // Paper end + } + + public void release() { +- this.lock.unlock(); ++ //this.j.unlock(); // Paper - disable this + } + + public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { +@@ -84,7 +83,7 @@ public class PalettedContainer implements PaletteResize { + } + + @Override +- public int onResize(int newSize, T objectAdded) { ++ public synchronized int onResize(int newSize, T objectAdded) { // Paper - synchronize + this.acquire(); + BitStorage databits = this.storage; + Palette datapalette = this.palette; +@@ -107,18 +106,18 @@ public class PalettedContainer implements PaletteResize { + } + + public T getAndSet(int x, int y, int z, T value) { +- this.acquire(); +- T t1 = this.getAndSet(getIndex(x, y, z), value); ++ //this.a(); // Paper - remove to reduce ops - synchronize handled below ++ return this.getAndSet(getIndex(x, y, z), value); // Paper + +- this.release(); +- return t1; ++ //this.b(); // Paper ++ //return t1; // PAper + } + + public T getAndSetUnchecked(int x, int y, int z, T value) { + return this.getAndSet(getIndex(x, y, z), value); + } + +- protected T getAndSet(int index, T value) { ++ protected synchronized T getAndSet(int index, T value) { // Paper - synchronize - writes + int j = this.palette.idFor(value); + int k = this.storage.getAndSet(index, j); + T t1 = this.palette.valueFor(k); +@@ -143,7 +142,7 @@ public class PalettedContainer implements PaletteResize { + } + + public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER +- public void write(FriendlyByteBuf buf) { ++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + buf.writeByte(this.bits); + this.palette.write(buf); +@@ -151,7 +150,7 @@ public class PalettedContainer implements PaletteResize { + this.release(); + } + +- public void read(ListTag paletteTag, long[] data) { ++ public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize + this.acquire(); + int i = Math.max(4, Mth.ceillog2(paletteTag.size())); + +@@ -184,7 +183,7 @@ public class PalettedContainer implements PaletteResize { + this.release(); + } + +- public void write(CompoundTag nbttagcompound, String s, String s1) { ++ public synchronized void write(CompoundTag nbttagcompound, String s, String s1) { // Paper - synchronize + this.acquire(); + HashMapPalette datapalettehash = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer); + T t0 = this.defaultValue; diff --git a/Remapped-Spigot-Server-Patches/0361-incremental-chunk-saving.patch b/Remapped-Spigot-Server-Patches/0361-incremental-chunk-saving.patch new file mode 100644 index 000000000..1600ad950 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0361-incremental-chunk-saving.patch @@ -0,0 +1,317 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 9 Jun 2019 03:53:22 +0100 +Subject: [PATCH] incremental chunk saving + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index ffe9b1a63d78925e1d77b9e730aef42fed6d58fa..1278d09f70c1e97607ef20d87a178dc252c7f723 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -446,4 +446,19 @@ public class PaperWorldConfig { + keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16); + log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16)); + } ++ ++ public int autoSavePeriod = -1; ++ private void autoSavePeriod() { ++ autoSavePeriod = getInt("auto-save-interval", -1); ++ if (autoSavePeriod > 0) { ++ log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)"); ++ } else if (autoSavePeriod < 0) { ++ autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod; ++ } ++ } ++ ++ public int maxAutoSaveChunksPerTick = 24; ++ private void maxAutoSaveChunksPerTick() { ++ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0efe7024493f96bb54e7d8c1ea7b233a1b481a04..aab1a055c065d1f1a92461e4442ec2cdd8e0b347 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -261,6 +261,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; ++ public boolean serverAutoSave = false; // Paper + public Commands vanillaCommandDispatcher; + private boolean forceTicks; + // CraftBukkit end +@@ -1256,14 +1257,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.tickCount % autosavePeriod == 0) { // CraftBukkit +- MinecraftServer.LOGGER.debug("Autosave started"); ++ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down ++ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper ++ serverAutoSave = (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0); // Paper + this.profiler.push("save"); ++ if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // Paper + this.playerList.saveAll(); +- this.saveAllChunks(true, false, false); ++ }// Paper ++ // Paper start ++ for (ServerLevel world : getAllLevels()) { ++ if (world.paperConfig.autoSavePeriod > 0) { ++ world.saveIncrementally(serverAutoSave); ++ } ++ } ++ // Paper end ++ + this.profiler.pop(); +- MinecraftServer.LOGGER.debug("Autosave finished"); +- } ++ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper ++ //} // Paper + + this.profiler.push("snooper"); + if (((DedicatedServer) this).getProperties().snooperEnabled && !this.snooper.isStarted() && this.tickCount > 100) { // Spigot +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 7010e0a970462d2b2e1b5696a1a49dba9ea60935..491a9e78fdcec8c211499e8f48cceb829f1e5c8b 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -66,6 +66,9 @@ public class ChunkHolder { + + private final ChunkMap chunkMap; // Paper + ++ long lastAutoSaveTime; // Paper - incremental autosave ++ long inactiveTimeStart; // Paper - incremental autosave ++ + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +@@ -421,7 +424,19 @@ public class ChunkHolder { + boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); + boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); + ++ boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper + this.wasAccessibleSinceLastSave |= flag3; ++ // Paper start - incremental autosave ++ if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod; ++ } ++ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } ++ // Paper end + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +@@ -541,8 +556,32 @@ public class ChunkHolder { + } + + public void refreshAccessibility() { ++ boolean prev = this.wasAccessibleSinceLastSave; // Paper ++ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); ++ // Paper start - incremental autosave ++ if (prev != this.wasAccessibleSinceLastSave) { ++ if (this.wasAccessibleSinceLastSave) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod; ++ } ++ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } else { ++ this.inactiveTimeStart = this.chunkMap.level.getGameTime(); ++ this.chunkMap.autoSaveQueue.remove(this); ++ } ++ } ++ // Paper end ++ } ++ ++ // Paper start - incremental autosave ++ public boolean setHasBeenLoaded() { + this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); ++ return this.wasAccessibleSinceLastSave; + } ++ // Paper end + + public void replaceProtoChunk(ImposterProtoChunk protochunkextension) { + for (int i = 0; i < this.futures.length(); ++i) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0aac29de933c84c34cb24e204e8fcc7010060d8f..cfec04e12dfaeb8852dc129a6a7e68c61dac54b6 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -91,6 +91,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; + import net.minecraft.world.phys.Vec3; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -378,6 +379,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + ++ // Paper start - incremental autosave ++ final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> { ++ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime); ++ if (timeCompare != 0) { ++ return timeCompare; ++ } ++ ++ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.pos), MCUtil.getCoordinateKey(playerchunk2.pos)); ++ }); ++ ++ protected void saveIncrementally() { ++ int savedThisTick = 0; ++ // optimized since we search far less chunks to hit ones that need to be saved ++ List reschedule = new java.util.ArrayList<>(this.level.paperConfig.maxAutoSaveChunksPerTick); ++ long currentTick = this.level.getGameTime(); ++ long maxSaveTime = currentTick - this.level.paperConfig.autoSavePeriod; ++ ++ for (Iterator iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) { ++ ChunkHolder playerchunk = iterator.next(); ++ if (playerchunk.lastAutoSaveTime > maxSaveTime) { ++ break; ++ } ++ ++ iterator.remove(); ++ ++ ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null); ++ if (ichunkaccess instanceof LevelChunk) { ++ boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime; ++ ++ if (shouldSave && this.save(ichunkaccess)) { ++ ++savedThisTick; ++ ++ if (!playerchunk.setHasBeenLoaded()) { ++ // do not fall through to reschedule logic ++ playerchunk.inactiveTimeStart = currentTick; ++ if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ continue; ++ } ++ } ++ } ++ ++ reschedule.add(playerchunk); ++ ++ if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ } ++ ++ for (int i = 0, len = reschedule.size(); i < len; ++i) { ++ ChunkHolder playerchunk = reschedule.get(i); ++ playerchunk.lastAutoSaveTime = this.level.getGameTime(); ++ this.autoSaveQueue.add(playerchunk); ++ } ++ } ++ // Paper end ++ + protected void saveAllChunks(boolean flush) { + if (flush) { + List list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); +@@ -488,6 +547,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.level.unload(chunk); + } ++ this.autoSaveQueue.remove(playerchunk); // Paper + + this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); + this.lightEngine.tryScheduleUpdate(); +@@ -680,6 +740,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + playerchunk.replaceProtoChunk(new ImposterProtoChunk(chunk)); + } + ++ chunk.setLastSaveTime(this.level.getGameTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks ++ + chunk.setFullStatus(() -> { + return ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel()); + }); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 1e8ac0110badbf2d1c2336168c3e11991667c782..c1aa40c01a80a8870478193b8cd7354b0d71045c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -558,6 +558,15 @@ public class ServerChunkCache extends ChunkSource { + } // Paper - Timings + } + ++ // Paper start - duplicate save, but call incremental ++ public void saveIncrementally() { ++ this.runDistanceManagerUpdates(); ++ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings ++ this.chunkMap.saveIncrementally(); ++ } // Paper - Timings ++ } ++ // Paper end ++ + @Override + public void close() throws IOException { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index b2ddf145ae9f581ec6820deb9cb6a98be87658d7..fd7ee4badb383ffb4347d62c00ea2dfa3d76fd12 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -882,6 +882,38 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); + } + ++ // Paper start - derived from below ++ public void saveIncrementally(boolean doFull) { ++ ServerChunkCache chunkproviderserver = this.getChunkSource(); ++ ++ if (doFull) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); ++ } ++ ++ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { ++ if (doFull) { ++ this.saveData(); ++ } ++ ++ timings.worldSaveChunks.startTiming(); // Paper ++ if (!this.noSave()) chunkproviderserver.saveIncrementally(); ++ timings.worldSaveChunks.stopTiming(); // Paper ++ ++ ++ // Copied from save() ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ if (doFull) { // Paper ++ ServerLevel worldserver1 = this; ++ ++ worldDataServer.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ worldDataServer.setCustomBossEvents(this.server.getCustomBossEvents().save()); ++ convertable.saveDataTag(this.server.registryHolder, this.worldDataServer, this.server.getPlayerList().getSingleplayerData()); ++ } ++ // CraftBukkit end ++ } ++ } ++ // Paper end ++ + public void save(@Nullable ProgressListener progressListener, boolean flush, boolean flag1) { + ServerChunkCache chunkproviderserver = this.getChunkSource(); + +@@ -912,6 +944,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit end + } + ++ private void saveData() { this.saveLevelData(); } // Paper - OBFHELPER + private void saveLevelData() { + if (this.dragonFight != null) { + this.worldDataServer.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 55872a17060a35b727a597bc414fecec3ada3515..419b4bf0549d798d52d73fbbd9de59313fc05eb1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -80,7 +80,7 @@ public class LevelChunk implements ChunkAccess { + private TickList blockTicks; + private TickList liquidTicks; + private boolean lastSaveHadEntities; +- private long lastSaveTime; ++ public long lastSaveTime; // Paper + private volatile boolean unsaved; + private long inhabitedTime; + @Nullable diff --git a/Remapped-Spigot-Server-Patches/0362-Anti-Xray.patch b/Remapped-Spigot-Server-Patches/0362-Anti-Xray.patch new file mode 100644 index 000000000..95ed9fdc2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0362-Anti-Xray.patch @@ -0,0 +1,1624 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Mon, 20 Aug 2018 03:03:58 +0200 +Subject: [PATCH] Anti-Xray + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1278d09f70c1e97607ef20d87a178dc252c7f723..c45493e88bf7e8811be2759ff9ac19e3fe9d938a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -1,7 +1,9 @@ + package com.destroystokyo.paper; + ++import java.util.Arrays; + import java.util.List; + ++import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; +@@ -461,4 +463,38 @@ public class PaperWorldConfig { + private void maxAutoSaveChunksPerTick() { + maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); + } ++ ++ public boolean antiXray; ++ public EngineMode engineMode; ++ public int maxChunkSectionIndex; ++ public int updateRadius; ++ public boolean lavaObscures; ++ public boolean usePermission; ++ public List hiddenBlocks; ++ public List replacementBlocks; ++ private void antiXray() { ++ antiXray = getBoolean("anti-xray.enabled", false); ++ engineMode = EngineMode.getById(getInt("anti-xray.engine-mode", EngineMode.HIDE.getId())); ++ engineMode = engineMode == null ? EngineMode.HIDE : engineMode; ++ maxChunkSectionIndex = getInt("anti-xray.max-chunk-section-index", 3); ++ maxChunkSectionIndex = maxChunkSectionIndex > 15 ? 15 : maxChunkSectionIndex; ++ updateRadius = getInt("anti-xray.update-radius", 2); ++ lavaObscures = getBoolean("anti-xray.lava-obscures", false); ++ usePermission = getBoolean("anti-xray.use-permission", false); ++ hiddenBlocks = getList("anti-xray.hidden-blocks", Arrays.asList("gold_ore", "iron_ore", "coal_ore", "lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "redstone_ore", "clay", "emerald_ore", "ender_chest")); ++ replacementBlocks = getList("anti-xray.replacement-blocks", Arrays.asList("stone", "oak_planks")); ++ if (PaperConfig.version < 19) { ++ hiddenBlocks.remove("lit_redstone_ore"); ++ int index = replacementBlocks.indexOf("planks"); ++ if (index != -1) { ++ replacementBlocks.set(index, "oak_planks"); ++ } ++ set("anti-xray.hidden-blocks", hiddenBlocks); ++ set("anti-xray.replacement-blocks", replacementBlocks); ++ } ++ log("Anti-Xray: " + (antiXray ? "enabled" : "disabled") + " / Engine Mode: " + engineMode.getDescription() + " / Up to " + ((maxChunkSectionIndex + 1) * 16) + " blocks / Update Radius: " + updateRadius); ++ if (antiXray && usePermission) { ++ Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues"); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8fb63441fbf9afb6f11e1185a9f29528e1950546 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +@@ -0,0 +1,45 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++public class ChunkPacketBlockController { ++ ++ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); ++ ++ protected ChunkPacketBlockController() { ++ ++ } ++ ++ public BlockState[] getPredefinedBlockData(Level world, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) { ++ return null; ++ } ++ ++ public boolean shouldModify(ServerPlayer entityPlayer, LevelChunk chunk, int chunkSectionSelector) { ++ return false; ++ } ++ ++ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) { ++ return null; ++ } ++ ++ public void modifyBlocks(ClientboundLevelChunkPacket packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ packetPlayOutMapChunk.setReady(true); ++ } ++ ++ public void onBlockChange(Level world, BlockPos blockPosition, BlockState newBlockData, BlockState oldBlockData, int flag) { ++ ++ } ++ ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode playerInteractManager, BlockPos blockPosition, Direction enumDirection) { ++ ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6d41628444e880dea5c96ad5caf557f4c56dea46 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +1,649 @@ ++package com.destroystokyo.paper.antixray; ++ ++import java.util.ArrayList; ++import java.util.LinkedHashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ThreadLocalRandom; ++import java.util.function.IntSupplier; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.Registry; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.EmptyLevelChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.Palette; ++import org.bukkit.Bukkit; ++import org.bukkit.World.Environment; ++ ++import com.destroystokyo.paper.PaperWorldConfig; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private final Executor executor; ++ private final EngineMode engineMode; ++ private final int maxChunkSectionIndex; ++ private final int updateRadius; ++ private final boolean usePermission; ++ private final BlockState[] predefinedBlockData; ++ private final BlockState[] predefinedBlockDataFull; ++ private final BlockState[] predefinedBlockDataStone; ++ private final BlockState[] predefinedBlockDataNetherrack; ++ private final BlockState[] predefinedBlockDataEndStone; ++ private final int[] predefinedBlockDataBitsGlobal; ++ private final int[] predefinedBlockDataBitsStoneGlobal; ++ private final int[] predefinedBlockDataBitsNetherrackGlobal; ++ private final int[] predefinedBlockDataBitsEndStoneGlobal; ++ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final LevelChunkSection[] emptyNearbyChunkSections = {LevelChunk.EMPTY_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION}; ++ private final int maxBlockYUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(Level world, Executor executor) { ++ PaperWorldConfig paperWorldConfig = world.paperConfig; ++ engineMode = paperWorldConfig.engineMode; ++ maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ ++ this.executor = executor; ++ ++ List toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ predefinedBlockData = null; ++ predefinedBlockDataFull = null; ++ predefinedBlockDataStone = new BlockState[] {Blocks.STONE.defaultBlockState()}; ++ predefinedBlockDataNetherrack = new BlockState[] {Blocks.NETHERRACK.defaultBlockState()}; ++ predefinedBlockDataEndStone = new BlockState[] {Blocks.END_STONE.defaultBlockState()}; ++ predefinedBlockDataBitsGlobal = null; ++ predefinedBlockDataBitsStoneGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.STONE.defaultBlockState())}; ++ predefinedBlockDataBitsNetherrackGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.NETHERRACK.defaultBlockState())}; ++ predefinedBlockDataBitsEndStoneGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.END_STONE.defaultBlockState())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List predefinedBlockDataList = new LinkedList(); ++ ++ for (String id : paperWorldConfig.hiddenBlocks) { ++ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); ++ ++ if (block != null && !block.isEntityBlock()) { ++ toObfuscate.add(id); ++ predefinedBlockDataList.add(block.defaultBlockState()); ++ } ++ } ++ ++ // The doc of the LinkedHashSet(Collection c) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation ++ Set predefinedBlockDataSet = new LinkedHashSet(); ++ // Therefore addAll(Collection c) is used, which guarantees this order in the doc ++ predefinedBlockDataSet.addAll(predefinedBlockDataList); ++ predefinedBlockData = predefinedBlockDataSet.size() == 0 ? new BlockState[] {Blocks.DIAMOND_ORE.defaultBlockState()} : predefinedBlockDataSet.toArray(new BlockState[0]); ++ predefinedBlockDataFull = predefinedBlockDataSet.size() == 0 ? new BlockState[] {Blocks.DIAMOND_ORE.defaultBlockState()} : predefinedBlockDataList.toArray(new BlockState[0]); ++ predefinedBlockDataStone = null; ++ predefinedBlockDataNetherrack = null; ++ predefinedBlockDataEndStone = null; ++ predefinedBlockDataBitsGlobal = new int[predefinedBlockDataFull.length]; ++ ++ for (int i = 0; i < predefinedBlockDataFull.length; i++) { ++ predefinedBlockDataBitsGlobal[i] = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ ++ predefinedBlockDataBitsStoneGlobal = null; ++ predefinedBlockDataBitsNetherrackGlobal = null; ++ predefinedBlockDataBitsEndStoneGlobal = null; ++ } ++ ++ for (String id : toObfuscate) { ++ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); ++ ++ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void ++ if (block != null && !block.defaultBlockState().isAir()) { ++ // Replace all block states of a specified block ++ // No OBFHELPER for nms.BlockStateList#a() due to too many decompile errors ++ // The OBFHELPER should be getBlockDataList() ++ for (BlockState blockData : block.getStateDefinition().getPossibleStates()) { ++ obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)] = true; ++ } ++ } ++ } ++ ++ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(world, new ChunkPos(0, 0)); ++ BlockPos zeroPos = new BlockPos(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ BlockState blockData = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getObject(i); ++ ++ if (blockData != null) { ++ solidGlobal[i] = blockData.isRedstoneConductor(emptyChunk, zeroPos) ++ && blockData.getBlock() != Blocks.SPAWNER && blockData.getBlock() != Blocks.BARRIER && blockData.getBlock() != Blocks.SHULKER_BOX && blockData.getBlock() != Blocks.SLIME_BLOCK || paperWorldConfig.lavaObscures && blockData == Blocks.LAVA.defaultBlockState(); ++ // Comparing blockData == Blocks.LAVA.getBlockData() instead of blockData.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used ++ // shulker box checks TE. ++ } ++ } ++ ++ this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1; ++ } ++ ++ private int getPredefinedBlockDataFullLength() { ++ return engineMode == EngineMode.HIDE ? 1 : predefinedBlockDataFull.length; ++ } ++ ++ @Override ++ public BlockState[] getPredefinedBlockData(Level world, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) { ++ // Return the block data which should be added to the data palettes so that they can be used for the obfuscation ++ if (chunkSection.bottomBlockY() >> 4 <= maxChunkSectionIndex) { ++ switch (engineMode) { ++ case HIDE: ++ switch (world.getWorld().getEnvironment()) { ++ case NETHER: ++ return predefinedBlockDataNetherrack; ++ case THE_END: ++ return predefinedBlockDataEndStone; ++ default: ++ return predefinedBlockDataStone; ++ } ++ default: ++ return predefinedBlockData; ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public boolean shouldModify(ServerPlayer entityPlayer, LevelChunk chunk, int chunkSectionSelector) { ++ return !usePermission || !entityPlayer.getBukkitEntity().hasPermission("paper.antixray.bypass"); ++ } ++ ++ @Override ++ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) { ++ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later ++ // Note: As of 1.14 this has to be moved later due to the chunk system. ++ ChunkPacketInfoAntiXray chunkPacketInfoAntiXray = new ChunkPacketInfoAntiXray(packetPlayOutMapChunk, chunk, chunkSectionSelector, this); ++ return chunkPacketInfoAntiXray; ++ } ++ ++ @Override ++ public void modifyBlocks(ClientboundLevelChunkPacket packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ if (chunkPacketInfo == null) { ++ packetPlayOutMapChunk.setReady(true); ++ return; ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ // plugins? ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ this.modifyBlocks(packetPlayOutMapChunk, chunkPacketInfo); ++ }); ++ return; ++ } ++ ++ LevelChunk chunk = chunkPacketInfo.getChunk(); ++ int x = chunk.getPos().x; ++ int z = chunk.getPos().z; ++ ServerLevel world = (ServerLevel)chunk.world; ++ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks( ++ (LevelChunk) world.getChunkIfLoadedImmediately(x - 1, z), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x + 1, z), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x, z - 1), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x, z + 1)); ++ ++ executor.execute((ChunkPacketInfoAntiXray) chunkPacketInfo); ++ } ++ ++ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal) ++ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here ++ private final ThreadLocal predefinedBlockDataBits = ThreadLocal.withInitial(() -> new int[getPredefinedBlockDataFullLength()]); ++ private static final ThreadLocal solid = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ private static final ThreadLocal obfuscate = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate ++ private static final ThreadLocal current = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal next = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal nextNext = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ ++ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { ++ int[] predefinedBlockDataBits = this.predefinedBlockDataBits.get(); ++ boolean[] solid = this.solid.get(); ++ boolean[] obfuscate = this.obfuscate.get(); ++ boolean[][] current = this.current.get(); ++ boolean[][] next = this.next.get(); ++ boolean[][] nextNext = this.nextNext.get(); ++ // dataBitsReader, dataBitsWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it ++ DataBitsReader dataBitsReader = new DataBitsReader(); ++ DataBitsWriter dataBitsWriter = new DataBitsWriter(); ++ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; ++ boolean[] solidTemp = null; ++ boolean[] obfuscateTemp = null; ++ dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData()); ++ dataBitsWriter.setDataBits(chunkPacketInfoAntiXray.getData()); ++ int numberOfBlocks = predefinedBlockDataBits.length; ++ // Keep the lambda expressions as simple as possible. They are used very frequently. ++ IntSupplier random = numberOfBlocks == 1 ? (() -> 0) : new IntSupplier() { ++ private int state; ++ ++ { ++ while ((state = ThreadLocalRandom.current().nextInt()) == 0); ++ } ++ ++ @Override ++ public int getAsInt() { ++ // https://en.wikipedia.org/wiki/Xorshift ++ state ^= state << 13; ++ state ^= state >>> 17; ++ state ^= state << 5; ++ // https://www.pcg-random.org/posts/bounded-rands.html ++ return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); ++ } ++ }; ++ ++ for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) { ++ if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex) != null) { ++ int[] predefinedBlockDataBitsTemp; ++ ++ if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) { ++ predefinedBlockDataBitsTemp = engineMode == EngineMode.HIDE ? chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.THE_END ? predefinedBlockDataBitsEndStoneGlobal : predefinedBlockDataBitsStoneGlobal : predefinedBlockDataBitsGlobal; ++ } else { ++ // If it's this.predefinedBlockData, use this.predefinedBlockDataFull instead ++ BlockState[] predefinedBlockDataFull = chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex) == predefinedBlockData ? this.predefinedBlockDataFull : chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex); ++ predefinedBlockDataBitsTemp = predefinedBlockDataBits; ++ ++ for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) { ++ predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ } ++ ++ dataBitsWriter.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ ++ // Check if the chunk section below was not obfuscated ++ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex - 1) == null) { ++ // If so, initialize some stuff ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); ++ // Read the blocks of the upper layer of the chunk section below if it exists ++ LevelChunkSection belowChunkSection = null; ++ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex - 1]) == LevelChunk.EMPTY_CHUNK_SECTION; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ current[z][x] = true; ++ next[z][x] = skipFirstLayer || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(belowChunkSection.getBlockState(x, 15, z))]; ++ } ++ } ++ ++ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section ++ dataBitsWriter.setBitsPerObject(0); ++ obfuscateLayer(-1, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex]; ++ ++ // Obfuscate all layers of the current chunk section except the upper one ++ for (int y = 0; y < 15; y++) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(y, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ // Check if the chunk section above doesn't need obfuscation ++ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex + 1) == null) { ++ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists ++ LevelChunkSection aboveChunkSection; ++ ++ if (chunkSectionIndex != 15 && (aboveChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex + 1]) != LevelChunk.EMPTY_CHUNK_SECTION) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ if (!solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(aboveChunkSection.getBlockState(x, 0, z))]) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ dataBitsReader.setBitsPerObject(0); ++ solid[0] = true; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solid, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ } else { ++ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex + 1)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex + 1)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.finish(); ++ } ++ } ++ ++ chunkPacketInfoAntiXray.getPacketPlayOutMapChunk().setReady(true); ++ } ++ ++ private void obfuscateLayer(int y, DataBitsReader dataBitsReader, DataBitsWriter dataBitsWriter, boolean[] solid, boolean[] obfuscate, int[] predefinedBlockDataBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { ++ // First block of first line ++ int dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][1] = true; ++ next[1][0] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(0, y, 15))] || nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, 0))] || current[0][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][0] = true; ++ } ++ ++ // First line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][x - 1] = true; ++ next[0][x + 1] = true; ++ next[1][x] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(x, y, 15))] || current[0][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][x] = true; ++ } ++ } ++ ++ // Last block of first line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][14] = true; ++ next[1][15] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(15, y, 15))] || nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, 0))] || current[0][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][15] = true; ++ } ++ ++ // All inner lines ++ for (int z = 1; z < 15; z++) { ++ // First block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][1] = true; ++ next[z - 1][0] = true; ++ next[z + 1][0] = true; ++ } else { ++ if (nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, z))] || current[z][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][0] = true; ++ } ++ ++ // All inner blocks ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][x - 1] = true; ++ next[z][x + 1] = true; ++ next[z - 1][x] = true; ++ next[z + 1][x] = true; ++ } else { ++ if (current[z][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][x] = true; ++ } ++ } ++ ++ // Last block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][14] = true; ++ next[z - 1][15] = true; ++ next[z + 1][15] = true; ++ } else { ++ if (nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, z))] || current[z][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][15] = true; ++ } ++ } ++ ++ // First block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][1] = true; ++ next[14][0] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(0, y, 0))] || nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, 15))] || current[15][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][0] = true; ++ } ++ ++ // Last line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][x - 1] = true; ++ next[15][x + 1] = true; ++ next[14][x] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(x, y, 0))] || current[15][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][x] = true; ++ } ++ } ++ ++ // Last block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][14] = true; ++ next[14][15] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(15, y, 0))] || nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, 15))] || current[15][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean[] readDataPalette(Palette dataPalette, boolean[] temp, boolean[] global) { ++ if (dataPalette == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) { ++ return global; ++ } ++ ++ BlockState blockData; ++ ++ for (int i = 0; (blockData = dataPalette.getObject(i)) != null; i++) { ++ temp[i] = global[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)]; ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(Level world, BlockPos blockPosition, BlockState newBlockData, BlockState oldBlockData, int flag) { ++ if (oldBlockData != null && solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(oldBlockData)] && !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(newBlockData)] && blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(world, blockPosition); ++ } ++ } ++ ++ @Override ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode playerInteractManager, BlockPos blockPosition, Direction enumDirection) { ++ if (blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(playerInteractManager.level, blockPosition); ++ } ++ } ++ ++ private void updateNearbyBlocks(Level world, BlockPos blockPosition) { ++ if (updateRadius >= 2) { ++ BlockPos temp = blockPosition.west(); ++ updateBlock(world, temp); ++ updateBlock(world, temp.west()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.east()); ++ updateBlock(world, temp.east()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.below()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.above()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.north()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp = blockPosition.south()); ++ updateBlock(world, temp.south()); ++ } else if (updateRadius == 1) { ++ updateBlock(world, blockPosition.west()); ++ updateBlock(world, blockPosition.east()); ++ updateBlock(world, blockPosition.below()); ++ updateBlock(world, blockPosition.above()); ++ updateBlock(world, blockPosition.north()); ++ updateBlock(world, blockPosition.south()); ++ } else { ++ // Do nothing if updateRadius <= 0 (test mode) ++ } ++ } ++ ++ private void updateBlock(Level world, BlockPos blockPosition) { ++ BlockState blockData = world.getTypeIfLoaded(blockPosition); ++ ++ if (blockData != null && obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)]) { ++ // world.notify(blockPosition, blockData, blockData, 3); ++ ((ServerLevel)world).getChunkSource().blockChanged(blockPosition); // We only need to re-send to client ++ } ++ } ++ ++ public enum EngineMode { ++ ++ HIDE(1, "hide ores"), ++ OBFUSCATE(2, "obfuscate"); ++ ++ private final int id; ++ private final String description; ++ ++ EngineMode(int id, String description) { ++ this.id = id; ++ this.description = description; ++ } ++ ++ public static EngineMode getById(int id) { ++ for (EngineMode engineMode : values()) { ++ if (engineMode.id == id) { ++ return engineMode; ++ } ++ } ++ ++ return null; ++ } ++ ++ public int getId() { ++ return id; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dc04ffc76e11ab63cd98a84cf95c58dc5cd1efdb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +@@ -0,0 +1,81 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.Palette; ++ ++public class ChunkPacketInfo { ++ ++ private final ClientboundLevelChunkPacket packetPlayOutMapChunk; ++ private final LevelChunk chunk; ++ private final int chunkSectionSelector; ++ private byte[] data; ++ private final int[] bitsPerObject = new int[16]; ++ private final Object[] dataPalettes = new Object[16]; ++ private final int[] dataBitsIndexes = new int[16]; ++ private final Object[][] predefinedObjects = new Object[16][]; ++ ++ public ChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) { ++ this.packetPlayOutMapChunk = packetPlayOutMapChunk; ++ this.chunk = chunk; ++ this.chunkSectionSelector = chunkSectionSelector; ++ } ++ ++ public ClientboundLevelChunkPacket getPacketPlayOutMapChunk() { ++ return packetPlayOutMapChunk; ++ } ++ ++ public LevelChunk getChunk() { ++ return chunk; ++ } ++ ++ public int getChunkSectionSelector() { ++ return chunkSectionSelector; ++ } ++ ++ public byte[] getData() { ++ return data; ++ } ++ ++ public void setData(byte[] data) { ++ this.data = data; ++ } ++ ++ public int getBitsPerObject(int chunkSectionIndex) { ++ return bitsPerObject[chunkSectionIndex]; ++ } ++ ++ public void setBitsPerObject(int chunkSectionIndex, int bitsPerObject) { ++ this.bitsPerObject[chunkSectionIndex] = bitsPerObject; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public Palette getDataPalette(int chunkSectionIndex) { ++ return (Palette) dataPalettes[chunkSectionIndex]; ++ } ++ ++ public void setDataPalette(int chunkSectionIndex, Palette dataPalette) { ++ dataPalettes[chunkSectionIndex] = dataPalette; ++ } ++ ++ public int getDataBitsIndex(int chunkSectionIndex) { ++ return dataBitsIndexes[chunkSectionIndex]; ++ } ++ ++ public void setDataBitsIndex(int chunkSectionIndex, int dataBitsIndex) { ++ dataBitsIndexes[chunkSectionIndex] = dataBitsIndex; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T[] getPredefinedObjects(int chunkSectionIndex) { ++ return (T[]) predefinedObjects[chunkSectionIndex]; ++ } ++ ++ public void setPredefinedObjects(int chunkSectionIndex, T[] predefinedObjects) { ++ this.predefinedObjects[chunkSectionIndex] = predefinedObjects; ++ } ++ ++ public boolean isWritten(int chunkSectionIndex) { ++ return bitsPerObject[chunkSectionIndex] != 0; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7345f1dc7c5c05f2e1ee09b94f4ebf56dd59bc55 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { ++ ++ private LevelChunk[] nearbyChunks; ++ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; ++ ++ public ChunkPacketInfoAntiXray(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector, ++ ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { ++ super(packetPlayOutMapChunk, chunk, chunkSectionSelector); ++ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; ++ } ++ ++ public LevelChunk[] getNearbyChunks() { ++ return nearbyChunks; ++ } ++ ++ public void setNearbyChunks(LevelChunk... nearbyChunks) { ++ this.nearbyChunks = nearbyChunks; ++ } ++ ++ @Override ++ public void run() { ++ chunkPacketBlockControllerAntiXray.obfuscate(this); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..298ea423084dbcc1b61f991bcd82b8ae51bf0977 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +@@ -0,0 +1,51 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsReader { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private int mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ } ++ ++ public int read() { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ int value = (int) (current >>> bitInLongIndex) & mask; ++ bitInLongIndex += bitsPerObject; ++ return value; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..333763936897befda5bb6c077944d2667f922799 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +@@ -0,0 +1,79 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsWriter { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private long mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ private boolean dirty; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ ++ dirty = false; ++ } ++ ++ public void finish() { ++ if (dirty && dataBits.length > longInDataBitsIndex + 7) { ++ dataBits[longInDataBitsIndex] = (byte) (current >> 56 & 0xff); ++ dataBits[longInDataBitsIndex + 1] = (byte) (current >> 48 & 0xff); ++ dataBits[longInDataBitsIndex + 2] = (byte) (current >> 40 & 0xff); ++ dataBits[longInDataBitsIndex + 3] = (byte) (current >> 32 & 0xff); ++ dataBits[longInDataBitsIndex + 4] = (byte) (current >> 24 & 0xff); ++ dataBits[longInDataBitsIndex + 5] = (byte) (current >> 16 & 0xff); ++ dataBits[longInDataBitsIndex + 6] = (byte) (current >> 8 & 0xff); ++ dataBits[longInDataBitsIndex + 7] = (byte) (current & 0xff); ++ } ++ } ++ ++ public void write(int value) { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ finish(); ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; ++ dirty = true; ++ bitInLongIndex += bitsPerObject; ++ } ++ ++ public void skip() { ++ bitInLongIndex += bitsPerObject; ++ ++ if (bitInLongIndex > 64) { ++ finish(); ++ bitInLongIndex = bitsPerObject; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +index b587f774c8f88f2a1c3ea489f7e4fe0bbdeb5a41..10dd582b0fff4df27f1113e41c8ee3e274c6fb65 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.protocol.game; + ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import com.google.common.collect.Lists; + import io.netty.buffer.ByteBuf; + import io.netty.buffer.Unpooled; +@@ -16,6 +17,7 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.SkullBlockEntity; ++import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkBiomeContainer; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.level.chunk.LevelChunkSection; +@@ -33,7 +35,13 @@ public class ClientboundLevelChunkPacket implements Packet blockEntitiesTags; + private boolean fullChunk; + +- public ClientboundLevelChunkPacket() {} ++ // Paper start - Async-Anti-Xray - Set the ready flag to true ++ private volatile boolean ready; // Paper - Async-Anti-Xray - Ready flag for the network manager ++ public ClientboundLevelChunkPacket() { ++ this.ready = true; ++ } ++ // Paper end ++ + // Paper start + private final java.util.List extraPackets = new java.util.ArrayList<>(); + private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); +@@ -43,12 +51,16 @@ public class ClientboundLevelChunkPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i) : null; ++ // Paper end + ChunkPos chunkcoordintpair = chunk.getPos(); + + this.x = chunkcoordintpair.x; + this.z = chunkcoordintpair.z; +- this.fullChunk = includedSectionsMask == 65535; ++ this.fullChunk = i == 65535; + this.heightmaps = new CompoundTag(); + Iterator iterator = chunk.getHeightmaps().iterator(); + +@@ -65,8 +77,13 @@ public class ClientboundLevelChunkPacket implements Packet> 4; + +- if (this.isFullChunk() || (includedSectionsMask & 1 << j) != 0) { ++ if (this.isFullChunk() || (i & 1 << j) != 0) { + // Paper start - improve oversized chunk data packet handling + if (++totalTileEntities > TE_LIMIT) { + ClientboundBlockEntityDataPacket updatePacket = tileentity.getUpdatePacket(); +@@ -93,8 +110,19 @@ public class ClientboundLevelChunkPacket implements Packet chunkPacketInfo) { return this.a(packetDataSerializer, chunk, chunkSectionSelector, chunkPacketInfo); } // OBFHELPER ++ public int a(FriendlyByteBuf packetdataserializer, LevelChunk chunk, int i, ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + int j = 0; + LevelChunkSection[] achunksection = chunk.getSections(); + int k = 0; +@@ -169,9 +201,9 @@ public class ClientboundLevelChunkPacket implements Packet[] apacket, LevelChunk chunk) { this.playerLoadedChunk(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void playerLoadedChunk(ServerPlayer player, Packet[] packets, LevelChunk chunk) { + if (packets[0] == null) { +- packets[0] = new ClientboundLevelChunkPacket(chunk, 65535); ++ packets[0] = new ClientboundLevelChunkPacket(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(player, chunk, 65535)); // Paper - Anti-Xray - Bypass + packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, true); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index fd7ee4badb383ffb4347d62c00ea2dfa3d76fd12..7a09bc921827958f58290bd3d6f19984bb34a8f6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -204,7 +204,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + // Add env and gen to constructor, WorldData -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { +- super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env); ++ super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env, executor); // Paper pass executor + this.pvpMode = minecraftserver.isPvpAllowed(); + convertable = convertable_conversionsession; + uuid = WorldUUID.getUUID(convertable_conversionsession.levelPath.toFile()); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index d97607f2ded4977b253d3afa3bafcbe6d7f98837..af048ab682612233c01f7087d7b8afbf7e58945b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -308,6 +308,8 @@ public class ServerPlayerGameMode { + } + + } ++ ++ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, direction); // Paper - Anti-Xray + } + + public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index eb88d830fb45a6b8c990e8bdc1943d80f63c8b93..1377465e3dc062f34be25cac10aa018776fb22e7 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -2,6 +2,8 @@ package net.minecraft.world.level; + + import co.aikar.timings.Timing; + import co.aikar.timings.Timings; ++import com.destroystokyo.paper.antixray.ChunkPacketBlockController; // Paper - Anti-Xray ++import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; // Paper - Anti-Xray + import com.destroystokyo.paper.event.server.ServerExceptionEvent; + import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.base.MoreObjects; +@@ -144,6 +146,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + + public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper ++ public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot +@@ -165,9 +168,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return typeKey; + } + +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper ++ this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, env); + this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit +@@ -433,6 +437,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + + BlockState iblockdata1 = chunk.setType(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag ++ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags); // Paper - Anti-Xray + + if (iblockdata1 == null) { + // CraftBukkit start - remove blockstate if failed (or the same) +diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +index e369730ac6909ff5343468bd685c9ea2b6b3cfed..2c19d147710a3bbe2e980114161f1cdf81760947 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -8,6 +8,7 @@ import net.minecraft.Util; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Registry; + import net.minecraft.data.worldgen.biome.Biomes; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; +@@ -28,7 +29,7 @@ public class EmptyLevelChunk extends LevelChunk { + }); + + public EmptyLevelChunk(Level world, ChunkPos pos) { +- super(world, pos, new ChunkBiomeContainer(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), EmptyLevelChunk.BIOMES)); ++ super(world, pos, new ChunkBiomeContainer(MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), EmptyLevelChunk.BIOMES)); // Paper - world isnt ready yet for anti xray use here, use server singleton for registry + } + + // Paper start +diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +index 17fa8b23d1000ae53f2b4f1a6e8817c1005c1c81..56ab660e29a0dc7d22eeaa41cc8f50e8a96717ef 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +@@ -27,7 +27,7 @@ public class ImposterProtoChunk extends ProtoChunk { + private final LevelChunk wrapped; + + public ImposterProtoChunk(LevelChunk wrapped) { +- super(wrapped.getPos(), UpgradeData.EMPTY); ++ super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.world); // Paper - Anti-Xray - Add parameter + this.wrapped = wrapped; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 419b4bf0549d798d52d73fbbd9de59313fc05eb1..85861545ec4620a6cfd06876dad091637bd29b0b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -464,7 +464,7 @@ public class LevelChunk implements ChunkAccess { + return null; + } + +- chunksection = new LevelChunkSection(j >> 4 << 4); ++ chunksection = new LevelChunkSection(j >> 4 << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters + this.sections[j >> 4] = chunksection; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index f5db97fb0dac78e1d9aa68d0417aa13f39914f52..38c7c5f18fc84d4a1de2da1ddc6d3ac37c25f341 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -1,9 +1,11 @@ + package net.minecraft.world.level.chunk; + + import java.util.function.Predicate; ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import javax.annotation.Nullable; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; +@@ -18,16 +20,22 @@ public class LevelChunkSection { + private short tickingFluidCount; + final PalettedContainer states; // Paper - package-private + +- public LevelChunkSection(int yOffset) { +- this(yOffset, (short) 0, (short) 0, (short) 0); ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public LevelChunkSection(int i, ChunkAccess chunk, Level world, boolean initializeBlocks) { ++ this(i, (short) 0, (short) 0, (short) 0, chunk, world, initializeBlocks); ++ // Paper end + } + +- public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) { +- this.bottomBlockY = yOffset; +- this.nonEmptyBlockCount = nonEmptyBlockCount; +- this.tickingBlockCount = randomTickableBlockCount; +- this.tickingFluidCount = nonEmptyFluidCount; +- this.states = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState()); ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) { this(yOffset, nonEmptyBlockCount, randomTickableBlockCount, nonEmptyFluidCount, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public LevelChunkSection(int i, short short0, short short1, short short2, ChunkAccess chunk, Level world, boolean initializeBlocks) { ++ // Paper end ++ this.bottomBlockY = i; ++ this.nonEmptyBlockCount = short0; ++ this.tickingBlockCount = short1; ++ this.tickingFluidCount = short2; ++ this.states = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState(), world == null ? null : world.chunkPacketBlockController.getPredefinedBlockData(world, chunk, this, initializeBlocks), initializeBlocks); // Paper - Anti-Xray - Add predefined block data + } + + public final BlockState getBlockState(int x, int y, int z) { // Paper +@@ -139,10 +147,14 @@ public class LevelChunkSection { + return this.states; + } + +- public void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER +- public void write(FriendlyByteBuf packetdataserializer) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public final void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere ++ @Deprecated public final void write(FriendlyByteBuf packetdataserializer) { this.writeChunkSection(packetdataserializer, null); } // Notice for updates: Please make sure this method isn't used anywhere ++ public final void writeChunkSection(FriendlyByteBuf packetDataSerializer, ChunkPacketInfo chunkPacketInfo) { this.b(packetDataSerializer, chunkPacketInfo); } // OBFHELPER ++ public void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + packetdataserializer.writeShort(this.nonEmptyBlockCount); +- this.states.write(packetdataserializer); ++ this.states.writeDataPaletteBlock(packetdataserializer, chunkPacketInfo, this.bottomBlockY >> 4); // Paper - Anti-Xray - Add chunk packet info + } + + public int getSerializedSize() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 917b0a64083ebbe24321089b784b91f3af4918b9..dd252372e1e380674b1191e9ea265cbb10de437b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.chunk; + + import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import java.util.Arrays; + import java.util.Objects; + import java.util.concurrent.locks.ReentrantLock; +@@ -26,6 +27,7 @@ public class PalettedContainer implements PaletteResize { + private final Function reader; + private final Function writer; + private final T defaultValue; ++ private final T[] predefinedObjects; // Paper - Anti-Xray - Add predefined objects + protected BitStorage storage; public final BitStorage getDataBits() { return this.storage; } // Paper - OBFHELPER + private Palette palette; private Palette getDataPalette() { return this.palette; } // Paper - OBFHELPER + private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER +@@ -50,14 +52,47 @@ public class PalettedContainer implements PaletteResize { + //this.j.unlock(); // Paper - disable this + } + +- public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { +- this.globalPalette = fallbackPalette; +- this.registry = idList; +- this.reader = elementDeserializer; +- this.writer = elementSerializer; +- this.defaultValue = defaultElement; +- this.setBits(4); ++ // Paper start - Anti-Xray - Add predefined objects ++ @Deprecated public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { this(fallbackPalette, idList, elementDeserializer, elementSerializer, defaultElement, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public PalettedContainer(Palette datapalette, IdMapper registryblockid, Function function, Function function1, T t0, T[] predefinedObjects, boolean initialize) { ++ // Paper end ++ this.globalPalette = datapalette; ++ this.registry = registryblockid; ++ this.reader = function; ++ this.writer = function1; ++ this.defaultValue = t0; ++ // Paper start - Anti-Xray - Add predefined objects ++ this.predefinedObjects = predefinedObjects; ++ ++ if (initialize) { ++ if (predefinedObjects == null) { ++ // Default ++ this.initialize(4); ++ } else { ++ // MathHelper.d() is trailingBits(roundCeilPow2(n)), alternatively; (int)ceil(log2(n)); however it's trash, use numberOfLeadingZeros instead ++ // Count the bits of the maximum array index to initialize a data palette with enough space from the beginning ++ // The length of the array is used because air is also added to the data palette from the beginning ++ // Start with at least 4 ++ int maxIndex = predefinedObjects.length >> 4; ++ int bitCount = (32 - Integer.numberOfLeadingZeros(Math.max(16, maxIndex) - 1)); ++ ++ // Initialize with at least 15 free indixes ++ this.initialize((1 << bitCount) - predefinedObjects.length < 16 ? bitCount + 1 : bitCount); ++ this.addPredefinedObjects(); ++ } ++ } ++ // Paper end ++ } ++ ++ // Paper start - Anti-Xray - Add predefined objects ++ private void addPredefinedObjects() { ++ if (this.predefinedObjects != null && this.getDataPalette() != this.getDataPaletteGlobal()) { ++ for (int i = 0; i < this.predefinedObjects.length; i++) { ++ this.getDataPalette().getOrCreateIdFor(this.predefinedObjects[i]); ++ } ++ } + } ++ // Paper end + + private static int getIndex(int x, int y, int z) { + return y << 8 | z << 4 | x; +@@ -92,6 +127,7 @@ public class PalettedContainer implements PaletteResize { + + int j; + ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + for (j = 0; j < databits.getSize(); ++j) { + T t1 = datapalette.valueFor(databits.get(j)); + +@@ -141,24 +177,38 @@ public class PalettedContainer implements PaletteResize { + return t0 == null ? this.defaultValue : t0; + } + +- public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER +- public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere ++ @Deprecated public void write(FriendlyByteBuf buf) { this.writeDataPaletteBlock(buf, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere ++ public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { this.b(packetDataSerializer, chunkPacketInfo, chunkSectionIndex); } // OBFHELPER ++ public synchronized void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - synchronize ++ // Paper end + this.acquire(); +- buf.writeByte(this.bits); +- this.palette.write(buf); +- buf.writeLongArray(this.storage.getRaw()); ++ packetdataserializer.writeByte(this.bits); ++ this.palette.write(packetdataserializer); ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBitsPerObject(chunkSectionIndex, this.getBitsPerObject()); ++ chunkPacketInfo.setDataPalette(chunkSectionIndex, this.getDataPalette()); ++ chunkPacketInfo.setDataBitsIndex(chunkSectionIndex, packetdataserializer.writerIndex() + FriendlyByteBuf.countBytes(this.getDataBits().getDataBits().length)); ++ chunkPacketInfo.setPredefinedObjects(chunkSectionIndex, this.predefinedObjects); ++ } ++ // Paper end ++ packetdataserializer.writeLongArray(this.storage.getRaw()); + this.release(); + } + + public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize + this.acquire(); +- int i = Math.max(4, Mth.ceillog2(paletteTag.size())); ++ // Paper - Anti-Xray - TODO: Should this.predefinedObjects.length just be added here (faster) or should the contents be compared to calculate the size (less RAM)? ++ int i = Math.max(4, Mth.ceillog2(paletteTag.size() + (this.predefinedObjects == null ? 0 : this.predefinedObjects.length))); // Paper - Anti-Xray - Calculate the size with predefined objects + +- if (i != this.bits) { ++ if (true || i != this.bits) { // Paper - Anti-Xray - Not initialized yet + this.setBits(i); + } + + this.palette.read(paletteTag); ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + int j = data.length * 64 / 4096; + + if (this.palette == this.globalPalette) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index d8b7b210484079c9ca2c34831c84102cba6692f5..87fd585141ad9818fca0b697cb4c87248fe7ce11 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -64,16 +64,24 @@ public class ProtoChunk implements ChunkAccess { + private long inhabitedTime; + private final Map carvingMasks; + private volatile boolean isLightCorrect; ++ private final Level world; // Paper - Anti-Xray - Add world + +- public ProtoChunk(ChunkPos pos, UpgradeData upgradeData) { +- this(pos, upgradeData, (LevelChunkSection[]) null, new ProtoTickList<>((block) -> { ++ // Paper start - Anti-Xray - Add world ++ @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData) { this(pos, upgradeData, null); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ProtoChunk(ChunkPos chunkcoordintpair, UpgradeData chunkconverter, Level world) { ++ // Paper end ++ this(chunkcoordintpair, chunkconverter, (LevelChunkSection[]) null, new ProtoTickList<>((block) -> { + return block == null || block.defaultBlockState().isAir(); +- }, pos), new ProtoTickList<>((fluidtype) -> { ++ }, chunkcoordintpair), new ProtoTickList<>((fluidtype) -> { + return fluidtype == null || fluidtype == Fluids.EMPTY; +- }, pos)); ++ }, chunkcoordintpair), world); // Paper - Anti-Xray - Add world + } + +- public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler) { ++ // Paper start - Anti-Xray - Add world ++ @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler) { this(pos, upgradeData, sections, blockTickScheduler, fluidTickScheduler, null); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ProtoChunk(ChunkPos chunkcoordintpair, UpgradeData chunkconverter, @Nullable LevelChunkSection[] achunksection, ProtoTickList protochunkticklist, ProtoTickList protochunkticklist1, Level world) { ++ this.world = world; ++ // Paper end + this.heightmaps = Maps.newEnumMap(Heightmap.Types.class); + this.status = ChunkStatus.EMPTY; + this.blockEntities = Maps.newHashMap(); +@@ -85,15 +93,15 @@ public class ProtoChunk implements ChunkAccess { + this.structureStarts = Maps.newHashMap(); + this.structuresRefences = Maps.newHashMap(); + this.carvingMasks = new Object2ObjectArrayMap(); +- this.chunkPos = pos; +- this.upgradeData = upgradeData; +- this.blockTicks = blockTickScheduler; +- this.liquidTicks = fluidTickScheduler; +- if (sections != null) { +- if (this.sections.length == sections.length) { +- System.arraycopy(sections, 0, this.sections, 0, this.sections.length); ++ this.chunkPos = chunkcoordintpair; ++ this.upgradeData = chunkconverter; ++ this.blockTicks = protochunkticklist; ++ this.liquidTicks = protochunkticklist1; ++ if (achunksection != null) { ++ if (this.sections.length == achunksection.length) { ++ System.arraycopy(achunksection, 0, this.sections, 0, this.sections.length); + } else { +- ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", sections.length, this.sections.length); ++ ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", achunksection.length, this.sections.length); + } + } + +@@ -228,7 +236,7 @@ public class ProtoChunk implements ChunkAccess { + + public LevelChunkSection getOrCreateSection(int y) { + if (this.sections[y] == LevelChunk.EMPTY_SECTION) { +- this.sections[y] = new LevelChunkSection(y << 4); ++ this.sections[y] = new LevelChunkSection(y << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters + } + + return this.sections[y]; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 969130442b529eaac6f708107ff129f89cc0af90..8dbd1dc2de400ad0c6c2be49ba09dfc03216ffd2 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -101,7 +101,7 @@ public class ChunkSerializer { + byte b0 = nbttagcompound2.getByte("Y"); + + if (nbttagcompound2.contains("Palette", 9) && nbttagcompound2.contains("BlockStates", 12)) { +- LevelChunkSection chunksection = new LevelChunkSection(b0 << 4); ++ LevelChunkSection chunksection = new LevelChunkSection(b0 << 4, null, world, false); // Paper - Anti-Xray - Add parameters + + chunksection.getStates().read(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates")); + chunksection.recalcBlockCounts(); +@@ -165,7 +165,7 @@ public class ChunkSerializer { + // CraftBukkit end + }); + } else { +- ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1); ++ ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world); // Paper - Anti-Xray - Add parameter + + protochunk.setBiomes(biomestorage); + object = protochunk; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 423594177fe78600755d913f169f28dd1bfa2b37..74bad15034d9d55fb70931f38868f812160c6305 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -43,7 +43,7 @@ public class CraftChunk implements Chunk { + private final ServerLevel worldServer; + private final int x; + private final int z; +- private static final PalettedContainer emptyBlockIDs = new LevelChunkSection(0).getStates(); ++ private static final PalettedContainer emptyBlockIDs = new LevelChunkSection(0, null, null, true).getStates(); // Paper - Anti-Xray - Add parameters + private static final byte[] emptyLight = new byte[2048]; + + public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) { +@@ -287,7 +287,7 @@ public class CraftChunk implements Chunk { + CompoundTag data = new CompoundTag(); + cs[i].getStates().write(data, "Palette", "BlockStates"); + +- PalettedContainer blockids = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState()); // TODO: snapshot whole ChunkSection ++ PalettedContainer blockids = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState(), null, false); // TODO: snapshot whole ChunkSection // Paper - Anti-Xray - Add no predefined block data and don't initialize because it's done in the line below internally + blockids.read(data.getList("Palette", CraftMagicNumbers.NBT.TAG_COMPOUND), data.getLongArray("BlockStates")); + + sectionBlockIDs[i] = blockids; +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index a94c65f4d63a06be099fd67b0b7756c5b45b84a0..8d72cd6a44cf462cfe3adac9bf99a16883a587df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -21,9 +21,11 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; + private final LevelChunkSection[] sections; + private Set tiles; ++ private World world; // Paper - Anti-Xray - Add world + + public CraftChunkData(World world) { + this(world.getMaxHeight()); ++ this.world = world; // Paper - Anti-Xray - Add world + } + + /* pp for tests */ CraftChunkData(int maxHeight) { +@@ -157,7 +159,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + private LevelChunkSection getChunkSection(int y, boolean create) { + LevelChunkSection section = sections[y >> 4]; + if (create && section == null) { +- sections[y >> 4] = section = new LevelChunkSection(y >> 4 << 4); ++ sections[y >> 4] = section = new LevelChunkSection(y >> 4 << 4, null, world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) world).getHandle() : null, true); // Paper - Anti-Xray - Add parameters + } + return section; + } diff --git a/Remapped-Spigot-Server-Patches/0363-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/Remapped-Spigot-Server-Patches/0363-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch new file mode 100644 index 000000000..0d019759f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0363-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 01:01:32 -0400 +Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob + limit + +This resolves the super common complaint about mobs not spawning. + +This was ultimately a flaw in the vanilla count algorithim that allows +spawners and other misc mobs to count against the mob limit, which are +not bounded, and can prevent the entire world from spawning new. + +I believe Bukkits changes around persistence may of actually made it +worse than vanilla. + +This should fully solve all of the issues around it so that only natural +influences natural spawns. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c45493e88bf7e8811be2759ff9ac19e3fe9d938a..384cb363eed794551bee6b0ec11ba1be92a3d7ac 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -464,6 +464,16 @@ public class PaperWorldConfig { + maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); + } + ++ public boolean countAllMobsForSpawning = false; ++ private void countAllMobsForSpawning() { ++ countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false); ++ if (countAllMobsForSpawning) { ++ log("Counting all mobs for spawning. Mob farms may reduce natural spawns elsewhere in world."); ++ } else { ++ log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)"); ++ } ++ } ++ + public boolean antiXray; + public EngineMode engineMode; + public int maxChunkSectionIndex; +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index e23875ae07c23fed1161ea070e63bbc3a30168a0..0fb69f9194078e5e05e36ed909eb48424b6465b4 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -79,6 +79,13 @@ public final class NaturalSpawner { + MobCategory enumcreaturetype = entity.getType().getCategory(); + + if (enumcreaturetype != MobCategory.MISC) { ++ // Paper start - Only count natural spawns ++ if (!entity.level.paperConfig.countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end + BlockPos blockposition = entity.blockPosition(); + long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4); + diff --git a/Remapped-Spigot-Server-Patches/0364-Configurable-projectile-relative-velocity.patch b/Remapped-Spigot-Server-Patches/0364-Configurable-projectile-relative-velocity.patch new file mode 100644 index 000000000..111827bdd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0364-Configurable-projectile-relative-velocity.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lucavon +Date: Tue, 23 Jul 2019 20:29:20 -0500 +Subject: [PATCH] Configurable projectile relative velocity + +This patch adds an option "disable relative projectile velocity", which, when +nabled, will cause projectiles to ignore the shooter's current velocity, +like they did in Minecraft 1.8 and prior. +If a player is falling, for example, their shooting range will be drastically +reduced, as a downwards velocity is applied to the projectile. This prevents +players from saving themselves from falling off floating islands, for example, +as a thrown ender pearl will not make it back to the island, while it would +have in 1.8. + +While this could easily be done with plugins, too, there are multiple problems: +P1) If multiple plugins cancel the velocity by subtracting the shooter's velocity +from the projectile's velocity, the projectile's velocity would be different. +As there's no way to detect whether the projectile's velocity has already been +adjusted to ignore the player's velocity, plugins can't not do it if it's not +necessary. +P2) I've noticed some inconsistencies, e.g. weird velocity when shooting while +using an elytra. Checking for those inconsistencies is possible, but not as +efficient as just not applying the velocity in the first place. +P3) Solutions for 1) and especially 2) might not be future-proof, while this +server-internal fix makes this change future-proof. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 384cb363eed794551bee6b0ec11ba1be92a3d7ac..1ee2cced100626e48eb36ee14f84b9257c79a2f8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -507,4 +507,9 @@ public class PaperWorldConfig { + Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues"); + } + } ++ ++ public boolean disableRelativeProjectileVelocity; ++ private void disableRelativeProjectileVelocity() { ++ disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index d385fb6eee5000951c350b6ced5669dc3dcce725..ca3d936433cd47caa4e0335e41246b1c4ce0eb99 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -129,7 +129,7 @@ public abstract class Projectile extends Entity { + this.shoot((double) f5, (double) f6, (double) f7, modifierZ, modifierXYZ); + Vec3 vec3d = user.getDeltaMovement(); + +- this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, user.isOnGround() ? 0.0D : vec3d.y, vec3d.z)); ++ if (!user.level.paperConfig.disableRelativeProjectileVelocity) this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, user.isOnGround() ? 0.0D : vec3d.y, vec3d.z)); // Paper - allow disabling relative velocity + } + + // CraftBukkit start - call projectile hit event diff --git a/Remapped-Spigot-Server-Patches/0365-Mark-entities-as-being-ticked-when-notifying-navigat.patch b/Remapped-Spigot-Server-Patches/0365-Mark-entities-as-being-ticked-when-notifying-navigat.patch new file mode 100644 index 000000000..17630cf48 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0365-Mark-entities-as-being-ticked-when-notifying-navigat.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 28 Jul 2019 00:51:11 +0100 +Subject: [PATCH] Mark entities as being ticked when notifying navigation + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 7a09bc921827958f58290bd3d6f19984bb34a8f6..a811ced17721b70bb51837f47e466c2261db2466 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1469,6 +1469,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + + if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { ++ boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper + Iterator iterator = this.navigations.iterator(); + + while (iterator.hasNext()) { +@@ -1490,6 +1491,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + ++ this.tickingEntities = wasTicking; // Paper + } + } + diff --git a/Remapped-Spigot-Server-Patches/0366-offset-item-frame-ticking.patch b/Remapped-Spigot-Server-Patches/0366-offset-item-frame-ticking.patch new file mode 100644 index 000000000..32966f397 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0366-offset-item-frame-ticking.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Tue, 30 Jul 2019 03:17:16 +0500 +Subject: [PATCH] offset item frame ticking + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java b/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java +index e0bf8c6c838b18a0c55b6f3317033e892b631f5c..3277a56bcf3831f8d3c9fa9168c608b369eed7e4 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java +@@ -35,7 +35,7 @@ public abstract class HangingEntity extends Entity { + protected static final Predicate HANGING_ENTITY = (entity) -> { + return entity instanceof HangingEntity; + }; +- private int checkInterval; ++ private int checkInterval; { this.checkInterval = this.getId() % this.level.spigotConfig.hangingTickFrequency; } // Paper + public BlockPos pos; + protected Direction direction; + diff --git a/Remapped-Spigot-Server-Patches/0367-Avoid-hopper-searches-if-there-are-no-items.patch b/Remapped-Spigot-Server-Patches/0367-Avoid-hopper-searches-if-there-are-no-items.patch new file mode 100644 index 000000000..ee5090404 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0367-Avoid-hopper-searches-if-there-are-no-items.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CullanP +Date: Thu, 3 Mar 2016 02:13:38 -0600 +Subject: [PATCH] Avoid hopper searches if there are no items + +Hoppers searching for items and minecarts is the most expensive part of hopper ticking. +We keep track of the number of minecarts and items in a chunk. +If there are no items in the chunk, we skip searching for items. +If there are no minecarts in the chunk, we skip searching for them. + +Usually hoppers aren't near items, so we can skip most item searches. +And since minecart hoppers are used _very_ rarely near we can avoid alot of searching there. + +Combined, this adds up a lot. + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index d3640975c5a33b4911428760691215905b987385..e7facd849e3511c64b4ae44b34382f4a4985f2a4 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -16,6 +16,7 @@ public final class EntitySelector { + public static final Predicate ENTITY_NOT_BEING_RIDDEN = (entity) -> { + return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger(); + }; ++ public static final Predicate isInventory() { return CONTAINER_ENTITY_SELECTOR; } // Paper - OBFHELPER + public static final Predicate CONTAINER_ENTITY_SELECTOR = (entity) -> { + return entity instanceof Container && entity.isAlive(); + }; +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 85861545ec4620a6cfd06876dad091637bd29b0b..4fef3abe4b416cbebe1b456468b5c3e162de18f1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -31,10 +31,13 @@ import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.Mth; ++import net.minecraft.world.Container; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntitySelector; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ChunkTickList; + import net.minecraft.world.level.EmptyTickList; +@@ -122,6 +125,10 @@ public class LevelChunk implements ChunkAccess { + return removed; + } + } ++ // Track the number of minecarts and items ++ // Keep this synced with entitySlices.add() and entitySlices.remove() ++ private final int[] itemCounts = new int[16]; ++ private final int[] inventoryEntityCounts = new int[16]; + // Paper end + + public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList blockTickScheduler, TickList fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer loadToWorldConsumer) { +@@ -581,6 +588,13 @@ public class LevelChunk implements ChunkAccess { + entity.zChunk = this.chunkPos.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ // Paper start ++ if (entity instanceof ItemEntity) { ++ itemCounts[k]++; ++ } else if (entity instanceof Container) { ++ inventoryEntityCounts[k]++; ++ } ++ // Paper end + entity.entitySlice = this.entitySlices[k]; // Paper + this.markUnsaved(); // Paper + } +@@ -614,6 +628,11 @@ public class LevelChunk implements ChunkAccess { + if (!this.entitySlices[section].remove(entity)) { + return; + } ++ if (entity instanceof ItemEntity) { ++ itemCounts[section]--; ++ } else if (entity instanceof Container) { ++ inventoryEntityCounts[section]--; ++ } + entityCounts.decrement(entity.getMinecraftKeyString()); + this.markUnsaved(); // Paper + // Paper end +@@ -899,6 +918,14 @@ public class LevelChunk implements ChunkAccess { + for (int k = i; k <= j; ++k) { + Iterator iterator = this.entitySlices[k].iterator(); // Spigot + ++ // Paper start - Don't search for inventories if we have none, and that is all we want ++ /* ++ * We check if they want inventories by seeing if it is the static `IEntitySelector.d` ++ * ++ * Make sure the inventory selector stays in sync. ++ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()` ++ */ ++ if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue; + while (iterator.hasNext()) { + T entity = (T) iterator.next(); // CraftBukkit - decompile error + if (entity.shouldBeRemoved) continue; // Paper +@@ -919,9 +946,29 @@ public class LevelChunk implements ChunkAccess { + i = Mth.clamp(i, 0, this.entitySlices.length - 1); + j = Mth.clamp(j, 0, this.entitySlices.length - 1); + ++ // Paper start ++ int[] counts; ++ if (ItemEntity.class.isAssignableFrom(entityClass)) { ++ counts = itemCounts; ++ } else if (Container.class.isAssignableFrom(entityClass)) { ++ counts = inventoryEntityCounts; ++ } else { ++ counts = null; ++ } ++ // Paper end + for (int k = i; k <= j; ++k) { ++ if (counts != null && counts[k] <= 0) continue; // Paper - Don't check a chunk if it doesn't have the type we are looking for + Iterator iterator = this.entitySlices[k].iterator(); // Spigot + ++ // Paper start - Don't search for inventories if we have none, and that is all we want ++ /* ++ * We check if they want inventories by seeing if it is the static `IEntitySelector.d` ++ * ++ * Make sure the inventory selector stays in sync. ++ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()` ++ */ ++ if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue; ++ // Paper end + while (iterator.hasNext()) { + T t0 = (T) iterator.next(); // CraftBukkit - decompile error + if (t0.shouldBeRemoved) continue; // Paper diff --git a/Remapped-Spigot-Server-Patches/0368-Asynchronous-chunk-IO-and-loading.patch b/Remapped-Spigot-Server-Patches/0368-Asynchronous-chunk-IO-and-loading.patch new file mode 100644 index 000000000..c4fd70932 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0368-Asynchronous-chunk-IO-and-loading.patch @@ -0,0 +1,4410 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 13 Jul 2019 09:23:10 -0700 +Subject: [PATCH] Asynchronous chunk IO and loading + +This patch re-adds a file IO thread as well as shoving de-serializing +chunk NBT data onto worker threads. This patch also will shove +chunk data serialization onto the same worker threads when the chunk +is unloaded - this cannot be done for regular saves since that's unsafe. + +The file IO Thread + +Unlike 1.13 and below, the file IO thread is prioritized - IO tasks can +be reoredered, however they are "stuck" to a world & coordinate. + +Scheduling IO tasks works as follows, given a world & coordinate - location: + +The IO thread has been designed to ensure that reads and writes appear to +occur synchronously for a given location, however the implementation also +has the unfortunate side-effect of making every write appear as if +they occur without failure. + +The IO thread has also been designed to accomodate Mojang's decision to +store chunk data and POI data separately. It can independently schedule +tasks for each. + +However threads can wait for writes to complete and check if: + - The write was overwriten by another scheduler + - The write failed (however it does not indicate whether it was overwritten by another scheduler) + +Scheduling reads: + + - If a write task is in progress, the task is not scheduled and returns the in-progress write data + This means that readers cannot modify the NBTTagCompound returned and must clone if it they wish to write + - If a write task is not in progress but a read task is in progress, then the read task is simply chained + This means that again, readers cannot modify the NBTTagCompound returned + +Scheduling writes: + + - If a read task is in progress, ignore the read task and schedule the write + We cannot complete the read task since we assume it wants old data - not current + - If a write task is pending, overwrite the write data + The file IO thread does correctly handle cases where the data is overwritten when it + is writing data (before completing a task it will check if the data was overwritten and + will retry). + +When the file IO thread executes a task for a location, the it will +execute the read task first (if it exists), then it will execute the +write task. This ensures that, even when scheduling at different +priorities, that reads/writes for a location act synchronously. + +The downside of the file IO thread is that write failure can only be +indicated to the scheduling thread if: + +- No other thread decides to schedule another write for the location +concurrently +- The scheduling thread blocks on the write to complete (however the +current implementation can be modified to indicate success +asynchronously) + +The file io thread can be modified easily to provide indications +of write failure and write overwriting if needed. + +The upside of the file IO thread is that if a write failures, then +chunk data is not lost until server restart. This leaves more room +for spurious failure. + +Finally, the io thread will indicate to the console when reads +or writes fail - with relevant detail. + +Asynchronous chunk data serialization for unloading chunks + +When chunks unload they make a call to PlayerChunkMap#saveChunk(IChunkAccess). +Even if I make the IO asynchronous for this call, the data serialization +still hits pretty hard. And given that now the chunk system will +aggressively unload chunks more often (queued immediately at +ticket level 45 or higher), unloads occur more often, and +combined with our changes to the unload queue to make it +significantly more aggresive - chunk unloads can hit pretty hard. +Especially players running around with elytras and fireworks. + +For serializing chunk data off main, there are some tasks which cannot be +done asynchronously. Lighting data must be saved beforehand as well as +potentially some tick lists. These are completed before scheduling the +asynchronous save. + +However serializing chunk data off of the main thread is still risky. +Even though this patch schedules the save to occur after ALL references +of the chunk are removed from the world, plugins can still technically +access entities inside the chunks. For this, if the serialization task +fails for any reason, it will be re-scheduled to be serialized on the +main thread - with the hopes that the reason it failed was due to a plugin +and not an error with the save code itself. Like vanilla code - if the +serialization fails, the chunk data is lost. + +Asynchronous chunk io/loading + +Mojang's current implementation for loading chunk data off disk is +to return a CompletableFuture that will be completed by scheduling a +task to be executed on the world's chunk queue (which is only drained +on the main thread). This task will read the IO off disk and it will +apply data conversions & deserialization synchronously. Obviously +all 3 of these operations are expensive however all can be completed +asynchronously instead. + +The solution this patch uses is as follows: + +0. If an asynchronous chunk save is in progress (see above), wait +for that task to complete. It will use the serialized NBTTagCompound +created by the task. If the task fails to complete, then we would continue +with step 1. If it does not, we skip step 1. (Note: We actually load +POI data no matter what in this case). +1. Schedule an IO task to read chunk & poi data off disk. +2. The IO task will schedule a chunk load task. +3. The chunk load task executes on the async chunk loader threads +and will apply datafixers & de-serialize the chunk into a ProtoChunk +or ProtoChunkExtension. +4. The in progress chunk is then passed on to the world's chunk queue +to complete the ComletableFuture and execute any of the synchronous +tasks required to be executed by the chunk load task (i.e lighting +and some poi tasks). + +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index 79ede25e4fe7a648b1d29c49d876482a2158f892..24eac9400fbf971742e89bbf47b0ba52b587c4eb 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -59,6 +59,17 @@ public class WorldTimingsHandler { + + public final Timing miscMobSpawning; + ++ public final Timing poiUnload; ++ public final Timing chunkUnload; ++ public final Timing poiSaveDataSerialization; ++ public final Timing chunkSave; ++ public final Timing chunkSaveOverwriteCheck; ++ public final Timing chunkSaveDataSerialization; ++ public final Timing chunkSaveIOWait; ++ public final Timing chunkUnloadPrepareSave; ++ public final Timing chunkUnloadPOISerialization; ++ public final Timing chunkUnloadDataSave; ++ + public WorldTimingsHandler(Level server) { + String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; + +@@ -112,6 +123,17 @@ public class WorldTimingsHandler { + + + miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ ++ poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); ++ chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); ++ poiSaveDataSerialization = Timings.ofSafe(name + "Chunk save - POI Data serialization"); ++ chunkSave = Timings.ofSafe(name + "Chunk save - Chunk"); ++ chunkSaveOverwriteCheck = Timings.ofSafe(name + "Chunk save - Chunk Overwrite Check"); ++ chunkSaveDataSerialization = Timings.ofSafe(name + "Chunk save - Chunk Data serialization"); ++ chunkSaveIOWait = Timings.ofSafe(name + "Chunk save - Chunk IO Wait"); ++ chunkUnloadPrepareSave = Timings.ofSafe(name + "Chunk unload - Async Save Prepare"); ++ chunkUnloadPOISerialization = Timings.ofSafe(name + "Chunk unload - POI Data Serialization"); ++ chunkUnloadDataSave = Timings.ofSafe(name + "Chunk unload - Data Serialization"); + } + + public static Timing getTickList(ServerLevel worldserver, String timingsType) { +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 53dd6c18de8e80378852bbb141016d9574d42162..62711d95db62221a2e4e6423c518afe13a6c7dbe 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; + import com.google.common.base.Functions; + import com.google.common.base.Joiner; + import com.google.common.collect.ImmutableSet; +@@ -43,7 +44,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting").build(); + + public PaperCommand(String name) { + super(name); +@@ -155,6 +156,9 @@ public class PaperCommand extends Command { + case "debug": + doDebug(sender, args); + break; ++ case "dumpwaiting": ++ ChunkTaskManager.dumpAllChunkLoadInfo(); ++ break; + case "chunkinfo": + doChunkInfo(sender, args); + break; +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 469f78775b03cf363d88e35c69c0dc185c22547c..8bf4d2b8c38c02d6a5b2fea37113689a252f1571 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; + import com.google.common.base.Strings; + import com.google.common.base.Throwables; + +@@ -352,4 +353,54 @@ public class PaperConfig { + maxBookPageSize = getInt("settings.book-size.page-max", maxBookPageSize); + maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); + } ++ ++ public static boolean asyncChunks = false; ++ private static void asyncChunks() { ++ ConfigurationSection section; ++ if (version < 15) { ++ section = config.createSection("settings.async-chunks"); ++ section.set("threads", -1); ++ } else { ++ section = config.getConfigurationSection("settings.async-chunks"); ++ if (section == null) { ++ section = config.createSection("settings.async-chunks"); ++ } ++ } ++ // Clean up old configs ++ if (section.contains("load-threads")) { ++ if (!section.contains("threads")) { ++ section.set("threads", section.get("load-threads")); ++ } ++ section.set("load-threads", null); ++ } ++ section.set("generation", null); ++ section.set("enabled", null); ++ section.set("thread-per-world-generation", null); ++ ++ int threads = getInt("settings.async-chunks.threads", -1); ++ int cpus = Runtime.getRuntime().availableProcessors(); ++ if (threads <= 0) { ++ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, cpus - 1)); ++ } ++ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) { ++ asyncChunks = false; ++ } else { ++ asyncChunks = true; ++ } ++ ++ // Let Shared Host set some limits ++ String sharedHostThreads = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_THREADS"); ++ if (sharedHostThreads != null) { ++ try { ++ threads = Math.max(1, Math.min(threads, Integer.parseInt(sharedHostThreads))); ++ } catch (NumberFormatException ignored) {} ++ } ++ ++ if (!asyncChunks) { ++ log("Async Chunks: Disabled - Chunks will be managed synchronously, and will cause tremendous lag."); ++ } else { ++ ChunkTaskManager.initGlobalLoadThreads(threads); ++ log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag."); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/io/IOUtil.java b/src/main/java/com/destroystokyo/paper/io/IOUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5af0ac3d9e87c06053e65433060f15779c156c2a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/IOUtil.java +@@ -0,0 +1,62 @@ ++package com.destroystokyo.paper.io; ++ ++import org.bukkit.Bukkit; ++ ++public final class IOUtil { ++ ++ /* Copied from concrete or concurrentutil */ ++ ++ public static long getCoordinateKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getCoordinateX(final long key) { ++ return (int)key; ++ } ++ ++ public static int getCoordinateZ(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ public static int getRegionCoordinate(final int chunkCoordinate) { ++ return chunkCoordinate >> 5; ++ } ++ ++ public static int getChunkInRegion(final int chunkCoordinate) { ++ return chunkCoordinate & 31; ++ } ++ ++ public static String genericToString(final Object object) { ++ return object == null ? "null" : object.getClass().getName() + ":" + object.toString(); ++ } ++ ++ public static T notNull(final T obj) { ++ if (obj == null) { ++ throw new NullPointerException(); ++ } ++ return obj; ++ } ++ ++ public static T notNull(final T obj, final String msgIfNull) { ++ if (obj == null) { ++ throw new NullPointerException(msgIfNull); ++ } ++ return obj; ++ } ++ ++ public static void arrayBounds(final int off, final int len, final int arrayLength, final String msgPrefix) { ++ if (off < 0 || len < 0 || (arrayLength - off) < len) { ++ throw new ArrayIndexOutOfBoundsException(msgPrefix + ": off: " + off + ", len: " + len + ", array length: " + arrayLength); ++ } ++ } ++ ++ public static int getPriorityForCurrentThread() { ++ return Bukkit.isPrimaryThread() ? PrioritizedTaskQueue.HIGHEST_PRIORITY : PrioritizedTaskQueue.NORMAL_PRIORITY; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static void rethrow(final Throwable throwable) throws T { ++ throw (T)throwable; ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a630a84b60b4517e3bc330d4983b914bd064efa4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +@@ -0,0 +1,606 @@ ++package com.destroystokyo.paper.io; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import org.apache.logging.log4j.Logger; ++ ++import java.io.IOException; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++/** ++ * Prioritized singleton thread responsible for all chunk IO that occurs in a minecraft server. ++ * ++ *

++ * Singleton access: {@link Holder#INSTANCE} ++ *

++ * ++ *

++ * All functions provided are MT-Safe, however certain ordering constraints are (but not enforced): ++ *

  • ++ * Chunk saves may not occur for unloaded chunks. ++ *
  • ++ *
  • ++ * Tasks must be scheduled on the main thread. ++ *
  • ++ *

    ++ * ++ * @see Holder#INSTANCE ++ * @see #scheduleSave(ServerLevel, int, int, CompoundTag, CompoundTag, int) ++ * @see #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean) ++ */ ++public final class PaperFileIOThread extends QueueExecutorThread { ++ ++ public static final Logger LOGGER = MinecraftServer.LOGGER; ++ public static final CompoundTag FAILURE_VALUE = new CompoundTag(); ++ ++ public static final class Holder { ++ ++ public static final PaperFileIOThread INSTANCE = new PaperFileIOThread(); ++ ++ static { ++ INSTANCE.start(); ++ } ++ } ++ ++ private final AtomicLong writeCounter = new AtomicLong(); ++ ++ private PaperFileIOThread() { ++ super(new PrioritizedTaskQueue<>(), (int)(1.0e6)); // 1.0ms spinwait time ++ this.setName("Paper RegionFile IO Thread"); ++ this.setPriority(Thread.NORM_PRIORITY - 1); // we keep priority close to normal because threads can wait on us ++ this.setUncaughtExceptionHandler((final Thread unused, final Throwable thr) -> { ++ LOGGER.fatal("Uncaught exception thrown from IO thread, report this!", thr); ++ }); ++ } ++ ++ /* run() is implemented by superclass */ ++ ++ /* ++ * ++ * IO thread will perform reads before writes ++ * ++ * How reads/writes are scheduled: ++ * ++ * If read in progress while scheduling write, ignore read and schedule write ++ * If read in progress while scheduling read (no write in progress), chain the read task ++ * ++ * ++ * If write in progress while scheduling read, use the pending write data and ret immediately ++ * If write in progress while scheduling write (ignore read in progress), overwrite the write in progress data ++ * ++ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however ++ * it fails to properly propagate write failures. When writes fail the data is kept so future reads will actually ++ * read the failed write data. This should hopefully act as a way to prevent data loss for spurious fails for writing data. ++ * ++ */ ++ ++ /** ++ * Attempts to bump the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued. ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority level to try to bump to ++ */ ++ public void bumpPriority(final ServerLevel world, final int chunkX, final int chunkZ, final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key); ++ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key); ++ ++ if (poiTask != null) { ++ poiTask.raisePriority(priority); ++ } ++ if (chunkTask != null) { ++ chunkTask.raisePriority(priority); ++ } ++ } ++ ++ public CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final boolean poiData) { ++ final ChunkDataController taskController = poiData ? world.poiDataController : world.chunkDataController; ++ ++ final ChunkDataTask dataTask = taskController.tasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ ++ if (dataTask == null) { ++ return null; ++ } ++ ++ final ChunkDataController.InProgressWrite write = dataTask.inProgressWrite; ++ ++ if (write == null) { ++ return null; ++ } ++ ++ return write.data; ++ } ++ ++ /** ++ * Sets the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued. ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority level to set to ++ */ ++ public void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key); ++ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key); ++ ++ if (poiTask != null) { ++ poiTask.updatePriority(priority); ++ } ++ if (chunkTask != null) { ++ chunkTask.updatePriority(priority); ++ } ++ } ++ ++ /** ++ * Schedules the chunk data to be written asynchronously. ++ *

    ++ * Impl notes: ++ *

    ++ *
  • ++ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means ++ * saves must be scheduled before a chunk is unloaded. ++ *
  • ++ *
  • ++ * Writes may be called concurrently, although only the "later" write will go through. ++ *
  • ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param poiData Chunk point of interest data. If {@code null}, then no poi data is saved. ++ * @param chunkData Chunk data. If {@code null}, then no chunk data is saved. ++ * @param priority Priority level for this task. See {@link PrioritizedTaskQueue} ++ * @throws IllegalArgumentException If both {@code poiData} and {@code chunkData} are {@code null}. ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, ++ final CompoundTag poiData, final CompoundTag chunkData, ++ final int priority) throws IllegalArgumentException { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final long writeCounter = this.writeCounter.getAndIncrement(); ++ ++ if (poiData != null) { ++ this.scheduleWrite(world.poiDataController, world, chunkX, chunkZ, poiData, priority, writeCounter); ++ } ++ if (chunkData != null) { ++ this.scheduleWrite(world.chunkDataController, world, chunkX, chunkZ, chunkData, priority, writeCounter); ++ } ++ } ++ ++ private void scheduleWrite(final ChunkDataController dataController, final ServerLevel world, ++ final int chunkX, final int chunkZ, final CompoundTag data, final int priority, final long writeCounter) { ++ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask taskRunning) -> { ++ if (taskRunning == null) { ++ // no task is scheduled ++ ++ // create task ++ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController); ++ newTask.inProgressWrite = new ChunkDataController.InProgressWrite(); ++ newTask.inProgressWrite.writeCounter = writeCounter; ++ newTask.inProgressWrite.data = data; ++ ++ PaperFileIOThread.this.queueTask(newTask); // schedule ++ return newTask; ++ } ++ ++ taskRunning.raisePriority(priority); ++ ++ if (taskRunning.inProgressWrite == null) { ++ taskRunning.inProgressWrite = new ChunkDataController.InProgressWrite(); ++ } ++ ++ boolean reschedule = taskRunning.inProgressWrite.writeCounter == -1L; ++ ++ // synchronize for readers ++ //noinspection SynchronizationOnLocalVariableOrMethodParameter ++ synchronized (taskRunning) { ++ taskRunning.inProgressWrite.data = data; ++ taskRunning.inProgressWrite.writeCounter = writeCounter; ++ } ++ ++ if (reschedule) { ++ // We need to reschedule this task since the previous one is not currently scheduled since it failed ++ taskRunning.reschedule(priority); ++ } ++ ++ return taskRunning; ++ }); ++ } ++ ++ /** ++ * Same as {@link #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns ++ * a {@link CompletableFuture} which is potentially completed ASYNCHRONOUSLY ON THE FILE IO THREAD when the load task ++ * has completed. ++ *

    ++ * Note that if the chunk fails to load the returned future is completed with {@code null}. ++ *

    ++ */ ++ public CompletableFuture loadChunkDataAsyncFuture(final ServerLevel world, final int chunkX, final int chunkZ, ++ final int priority, final boolean readPoiData, final boolean readChunkData, ++ final boolean intendingToBlock) { ++ final CompletableFuture future = new CompletableFuture<>(); ++ this.loadChunkDataAsync(world, chunkX, chunkZ, priority, future::complete, readPoiData, readChunkData, intendingToBlock); ++ return future; ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. ++ *

    ++ * Impl notes: ++ *

    ++ *
  • ++ * If a chunk fails to load, the {@code onComplete} parameter is completed with {@code null}. ++ *
  • ++ *
  • ++ * It is possible for the {@code onComplete} parameter to be given {@link ChunkData} containing data ++ * this call did not request. ++ *
  • ++ *
  • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
  • ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority level for this task. See {@link PrioritizedTaskQueue} ++ * @param onComplete Consumer to execute once this task has completed ++ * @param readPoiData Whether to read point of interest data. If {@code false}, the {@code NBTTagCompound} will be {@code null}. ++ * @param readChunkData Whether to read chunk data. If {@code false}, the {@code NBTTagCompound} will be {@code null}. ++ * @return The {@link PrioritizedTaskQueue.PrioritizedTask} associated with this task. Note that this task does not support ++ * cancellation. ++ */ ++ public void loadChunkDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, ++ final int priority, final Consumer onComplete, ++ final boolean readPoiData, final boolean readChunkData, ++ final boolean intendingToBlock) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ if (!(readPoiData | readChunkData)) { ++ throw new IllegalArgumentException("Must read chunk data or poi data"); ++ } ++ ++ final ChunkData complete = new ChunkData(); ++ final boolean[] requireCompletion = new boolean[] { readPoiData, readChunkData }; ++ ++ if (readPoiData) { ++ this.scheduleRead(world.poiDataController, world, chunkX, chunkZ, (final CompoundTag poiData) -> { ++ complete.poiData = poiData; ++ ++ final boolean finished; ++ ++ // avoid a race condition where the file io thread completes and we complete synchronously ++ // Note: Synchronization can be elided if both of the accesses are volatile ++ synchronized (requireCompletion) { ++ requireCompletion[0] = false; // 0 -> poi data ++ finished = !requireCompletion[1]; // 1 -> chunk data ++ } ++ ++ if (finished) { ++ onComplete.accept(complete); ++ } ++ }, priority, intendingToBlock); ++ } ++ ++ if (readChunkData) { ++ this.scheduleRead(world.chunkDataController, world, chunkX, chunkZ, (final CompoundTag chunkData) -> { ++ complete.chunkData = chunkData; ++ ++ final boolean finished; ++ ++ // avoid a race condition where the file io thread completes and we complete synchronously ++ // Note: Synchronization can be elided if both of the accesses are volatile ++ synchronized (requireCompletion) { ++ requireCompletion[1] = false; // 1 -> chunk data ++ finished = !requireCompletion[0]; // 0 -> poi data ++ } ++ ++ if (finished) { ++ onComplete.accept(complete); ++ } ++ }, priority, intendingToBlock); ++ } ++ ++ } ++ ++ // Note: the onComplete may be called asynchronously or synchronously here. ++ private void scheduleRead(final ChunkDataController dataController, final ServerLevel world, ++ final int chunkX, final int chunkZ, final Consumer onComplete, final int priority, ++ final boolean intendingToBlock) { ++ ++ Function tryLoadFunction = (final RegionFile file) -> { ++ if (file == null) { ++ return Boolean.TRUE; ++ } ++ return Boolean.valueOf(file.hasChunk(new ChunkPos(chunkX, chunkZ))); ++ }; ++ ++ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask running) -> { ++ if (running == null) { ++ // not scheduled ++ ++ final Boolean shouldSchedule = intendingToBlock ? dataController.computeForRegionFile(chunkX, chunkZ, tryLoadFunction) : ++ dataController.computeForRegionFileIfLoaded(chunkX, chunkZ, tryLoadFunction); ++ ++ if (shouldSchedule == Boolean.FALSE) { ++ // not on disk ++ onComplete.accept(null); ++ return null; ++ } ++ ++ // set up task ++ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController); ++ newTask.inProgressRead = new ChunkDataController.InProgressRead(); ++ newTask.inProgressRead.readFuture.thenAccept(onComplete); ++ ++ PaperFileIOThread.this.queueTask(newTask); // schedule task ++ return newTask; ++ } ++ ++ running.raisePriority(priority); ++ ++ if (running.inProgressWrite == null) { ++ // chain to the read future ++ running.inProgressRead.readFuture.thenAccept(onComplete); ++ return running; ++ } ++ ++ // at this stage we have to use the in progress write's data to avoid an order issue ++ // we don't synchronize since all writes to data occur in the compute() call ++ onComplete.accept(running.inProgressWrite.data); ++ return running; ++ }); ++ } ++ ++ /** ++ * Same as {@link #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns ++ * the {@link ChunkData} associated with the specified chunk when the task is complete. ++ * @return The chunk data, or {@code null} if the chunk failed to load. ++ */ ++ public ChunkData loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, final int priority, ++ final boolean readPoiData, final boolean readChunkData) { ++ return this.loadChunkDataAsyncFuture(world, chunkX, chunkZ, priority, readPoiData, readChunkData, true).join(); ++ } ++ ++ /** ++ * Schedules the given task at the specified priority to be executed on the IO thread. ++ *

    ++ * Internal api. Do not use. ++ *

    ++ */ ++ public void runTask(final int priority, final Runnable runnable) { ++ this.queueTask(new GeneralTask(priority, runnable)); ++ } ++ ++ static final class GeneralTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ private final Runnable run; ++ ++ public GeneralTask(final int priority, final Runnable run) { ++ super(priority); ++ this.run = IOUtil.notNull(run, "Task may not be null"); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ this.run.run(); ++ } catch (final Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ LOGGER.fatal("Failed to execute general task on IO thread " + IOUtil.genericToString(this.run), throwable); ++ } ++ } ++ } ++ ++ public static final class ChunkData { ++ ++ public CompoundTag poiData; ++ public CompoundTag chunkData; ++ ++ public ChunkData() {} ++ ++ public ChunkData(final CompoundTag poiData, final CompoundTag chunkData) { ++ this.poiData = poiData; ++ this.chunkData = chunkData; ++ } ++ } ++ ++ public static abstract class ChunkDataController { ++ ++ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. ++ public final ConcurrentHashMap tasks = new ConcurrentHashMap<>(64, 0.5f); ++ ++ public abstract void writeData(final int x, final int z, final CompoundTag compound) throws IOException; ++ public abstract CompoundTag readData(final int x, final int z) throws IOException; ++ ++ public abstract T computeForRegionFile(final int chunkX, final int chunkZ, final Function function); ++ public abstract T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function); ++ ++ public static final class InProgressWrite { ++ public long writeCounter; ++ public CompoundTag data; ++ } ++ ++ public static final class InProgressRead { ++ public final CompletableFuture readFuture = new CompletableFuture<>(); ++ } ++ } ++ ++ public static final class ChunkDataTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ public ChunkDataController.InProgressWrite inProgressWrite; ++ public ChunkDataController.InProgressRead inProgressRead; ++ ++ private final ServerLevel world; ++ private final int x; ++ private final int z; ++ private final ChunkDataController taskController; ++ ++ public ChunkDataTask(final int priority, final ServerLevel world, final int x, final int z, final ChunkDataController taskController) { ++ super(priority); ++ this.world = world; ++ this.x = x; ++ this.z = z; ++ this.taskController = taskController; ++ } ++ ++ @Override ++ public String toString() { ++ return "Task for world: '" + this.world.getWorld().getName() + "' at " + this.x + "," + this.z + ++ " poi: " + (this.taskController == this.world.poiDataController) + ", hash: " + this.hashCode(); ++ } ++ ++ /* ++ * ++ * IO thread will perform reads before writes ++ * ++ * How reads/writes are scheduled: ++ * ++ * If read in progress while scheduling write, ignore read and schedule write ++ * If read in progress while scheduling read (no write in progress), chain the read task ++ * ++ * ++ * If write in progress while scheduling read, use the pending write data and ret immediately ++ * If write in progress while scheduling write (ignore read in progress), overwrite the write in progress data ++ * ++ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however ++ * it fails to properly propagate write failures ++ * ++ */ ++ ++ void reschedule(final int priority) { ++ // priority is checked before this stage // TODO what ++ this.queue.lazySet(null); ++ this.priority.lazySet(priority); ++ PaperFileIOThread.Holder.INSTANCE.queueTask(this); ++ } ++ ++ @Override ++ public void run() { ++ ChunkDataController.InProgressRead read = this.inProgressRead; ++ if (read != null) { ++ CompoundTag compound = PaperFileIOThread.FAILURE_VALUE; ++ try { ++ compound = this.taskController.readData(this.x, this.z); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ LOGGER.fatal("Failed to read chunk data for task: " + this.toString(), thr); ++ // fall through to complete with null data ++ } ++ read.readFuture.complete(compound); ++ } ++ ++ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(this.x, this.z)); ++ ++ ChunkDataController.InProgressWrite write = this.inProgressWrite; ++ ++ if (write == null) { ++ // IntelliJ warns this is invalid, however it does not consider that writes to the task map & the inProgress field can occur concurrently. ++ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long keyInMap, final ChunkDataTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkDataTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ return valueInMap.inProgressWrite == null ? null : valueInMap; ++ }); ++ ++ if (inMap == null) { ++ return; // set the task value to null, indicating we're done ++ } ++ ++ // not null, which means there was a concurrent write ++ write = this.inProgressWrite; ++ } ++ ++ // check if another process is writing ++ /*try { TODO: Can we restore this? ++ ((WorldServer)this.world).checkSession(); ++ } catch (final Exception ex) { ++ LOGGER.fatal("Couldn't save chunk; already in use by another instance of Minecraft?", ex); ++ // we don't need to set the write counter to -1 as we know at this stage there's no point in re-scheduling ++ // writes since they'll fail anyways. ++ return; ++ } ++*/ ++ for (;;) { ++ final long writeCounter; ++ final CompoundTag data; ++ ++ //noinspection SynchronizationOnLocalVariableOrMethodParameter ++ synchronized (write) { ++ writeCounter = write.writeCounter; ++ data = write.data; ++ } ++ ++ boolean failedWrite = false; ++ ++ try { ++ this.taskController.writeData(this.x, this.z, data); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ LOGGER.fatal("Failed to write chunk data for task: " + this.toString(), thr); ++ failedWrite = true; ++ } ++ ++ boolean finalFailWrite = failedWrite; ++ ++ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long keyInMap, final ChunkDataTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkDataTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ if (valueInMap.inProgressWrite.writeCounter == writeCounter) { ++ if (finalFailWrite) { ++ valueInMap.inProgressWrite.writeCounter = -1L; ++ } ++ ++ return null; ++ } ++ return valueInMap; ++ // Hack end ++ }); ++ ++ if (inMap == null) { ++ // write counter matched, so we wrote the most up-to-date pending data, we're done here ++ // or we failed to write and successfully set the write counter to -1 ++ return; // we're done here ++ } ++ ++ // fetch & write new data ++ continue; ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97f2e433c483f1ebd7500ae142269e144ef5fda4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java +@@ -0,0 +1,277 @@ ++package com.destroystokyo.paper.io; ++ ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicReference; ++ ++public class PrioritizedTaskQueue { ++ ++ // lower numbers are a higher priority (except < 0) ++ // higher priorities are always executed before lower priorities ++ ++ /** ++ * Priority value indicating the task has completed or is being completed. ++ */ ++ public static final int COMPLETING_PRIORITY = -1; ++ ++ /** ++ * Highest priority, should only be used for main thread tasks or tasks that are blocking the main thread. ++ */ ++ public static final int HIGHEST_PRIORITY = 0; ++ ++ /** ++ * Should be only used in an IO task so that chunk loads do not wait on other IO tasks. ++ * This only exists because IO tasks are scheduled before chunk load tasks to decrease IO waiting times. ++ */ ++ public static final int HIGHER_PRIORITY = 1; ++ ++ /** ++ * Should be used for scheduling chunk loads/generation that would increase response times to users. ++ */ ++ public static final int HIGH_PRIORITY = 2; ++ ++ /** ++ * Default priority. ++ */ ++ public static final int NORMAL_PRIORITY = 3; ++ ++ /** ++ * Use for tasks not at all critical and can potentially be delayed. ++ */ ++ public static final int LOW_PRIORITY = 4; ++ ++ /** ++ * Use for tasks that should "eventually" execute. ++ */ ++ public static final int LOWEST_PRIORITY = 5; ++ ++ private static final int TOTAL_PRIORITIES = 6; ++ ++ final ConcurrentLinkedQueue[] queues = (ConcurrentLinkedQueue[])new ConcurrentLinkedQueue[TOTAL_PRIORITIES]; ++ ++ private final AtomicBoolean shutdown = new AtomicBoolean(); ++ ++ { ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ this.queues[i] = new ConcurrentLinkedQueue<>(); ++ } ++ } ++ ++ /** ++ * Returns whether the specified priority is valid ++ */ ++ public static boolean validPriority(final int priority) { ++ return priority >= 0 && priority < TOTAL_PRIORITIES; ++ } ++ ++ /** ++ * Queues a task. ++ * @throws IllegalStateException If the task has already been queued. Use {@link PrioritizedTask#raisePriority(int)} to ++ * raise a task's priority. ++ * This can also be thrown if the queue has shutdown. ++ */ ++ public void add(final T task) throws IllegalStateException { ++ int priority = task.getPriority(); ++ if (priority != COMPLETING_PRIORITY) { ++ task.setQueue(this); ++ this.queues[priority].add(task); ++ } ++ if (this.shutdown.get()) { ++ // note: we're not actually sure at this point if our task will go through ++ throw new IllegalStateException("Queue has shutdown, refusing to execute task " + IOUtil.genericToString(task)); ++ } ++ } ++ ++ /** ++ * Polls the highest priority task currently available. {@code null} if none. ++ */ ++ public T poll() { ++ T task; ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ final ConcurrentLinkedQueue queue = this.queues[i]; ++ ++ while ((task = queue.poll()) != null) { ++ final int prevPriority = task.tryComplete(i); ++ if (prevPriority != COMPLETING_PRIORITY && prevPriority <= i) { ++ // if the prev priority was greater-than or equal to our current priority ++ return task; ++ } ++ } ++ } ++ ++ return null; ++ } ++ ++ /** ++ * Returns whether this queue may have tasks queued. ++ *

    ++ * This operation is not atomic, but is MT-Safe. ++ *

    ++ * @return {@code true} if tasks may be queued, {@code false} otherwise ++ */ ++ public boolean hasTasks() { ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ final ConcurrentLinkedQueue queue = this.queues[i]; ++ ++ if (queue.peek() != null) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will ++ * result in {@link IllegalStateException} being thrown. ++ *

    ++ * This operation is atomic with respect to other shutdown calls ++ *

    ++ *

    ++ * After this call has completed, regardless of return value, this queue will be shutdown. ++ *

    ++ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already ++ */ ++ public boolean shutdown() { ++ return this.shutdown.getAndSet(false); ++ } ++ ++ public abstract static class PrioritizedTask { ++ ++ protected final AtomicReference queue = new AtomicReference<>(); ++ ++ protected final AtomicInteger priority; ++ ++ protected PrioritizedTask() { ++ this(PrioritizedTaskQueue.NORMAL_PRIORITY); ++ } ++ ++ protected PrioritizedTask(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.priority = new AtomicInteger(priority); ++ } ++ ++ /** ++ * Returns the current priority. Note that {@link PrioritizedTaskQueue#COMPLETING_PRIORITY} will be returned ++ * if this task is completing or has completed. ++ */ ++ public final int getPriority() { ++ return this.priority.get(); ++ } ++ ++ /** ++ * Returns whether this task is scheduled to execute, or has been already executed. ++ */ ++ public boolean isScheduled() { ++ return this.queue.get() != null; ++ } ++ ++ final int tryComplete(final int minPriority) { ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return COMPLETING_PRIORITY; ++ } ++ if (curr > minPriority) { ++ // curr is lower priority ++ return curr; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, COMPLETING_PRIORITY))) { ++ return curr; ++ } ++ continue; ++ } ++ } ++ ++ /** ++ * Forces this task to be completed. ++ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. ++ */ ++ public boolean cancel() { ++ return this.exchangePriorityVolatile(PrioritizedTaskQueue.COMPLETING_PRIORITY) != PrioritizedTaskQueue.COMPLETING_PRIORITY; ++ } ++ ++ /** ++ * Attempts to raise the priority to the priority level specified. ++ * @param priority Priority specified ++ * @return {@code true} if successful, {@code false} otherwise. ++ */ ++ public boolean raisePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority"); ++ } ++ ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return false; ++ } ++ if (priority >= curr) { ++ return true; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) { ++ PrioritizedTaskQueue queue = this.queue.get(); ++ if (queue != null) { ++ //noinspection unchecked ++ queue.queues[priority].add(this); // silently fail on shutdown ++ } ++ return true; ++ } ++ continue; ++ } ++ } ++ ++ /** ++ * Attempts to set this task's priority level to the level specified. ++ * @param priority Specified priority level. ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed. ++ */ ++ public boolean updatePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority"); ++ } ++ ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return false; ++ } ++ if (curr == priority) { ++ return true; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) { ++ PrioritizedTaskQueue queue = this.queue.get(); ++ if (queue != null) { ++ //noinspection unchecked ++ queue.queues[priority].add(this); // silently fail on shutdown ++ } ++ return true; ++ } ++ continue; ++ } ++ } ++ ++ void setQueue(final PrioritizedTaskQueue queue) { ++ this.queue.set(queue); ++ } ++ ++ /* priority */ ++ ++ protected final int getPriorityVolatile() { ++ return this.priority.get(); ++ } ++ ++ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { ++ if (this.priority.compareAndSet(expect, update)) { ++ return expect; ++ } ++ return this.priority.get(); ++ } ++ ++ protected final int exchangePriorityVolatile(final int value) { ++ return this.priority.getAndSet(value); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee906b594b306906c170180a29a8b61997d05168 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +@@ -0,0 +1,241 @@ ++package com.destroystokyo.paper.io; ++ ++import net.minecraft.server.MinecraftServer; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.locks.LockSupport; ++ ++public class QueueExecutorThread extends Thread { ++ ++ private static final Logger LOGGER = MinecraftServer.LOGGER; ++ ++ protected final PrioritizedTaskQueue queue; ++ protected final long spinWaitTime; ++ ++ protected volatile boolean closed; ++ ++ protected final AtomicBoolean parked = new AtomicBoolean(); ++ ++ protected volatile ConcurrentLinkedQueue flushQueue = new ConcurrentLinkedQueue<>(); ++ protected volatile long flushCycles; ++ ++ public QueueExecutorThread(final PrioritizedTaskQueue queue) { ++ this(queue, (int)(1.e6)); // 1.0ms ++ } ++ ++ public QueueExecutorThread(final PrioritizedTaskQueue queue, final long spinWaitTime) { // in ms ++ this.queue = queue; ++ this.spinWaitTime = spinWaitTime; ++ } ++ ++ @Override ++ public void run() { ++ final long spinWaitTime = this.spinWaitTime; ++ main_loop: ++ for (;;) { ++ this.pollTasks(true); ++ ++ // spinwait ++ ++ final long start = System.nanoTime(); ++ ++ for (;;) { ++ // If we are interrpted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event. ++ Thread.interrupted(); ++ LockSupport.parkNanos("Spinwaiting on tasks", 1000L); // 1us ++ ++ if (this.pollTasks(true)) { ++ // restart loop, found tasks ++ continue main_loop; ++ } ++ ++ if (this.handleClose()) { ++ return; // we're done ++ } ++ ++ if ((System.nanoTime() - start) >= spinWaitTime) { ++ break; ++ } ++ } ++ ++ if (this.handleClose()) { ++ return; ++ } ++ ++ this.parked.set(true); ++ ++ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true ++ // (i.e it will not notify us) ++ if (this.pollTasks(true)) { ++ this.parked.set(false); ++ continue; ++ } ++ ++ if (this.handleClose()) { ++ return; ++ } ++ ++ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop ++ // LockSupport.park() can fail for any reason ++ do { ++ Thread.interrupted(); ++ LockSupport.park("Waiting on tasks"); ++ } while (this.parked.get()); ++ } ++ } ++ ++ protected boolean handleClose() { ++ if (this.closed) { ++ this.pollTasks(true); // this ensures we've emptied the queue ++ this.handleFlushThreads(true); ++ return true; ++ } ++ return false; ++ } ++ ++ protected boolean pollTasks(boolean flushTasks) { ++ Runnable task; ++ boolean ret = false; ++ ++ while ((task = this.queue.poll()) != null) { ++ ret = true; ++ try { ++ task.run(); ++ } catch (final Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ LOGGER.fatal("Exception thrown from prioritized runnable task in thread '" + this.getName() + "': " + IOUtil.genericToString(task), throwable); ++ } ++ } ++ ++ if (flushTasks) { ++ this.handleFlushThreads(false); ++ } ++ ++ return ret; ++ } ++ ++ protected void handleFlushThreads(final boolean shutdown) { ++ Thread parking; ++ ConcurrentLinkedQueue flushQueue = this.flushQueue; ++ do { ++ ++flushCycles; // may be plain read opaque write ++ while ((parking = flushQueue.poll()) != null) { ++ LockSupport.unpark(parking); ++ } ++ } while (this.pollTasks(false)); ++ ++ if (shutdown) { ++ this.flushQueue = null; ++ ++ // defend against a race condition where a flush thread double-checks right before we set to null ++ while ((parking = flushQueue.poll()) != null) { ++ LockSupport.unpark(parking); ++ } ++ } ++ } ++ ++ /** ++ * Notify's this thread that a task has been added to its queue ++ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks ++ */ ++ public boolean notifyTasks() { ++ if (this.parked.get() && this.parked.getAndSet(false)) { ++ LockSupport.unpark(this); ++ return true; ++ } ++ return false; ++ } ++ ++ protected void queueTask(final T task) { ++ this.queue.add(task); ++ this.notifyTasks(); ++ } ++ ++ /** ++ * Waits until this thread's queue is empty. ++ * ++ * @throws IllegalStateException If the current thread is {@code this} thread. ++ */ ++ public void flush() { ++ final Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread == this) { ++ // avoid deadlock ++ throw new IllegalStateException("Cannot flush the queue executor thread while on the queue executor thread"); ++ } ++ ++ // order is important ++ ++ int successes = 0; ++ long lastCycle = -1L; ++ ++ do { ++ final ConcurrentLinkedQueue flushQueue = this.flushQueue; ++ if (flushQueue == null) { ++ return; ++ } ++ ++ flushQueue.add(currentThread); ++ ++ // double check flush queue ++ if (this.flushQueue == null) { ++ return; ++ } ++ ++ final long currentCycle = this.flushCycles; // may be opaque read ++ ++ if (currentCycle == lastCycle) { ++ Thread.yield(); ++ continue; ++ } ++ ++ // force response ++ this.parked.set(false); ++ LockSupport.unpark(this); ++ ++ LockSupport.park("flushing queue executor thread"); ++ ++ // returns whether there are tasks queued, does not return whether there are tasks executing ++ // this is why we cycle twice twice through flush (we know a pollTask call is made after a flush cycle) ++ // we really only need to guarantee that the tasks this thread has queued has gone through, and can leave ++ // tasks queued concurrently that are unsychronized with this thread as undefined behavior ++ if (this.queue.hasTasks()) { ++ successes = 0; ++ } else { ++ ++successes; ++ } ++ ++ } while (successes != 2); ++ ++ } ++ ++ /** ++ * Closes this queue executor's queue and optionally waits for it to empty. ++ *

    ++ * If wait is {@code true}, then the queue will be empty by the time this call completes. ++ *

    ++ *

    ++ * This function is MT-Safe. ++ *

    ++ * @param wait If this call is to wait until the queue is empty ++ * @param killQueue Whether to shutdown this thread's queue ++ * @return whether this thread shut down the queue ++ */ ++ public boolean close(final boolean wait, final boolean killQueue) { ++ boolean ret = !killQueue ? false : this.queue.shutdown(); ++ this.closed = true; ++ ++ // force thread to respond to the shutdown ++ this.parked.set(false); ++ LockSupport.unpark(this); ++ ++ if (wait) { ++ this.flush(); ++ } ++ return ret; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..26a5da48c87674f320aa9f7382217cde2c93e08c +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java +@@ -0,0 +1,145 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import co.aikar.timings.Timing; ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import java.util.ArrayDeque; ++import java.util.function.Consumer; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++ ++public final class ChunkLoadTask extends ChunkTask { ++ ++ public boolean cancelled; ++ ++ Consumer onComplete; ++ public PaperFileIOThread.ChunkData chunkData; ++ ++ private boolean hasCompleted; ++ ++ public ChunkLoadTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager, ++ final Consumer onComplete) { ++ super(world, chunkX, chunkZ, priority, taskManager); ++ this.onComplete = onComplete; ++ } ++ ++ private static final ArrayDeque EMPTY_QUEUE = new ArrayDeque<>(); ++ ++ private static ChunkSerializer.InProgressChunkHolder createEmptyHolder() { ++ return new ChunkSerializer.InProgressChunkHolder(null, EMPTY_QUEUE); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ this.executeTask(); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Failed to execute chunk load task: " + this.toString(), ex); ++ if (!this.hasCompleted) { ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ } ++ } ++ } ++ ++ private boolean checkCancelled() { ++ if (this.cancelled) { ++ // IntelliJ does not understand writes may occur to cancelled concurrently. ++ return this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != ChunkLoadTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this); ++ } ++ ++ if (valueInMap.cancelled) { ++ return null; ++ } ++ return valueInMap; ++ }) == null; ++ } ++ return false; ++ } ++ ++ public void executeTask() { ++ if (this.checkCancelled()) { ++ return; ++ } ++ ++ // either executed synchronously or asynchronously ++ final PaperFileIOThread.ChunkData chunkData = this.chunkData; ++ ++ if (chunkData.poiData == PaperFileIOThread.FAILURE_VALUE || chunkData.chunkData == PaperFileIOThread.FAILURE_VALUE) { ++ PaperFileIOThread.LOGGER.error("Could not load chunk for task: " + this.toString() + ", file IO thread has dumped the relevant exception above"); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ if (chunkData.chunkData == null) { ++ // not on disk ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ); ++ ++ final ChunkMap chunkManager = this.world.getChunkSource().chunkMap; ++ ++ try (Timing ignored = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { ++ final ChunkSerializer.InProgressChunkHolder chunkHolder; ++ ++ // apply fixes ++ ++ try { ++ chunkData.chunkData = chunkManager.getChunkData(this.world.getTypeKey(), ++ chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData, chunkPos, this.world); // clone data for safety, file IO thread does not clone ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ } ++ ++ if (this.checkCancelled()) { ++ return; ++ } ++ ++ try { ++ this.world.getChunkSource().chunkMap.updateChunkStatusOnDisk(chunkPos, chunkData.chunkData); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.warn("Failed to update chunk status cache for task: " + this.toString(), ex); ++ // non-fatal, continue ++ } ++ ++ try { ++ chunkHolder = ChunkSerializer.loadChunk(this.world, ++ chunkManager.structureManager, chunkManager.getVillagePlace(), chunkPos, ++ chunkData.chunkData, true); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Could not de-serialize chunk data for task: " + this.toString(), ex); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ this.complete(chunkHolder); ++ } ++ } ++ ++ private void complete(final ChunkSerializer.InProgressChunkHolder holder) { ++ this.hasCompleted = true; ++ holder.poiData = this.chunkData == null ? null : this.chunkData.poiData; ++ ++ this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != ChunkLoadTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this); ++ } ++ if (valueInMap.cancelled) { ++ return null; ++ } ++ try { ++ ChunkLoadTask.this.onComplete.accept(holder); ++ } catch (final Throwable thr) { ++ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); ++ } ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..69ebbfa171385c46a84d1a0d241d168a8c2af145 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java +@@ -0,0 +1,111 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import co.aikar.timings.Timing; ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.atomic.AtomicInteger; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++ ++public final class ChunkSaveTask extends ChunkTask { ++ ++ public final ChunkSerializer.AsyncSaveData asyncSaveData; ++ public final ChunkAccess chunk; ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ ++ private final AtomicInteger attemptedPriority; ++ ++ public ChunkSaveTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager, final ChunkSerializer.AsyncSaveData asyncSaveData, ++ final ChunkAccess chunk) { ++ super(world, chunkX, chunkZ, priority, taskManager); ++ this.chunk = chunk; ++ this.asyncSaveData = asyncSaveData; ++ this.attemptedPriority = new AtomicInteger(priority); ++ } ++ ++ @Override ++ public void run() { ++ // can be executed asynchronously or synchronously ++ final CompoundTag compound; ++ ++ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTimingIfSync()) { ++ compound = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData); ++ } catch (final Throwable ex) { ++ // has a plugin modified something it should not have and made us CME? ++ PaperFileIOThread.LOGGER.error("Failed to serialize unloading chunk data for task: " + this.toString() + ", falling back to a synchronous execution", ex); ++ ++ // Note: We add to the server thread queue here since this is what the server will drain tasks from ++ // when waiting for chunks ++ ChunkTaskManager.queueChunkWaitTask(() -> { ++ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTiming()) { ++ CompoundTag data = PaperFileIOThread.FAILURE_VALUE; ++ ++ try { ++ data = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData); ++ PaperFileIOThread.LOGGER.info("Successfully serialized chunk data for task: " + this.toString() + " synchronously"); ++ } catch (final Throwable ex1) { ++ PaperFileIOThread.LOGGER.fatal("Failed to synchronously serialize unloading chunk data for task: " + this.toString() + "! Chunk data will be lost", ex1); ++ } ++ ++ ChunkSaveTask.this.complete(data); ++ } ++ }); ++ ++ return; // the main thread will now complete the data ++ } ++ ++ this.complete(compound); ++ } ++ ++ @Override ++ public boolean raisePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalStateException("Invalid priority: " + priority); ++ } ++ ++ // we know priority is valid here ++ for (int curr = this.attemptedPriority.get();;) { ++ if (curr <= priority) { ++ break; // curr is higher/same priority ++ } ++ if (this.attemptedPriority.compareAndSet(curr, priority)) { ++ break; ++ } ++ curr = this.attemptedPriority.get(); ++ } ++ ++ return super.raisePriority(priority); ++ } ++ ++ @Override ++ public boolean updatePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalStateException("Invalid priority: " + priority); ++ } ++ this.attemptedPriority.set(priority); ++ return super.updatePriority(priority); ++ } ++ ++ private void complete(final CompoundTag compound) { ++ try { ++ this.onComplete.complete(compound); ++ } catch (final Throwable thr) { ++ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); ++ } ++ if (compound != PaperFileIOThread.FAILURE_VALUE) { ++ PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, this.chunkX, this.chunkZ, null, compound, this.attemptedPriority.get()); ++ } ++ this.taskManager.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> { ++ if (valueInMap != ChunkSaveTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", this: " + ChunkSaveTask.this); ++ } ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..058fb5a41565e6ce2acbd1f4d071a1b8be449f5d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java +@@ -0,0 +1,40 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++import net.minecraft.server.level.ServerLevel; ++ ++abstract class ChunkTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ public final ServerLevel world; ++ public final int chunkX; ++ public final int chunkZ; ++ public final ChunkTaskManager taskManager; ++ ++ public ChunkTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager) { ++ super(priority); ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.taskManager = taskManager; ++ } ++ ++ @Override ++ public String toString() { ++ return "Chunk task: class:" + this.getClass().getName() + ", for world '" + this.world.getWorld().getName() + ++ "', (" + this.chunkX + "," + this.chunkZ + "), hashcode:" + this.hashCode() + ", priority: " + this.getPriority(); ++ } ++ ++ @Override ++ public boolean raisePriority(final int priority) { ++ PaperFileIOThread.Holder.INSTANCE.bumpPriority(this.world, this.chunkX, this.chunkZ, priority); ++ return super.raisePriority(priority); ++ } ++ ++ @Override ++ public boolean updatePriority(final int priority) { ++ PaperFileIOThread.Holder.INSTANCE.setPriority(this.world, this.chunkX, this.chunkZ, priority); ++ return super.updatePriority(priority); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..499aff1f1e1ffc01ba8f9de43ca17899525a306f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +@@ -0,0 +1,513 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++import com.destroystokyo.paper.io.QueueExecutorThread; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.thread.BlockableEventLoop; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import org.apache.commons.lang.StringUtils; ++import org.apache.logging.log4j.Level; ++import org.bukkit.Bukkit; ++import org.spigotmc.AsyncCatcher; ++ ++import java.util.ArrayDeque; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.function.Consumer; ++ ++public final class ChunkTaskManager { ++ ++ private final QueueExecutorThread[] workers; ++ private final ServerLevel world; ++ ++ private final PrioritizedTaskQueue queue; ++ private final boolean perWorldQueue; ++ ++ final ConcurrentHashMap chunkLoadTasks = new ConcurrentHashMap<>(64, 0.5f); ++ final ConcurrentHashMap chunkSaveTasks = new ConcurrentHashMap<>(64, 0.5f); ++ ++ private final PrioritizedTaskQueue chunkTasks = new PrioritizedTaskQueue<>(); // used if async chunks are disabled in config ++ ++ protected static QueueExecutorThread[] globalWorkers; ++ protected static QueueExecutorThread globalUrgentWorker; ++ protected static PrioritizedTaskQueue globalQueue; ++ protected static PrioritizedTaskQueue globalUrgentQueue; ++ ++ protected static final ConcurrentLinkedQueue CHUNK_WAIT_QUEUE = new ConcurrentLinkedQueue<>(); ++ ++ public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack ++ ++ private static final class ChunkInfo { ++ ++ public final int chunkX; ++ public final int chunkZ; ++ public final ServerLevel world; ++ ++ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) { ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.world = world; ++ } ++ ++ @Override ++ public String toString() { ++ return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "']"; ++ } ++ } ++ ++ public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) { ++ synchronized (WAITING_CHUNKS) { ++ WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world)); ++ } ++ } ++ ++ public static void popChunkWait() { ++ synchronized (WAITING_CHUNKS) { ++ WAITING_CHUNKS.pop(); ++ } ++ } ++ ++ private static ChunkInfo[] getChunkInfos() { ++ ChunkInfo[] chunks; ++ synchronized (WAITING_CHUNKS) { ++ chunks = WAITING_CHUNKS.toArray(new ChunkInfo[0]); ++ } ++ return chunks; ++ } ++ ++ public static void dumpAllChunkLoadInfo() { ++ ChunkInfo[] chunks = getChunkInfos(); ++ if (chunks.length > 0) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Chunk wait task info below: "); ++ ++ for (final ChunkInfo chunkInfo : chunks) { ++ final long key = IOUtil.getCoordinateKey(chunkInfo.chunkX, chunkInfo.chunkZ); ++ final ChunkLoadTask loadTask = chunkInfo.world.asyncChunkTaskManager.chunkLoadTasks.get(key); ++ final ChunkSaveTask saveTask = chunkInfo.world.asyncChunkTaskManager.chunkSaveTasks.get(key); ++ ++ PaperFileIOThread.LOGGER.log(Level.ERROR, chunkInfo.chunkX + "," + chunkInfo.chunkZ + " in '" + chunkInfo.world.getWorld().getName() + ":"); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Load Task - " + (loadTask == null ? "none" : loadTask.toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Save Task - " + (saveTask == null ? "none" : saveTask.toString())); ++ // log current status of chunk to indicate whether we're waiting on generation or loading ++ ChunkHolder chunkHolder = chunkInfo.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(key); ++ ++ dumpChunkInfo(new HashSet<>(), chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); ++ } ++ } ++ } ++ ++ static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z) { ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); ++ } ++ ++ static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) { ++ if (seenChunks.contains(chunkHolder)) { ++ return; ++ } ++ if (indent > maxDepth) { ++ return; ++ } ++ seenChunks.add(chunkHolder); ++ String indentStr = StringUtils.repeat(" ", indent); ++ if (chunkHolder == null) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder - null for (" + x +"," + z +")"); ++ } else { ++ ChunkAccess chunk = chunkHolder.getAvailableChunkNow(); ++ ChunkStatus holderStatus = chunkHolder.getChunkHolderStatus(); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder - non-null"); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); ++ } ++ } ++ ++ public static void initGlobalLoadThreads(int threads) { ++ if (threads <= 0 || globalWorkers != null) { ++ return; ++ } ++ ++ globalWorkers = new QueueExecutorThread[threads]; ++ globalQueue = new PrioritizedTaskQueue<>(); ++ globalUrgentQueue = new PrioritizedTaskQueue<>(); ++ ++ for (int i = 0; i < threads; ++i) { ++ globalWorkers[i] = new QueueExecutorThread<>(globalQueue, (long)0.10e6); //0.1ms ++ globalWorkers[i].setName("Paper Async Chunk Task Thread #" + i); ++ globalWorkers[i].setPriority(Thread.NORM_PRIORITY - 1); ++ globalWorkers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ globalWorkers[i].start(); ++ } ++ ++ globalUrgentWorker = new QueueExecutorThread<>(globalUrgentQueue, (long)0.10e6); //0.1ms ++ globalUrgentWorker.setName("Paper Async Chunk Urgent Task Thread"); ++ globalUrgentWorker.setPriority(Thread.NORM_PRIORITY+1); ++ globalUrgentWorker.setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ globalUrgentWorker.start(); ++ } ++ ++ /** ++ * Creates this chunk task manager to operate off the specified number of threads. If the specified number of threads is ++ * less-than or equal to 0, then this chunk task manager will operate off of the world's chunk task queue. ++ * @param world Specified world. ++ * @param threads Specified number of threads. ++ * @see ServerChunkCache#mainThreadProcessor ++ */ ++ public ChunkTaskManager(final ServerLevel world, final int threads) { ++ this.world = world; ++ this.workers = threads <= 0 ? null : new QueueExecutorThread[threads]; ++ this.queue = new PrioritizedTaskQueue<>(); ++ this.perWorldQueue = true; ++ ++ for (int i = 0; i < threads; ++i) { ++ this.workers[i] = new QueueExecutorThread<>(this.queue, (long)0.10e6); //0.1ms ++ this.workers[i].setName("Async chunk loader thread #" + i + " for world: " + world.getWorld().getName()); ++ this.workers[i].setPriority(Thread.NORM_PRIORITY - 1); ++ this.workers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ this.workers[i].start(); ++ } ++ } ++ ++ /** ++ * Creates the chunk task manager to work from the global workers. When {@link #close(boolean)} is invoked, ++ * the global queue is not shutdown. If the global workers is configured to be disabled or use 0 threads, then ++ * this chunk task manager will operate off of the world's chunk task queue. ++ * @param world The world that this task manager is responsible for ++ * @see ServerChunkCache#mainThreadProcessor ++ */ ++ public ChunkTaskManager(final ServerLevel world) { ++ this.world = world; ++ this.workers = globalWorkers; ++ this.queue = globalQueue; ++ this.perWorldQueue = false; ++ } ++ ++ public boolean pollNextChunkTask() { ++ final ChunkTask task = this.chunkTasks.poll(); ++ ++ if (task != null) { ++ task.run(); ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Polls and runs the next available chunk wait queue task. This is to be used when the server is waiting on a chunk queue. ++ * (per-world can cause issues if all the worker threads are blocked waiting for a response from the main thread) ++ */ ++ public static boolean pollChunkWaitQueue() { ++ final Runnable run = CHUNK_WAIT_QUEUE.poll(); ++ if (run != null) { ++ run.run(); ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Queues a chunk wait task. Note that this will execute out of order with respect to tasks scheduled on a world's ++ * chunk task queue, since this is the global chunk wait queue. ++ */ ++ public static void queueChunkWaitTask(final Runnable runnable) { ++ CHUNK_WAIT_QUEUE.add(runnable); ++ } ++ ++ private static void drainChunkWaitQueue() { ++ Runnable run; ++ while ((run = CHUNK_WAIT_QUEUE.poll()) != null) { ++ run.run(); ++ } ++ } ++ ++ /** ++ * The exact same as {@link #scheduleChunkLoad(int, int, int, Consumer, boolean)}, except that the chunk data is provided as ++ * the {@code data} parameter. ++ */ ++ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority, ++ final Consumer onComplete, ++ final boolean intendingToBlock, final CompletableFuture dataFuture) { ++ final ServerLevel world = this.world; ++ ++ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != null) { ++ if (!valueInMap.cancelled) { ++ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString()); ++ } ++ valueInMap.cancelled = false; ++ valueInMap.onComplete = onComplete; ++ return valueInMap; ++ } ++ ++ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete); ++ ++ dataFuture.thenAccept((final CompoundTag data) -> { ++ final boolean failed = data == PaperFileIOThread.FAILURE_VALUE; ++ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> { ++ ret.chunkData = chunkData; ++ if (!failed) { ++ chunkData.chunkData = data; ++ } ++ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here ++ }, true, failed, intendingToBlock); // read data off disk if the future fails ++ }); ++ ++ return ret; ++ }); ++ } ++ ++ public void cancelChunkLoad(final int chunkX, final int chunkZ) { ++ this.chunkLoadTasks.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap == null) { ++ return null; ++ } ++ ++ if (valueInMap.cancelled) { ++ PaperFileIOThread.LOGGER.warn("Task " + valueInMap.toString() + " is already cancelled!"); ++ } ++ valueInMap.cancelled = true; ++ if (valueInMap.cancel()) { ++ return null; ++ } ++ ++ return valueInMap; ++ }); ++ } ++ ++ /** ++ * Schedules an asynchronous chunk load for the specified coordinates. The onComplete parameter may be invoked asynchronously ++ * on a worker thread or on the world's chunk executor queue. As such the code that is executed for the parameter should be ++ * carefully chosen. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority for this task ++ * @param onComplete The consumer to invoke with the {@link ChunkSerializer.InProgressChunkHolder} object once this task is complete ++ * @param intendingToBlock Whether the caller is intending to block on this task completing (this is a performance tune, and has no adverse side-effects) ++ * @return The {@link ChunkLoadTask} associated with ++ */ ++ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority, ++ final Consumer onComplete, ++ final boolean intendingToBlock) { ++ final ServerLevel world = this.world; ++ ++ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != null) { ++ if (!valueInMap.cancelled) { ++ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString()); ++ } ++ valueInMap.cancelled = false; ++ valueInMap.onComplete = onComplete; ++ return valueInMap; ++ } ++ ++ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete); ++ ++ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> { ++ ret.chunkData = chunkData; ++ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here ++ }, true, true, intendingToBlock); ++ ++ return ret; ++ }); ++ } ++ ++ /** ++ * Schedules an async save for the specified chunk. The chunk, at the beginning of this call, must be completely unloaded ++ * from the world. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority for this task ++ * @param asyncSaveData Async save data. See {@link ChunkSerializer#getAsyncSaveData(ServerLevel, ChunkAccess)} ++ * @param chunk Chunk to save ++ * @return The {@link ChunkSaveTask} associated with the save task. ++ */ ++ public ChunkSaveTask scheduleChunkSave(final int chunkX, final int chunkZ, final int priority, ++ final ChunkSerializer.AsyncSaveData asyncSaveData, ++ final ChunkAccess chunk) { ++ AsyncCatcher.catchOp("chunk save schedule"); ++ ++ final ServerLevel world = this.world; ++ ++ return this.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> { ++ if (valueInMap != null) { ++ throw new IllegalStateException("Double scheduling chunk save for task: " + valueInMap.toString()); ++ } ++ ++ final ChunkSaveTask ret = new ChunkSaveTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, asyncSaveData, chunk); ++ ++ ChunkTaskManager.this.internalSchedule(ret); ++ ++ return ret; ++ }); ++ } ++ ++ /** ++ * Returns a completable future which will be completed with the un-copied chunk data for an in progress async save. ++ * Returns {@code null} if no save is in progress. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ */ ++ public CompletableFuture getChunkSaveFuture(final int chunkX, final int chunkZ) { ++ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ if (chunkSaveTask == null) { ++ return null; ++ } ++ return chunkSaveTask.onComplete; ++ } ++ ++ /** ++ * Returns the chunk object being used to serialize data async for an unloaded chunk. Note that modifying this chunk ++ * is not safe to do as another thread is handling its save. The chunk is also not loaded into the world. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @return Chunk object for an in-progress async save, or {@code null} if no save is in progress ++ */ ++ public ChunkAccess getChunkInSaveProgress(final int chunkX, final int chunkZ) { ++ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ if (chunkSaveTask == null) { ++ return null; ++ } ++ return chunkSaveTask.chunk; ++ } ++ ++ public void flush() { ++ // flush here since we schedule tasks on the IO thread that can schedule tasks here ++ drainChunkWaitQueue(); ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ drainChunkWaitQueue(); ++ ++ if (this.workers == null) { ++ if (Bukkit.isPrimaryThread() || MinecraftServer.getServer().hasStopped()) { ++ ((BlockableEventLoop)this.world.getChunkSource().mainThreadProcessor).runAllTasks(); ++ } else { ++ CompletableFuture wait = new CompletableFuture<>(); ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ ((BlockableEventLoop)this.world.getChunkSource().mainThreadProcessor).runAllTasks(); ++ }); ++ wait.join(); ++ } ++ } else { ++ for (final QueueExecutorThread worker : this.workers) { ++ worker.flush(); ++ } ++ } ++ if (globalUrgentWorker != null) globalUrgentWorker.flush(); ++ ++ // flush again since tasks we execute async saves ++ drainChunkWaitQueue(); ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ } ++ ++ public void close(final boolean wait) { ++ // flush here since we schedule tasks on the IO thread that can schedule tasks to this task manager ++ // we do this regardless of the wait param since after we invoke close no tasks can be queued ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ ++ if (this.workers == null) { ++ if (wait) { ++ this.flush(); ++ } ++ return; ++ } ++ ++ if (this.workers != globalWorkers) { ++ for (final QueueExecutorThread worker : this.workers) { ++ worker.close(false, this.perWorldQueue); ++ } ++ } ++ ++ if (wait) { ++ this.flush(); ++ } ++ } ++ ++ public void raisePriority(final int chunkX, final int chunkZ, final int priority) { ++ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ ChunkTask chunkSaveTask = this.chunkSaveTasks.get(chunkKey); ++ if (chunkSaveTask != null) { ++ // don't bump save into urgent queue ++ raiseTaskPriority(chunkSaveTask, priority != PrioritizedTaskQueue.HIGHEST_PRIORITY ? priority : PrioritizedTaskQueue.HIGH_PRIORITY); ++ } ++ ++ ChunkLoadTask chunkLoadTask = this.chunkLoadTasks.get(chunkKey); ++ if (chunkLoadTask != null) { ++ raiseTaskPriority(chunkLoadTask, priority); ++ } ++ } ++ ++ private void raiseTaskPriority(ChunkTask task, int priority) { ++ final boolean raised = task.raisePriority(priority); ++ if (task.isScheduled() && raised && this.workers != null) { ++ // only notify if we're in queue to be executed ++ if (priority == PrioritizedTaskQueue.HIGHEST_PRIORITY) { ++ // was in another queue but became urgent later, add to urgent queue and the previous ++ // queue will just have to ignore this task if it has already been started. ++ // Ultimately, we now have 2 potential queues that can pull it out whoever gets it first ++ // but the urgent queue has dedicated thread(s) so it's likely to win.... ++ globalUrgentQueue.add(task); ++ this.internalScheduleNotifyUrgent(); ++ } else { ++ this.internalScheduleNotify(); ++ } ++ } ++ } ++ ++ protected void internalSchedule(final ChunkTask task) { ++ if (this.workers == null) { ++ this.chunkTasks.add(task); ++ return; ++ } ++ ++ // It's important we order the task to be executed before notifying. Avoid a race condition where the worker thread ++ // wakes up and goes to sleep before we actually schedule (or it's just about to sleep) ++ if (task.getPriority() == PrioritizedTaskQueue.HIGHEST_PRIORITY) { ++ globalUrgentQueue.add(task); ++ this.internalScheduleNotifyUrgent(); ++ } else { ++ this.queue.add(task); ++ this.internalScheduleNotify(); ++ } ++ ++ } ++ ++ protected void internalScheduleNotify() { ++ if (this.workers == null) { ++ return; ++ } ++ for (final QueueExecutorThread worker : this.workers) { ++ if (worker.notifyTasks()) { ++ // break here since we only want to wake up one worker for scheduling one task ++ break; ++ } ++ } ++ } ++ ++ ++ protected void internalScheduleNotifyUrgent() { ++ if (globalUrgentWorker == null) { ++ return; ++ } ++ globalUrgentWorker.notifyTasks(); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +index 354783f862986bf939639a86a9076ac0f5ed97e3..c171860bc117199ca00085bf37507f867d51fb62 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +@@ -14,7 +14,7 @@ public class ServerboundCommandSuggestionPacket implements Packet { + DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, datapackconfiguration1, thread, iregistrycustom_dimension, convertable_conversionsession, resourcepackrepository, datapackresources, null, dedicatedserversettings, DataFixers.getDataFixer(), minecraftsessionservice, gameprofilerepository, usercache, LoggerChunkProgressListener::new); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index aab1a055c065d1f1a92461e4442ec2cdd8e0b347..643d75b999c3da006eaaab11f4acd77e807683d4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -920,7 +920,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> future = this.getFutureIfPresentUnchecked(curr); ++ Either either = future.getNow(null); ++ if (either == null || !either.left().isPresent()) { ++ continue; ++ } ++ return curr; ++ } ++ return null; ++ } + // Paper end + + public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { +@@ -375,7 +387,7 @@ public class ChunkHolder { + ChunkStatus chunkstatus = getStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = getStatus(this.ticketLevel); + boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; +- boolean flag1 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; ++ boolean flag1 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; // Paper - diff on change: (flag1 = new ticket level is in loadable range) + ChunkHolder.FullChunkStatus playerchunk_state = getFullChunkStatus(this.oldTicketLevel); + ChunkHolder.FullChunkStatus playerchunk_state1 = getFullChunkStatus(this.ticketLevel); + // CraftBukkit start +@@ -411,6 +423,12 @@ public class ChunkHolder { + } + }); + ++ // Paper start ++ if (!flag1) { ++ chunkStorage.level.asyncChunkTaskManager.cancelChunkLoad(this.pos.x, this.pos.z); ++ } ++ // Paper end ++ + for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) { + completablefuture = (CompletableFuture) this.futures.get(i); + if (completablefuture != null) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index b2d668607c2b5122d06fa75f77b3cef44100fe28..c00f7c60ce7b497d697d1abdf230f91f327e2113 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -86,6 +86,7 @@ import net.minecraft.world.level.chunk.ProtoChunk; + import net.minecraft.world.level.chunk.UpgradeData; + import net.minecraft.world.level.chunk.storage.ChunkSerializer; + import net.minecraft.world.level.chunk.storage.ChunkStorage; ++import net.minecraft.world.level.chunk.storage.RegionFile; + import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; + import net.minecraft.world.level.storage.DimensionDataStorage; +@@ -110,7 +111,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final ThreadedLevelLightEngine lightEngine; + private final BlockableEventLoop mainThreadExecutor; + public final ChunkGenerator generator; +- private final Supplier overworldDataStorage; ++ private final Supplier overworldDataStorage; public final Supplier getWorldPersistentDataSupplier() { return this.overworldDataStorage; } // Paper - OBFHELPER + private final PoiManager poiManager; + public final LongSet toDrop; + private boolean modified; +@@ -120,7 +121,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final ChunkProgressListener progressListener; + public final ChunkMap.ChunkDistanceManager distanceManager; + private final AtomicInteger tickingGenerated; +- private final StructureManager structureManager; ++ public final StructureManager structureManager; // Paper - private -> public + private final File storageFolder; + private final PlayerMap playerMap; + public final Int2ObjectMap entityMap; +@@ -203,7 +204,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); + this.distanceManager = new ChunkMap.ChunkDistanceManager(workerExecutor, mainThreadExecutor); + this.overworldDataStorage = supplier; +- this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag); ++ this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper + this.setViewDistance(i); + } + +@@ -245,12 +246,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + @Nullable +- protected ChunkHolder getUpdatingChunkIfPresent(long pos) { ++ public ChunkHolder getUpdatingChunkIfPresent(long pos) { // Paper + return (ChunkHolder) this.updatingChunkMap.get(pos); + } + + @Nullable +- protected ChunkHolder getVisibleChunkIfPresent(long pos) { ++ public ChunkHolder getVisibleChunkIfPresent(long pos) { // Paper - protected -> public + return (ChunkHolder) this.visibleChunkMap.get(pos); + } + +@@ -372,6 +373,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public void close() throws IOException { + try { + this.queueSorter.close(); ++ this.level.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line + this.poiManager.close(); + } finally { + super.close(); +@@ -463,7 +465,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.processUnloads(() -> { + return true; + }); +- this.flushWorker(); ++ this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour ++// this.i(); // Paper - nuke IOWorker + ChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.storageFolder.getName()); + } else { + this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> { +@@ -479,16 +482,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + +- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; // Spigot ++ private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more + + protected void tick(BooleanSupplier shouldKeepTicking) { + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + ++ try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper + gameprofilerfiller.push("poi"); + this.poiManager.tick(shouldKeepTicking); ++ } // Paper + gameprofilerfiller.popPush("chunk_unload"); + if (!this.level.noSave()) { ++ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper + this.processUnloads(shouldKeepTicking); ++ }// Paper + } + + gameprofilerfiller.pop(); +@@ -509,12 +516,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (playerchunk != null) { + this.pendingUnloads.put(j, playerchunk); + this.modified = true; ++ this.scheduleUnload(j, playerchunk); // Paper - Move up - don't leak chunks + // Spigot start + if (!shouldKeepTicking.getAsBoolean() && this.toDrop.size() <= targetSize && activityAccountant.activityTimeIsExhausted()) { + break; + } + // Spigot end +- this.scheduleUnload(j, playerchunk); ++ //this.a(j, playerchunk); // Paper - move up because spigot did a dumb + } + } + activityAccountant.endActivity(); // Spigot +@@ -528,6 +536,60 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + ++ // Paper start - async chunk save for unload ++ // Note: This is very unsafe to call if the chunk is still in use. ++ // This is also modeled after PlayerChunkMap#saveChunk(IChunkAccess, boolean), with the intentional difference being ++ // serializing the chunk is left to a worker thread. ++ private void asyncSave(ChunkAccess chunk) { ++ ChunkPos chunkPos = chunk.getPos(); ++ CompoundTag poiData; ++ try (Timing ignored = this.level.timings.chunkUnloadPOISerialization.startTiming()) { ++ poiData = this.getVillagePlace().getData(chunk.getPos()); ++ } ++ ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkPos.x, chunkPos.z, ++ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ ++ if (!chunk.isUnsaved()) { ++ return; ++ } ++ ++ ChunkStatus chunkstatus = chunk.getStatus(); ++ ++ // Copied from PlayerChunkMap#saveChunk(IChunkAccess, boolean) ++ if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) { ++ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveOverwriteCheck.startTiming()) { // Paper ++ // Paper start - Optimize save by using status cache ++ try { ++ ChunkStatus statusOnDisk = this.getChunkStatusOnDisk(chunkPos); ++ if (statusOnDisk != null && statusOnDisk.getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) { ++ // Paper end ++ return; ++ } ++ ++ if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::e)) { ++ return; ++ } ++ } catch (IOException ex) { ++ ex.printStackTrace(); ++ return; ++ } ++ } ++ } ++ ++ ChunkSerializer.AsyncSaveData asyncSaveData; ++ try (Timing ignored = this.level.timings.chunkUnloadPrepareSave.startTiming()) { ++ asyncSaveData = ChunkSerializer.getAsyncSaveData(this.level, chunk); ++ } ++ ++ this.level.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY, ++ asyncSaveData, chunk); ++ ++ chunk.setLastSaveTime(this.level.getGameTime()); ++ chunk.setUnsaved(false); ++ } ++ // Paper end ++ + private void scheduleUnload(long pos, ChunkHolder playerchunk) { + CompletableFuture completablefuture = playerchunk.getChunkToSave(); + Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error +@@ -541,7 +603,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ((LevelChunk) ichunkaccess).setLoaded(false); + } + +- this.save(ichunkaccess); ++ //this.saveChunk(ichunkaccess);// Paper - delay + if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) { + LevelChunk chunk = (LevelChunk) ichunkaccess; + +@@ -549,6 +611,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + this.autoSaveQueue.remove(playerchunk); // Paper + ++ try { ++ this.asyncSave(ichunkaccess); // Paper - async chunk saving ++ } catch (Throwable ex) { ++ LOGGER.fatal("Failed to prepare async save, attempting synchronous save", ex); ++ this.save(ichunkaccess); ++ } ++ + this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); + this.lightEngine.tryScheduleUpdate(); + this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); +@@ -619,19 +688,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private CompletableFuture> scheduleChunkLoad(ChunkPos pos) { +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - Async chunk io ++ final java.util.function.BiFunction> syncLoadComplete = (chunkHolder, ioThrowable) -> { + try (Timing ignored = this.level.timings.chunkLoad.startTimingIfSync()) { // Paper + this.level.getProfiler().incrementCounter("chunkLoad"); +- CompoundTag nbttagcompound; // Paper +- try (Timing ignored2 = this.level.timings.chunkIO.startTimingIfSync()) { // Paper start - timings +- nbttagcompound = this.readChunk(pos); +- } // Paper end ++ // Paper start ++ if (ioThrowable != null) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable); ++ } ++ ++ this.getVillagePlace().loadInData(pos, chunkHolder.poiData); ++ chunkHolder.tasks.forEach(Runnable::run); ++ // Paper end + +- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings +- boolean flag = nbttagcompound.contains("Level", 10) && nbttagcompound.getCompound("Level").contains("Status", 8); ++ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async + +- if (flag) { +- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.structureManager, this.poiManager, pos, nbttagcompound); ++ if (true) { ++ ProtoChunk protochunk = chunkHolder.protoChunk; + + protochunk.setLastSaveTime(this.level.getGameTime()); + this.markPosition(pos, protochunk.getStatus().getChunkType()); +@@ -655,7 +728,32 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.markPositionReplaceable(pos); + return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level)); // Paper - Anti-Xray - Add parameter +- }, this.mainThreadExecutor); ++ // Paper start - Async chunk io ++ }; ++ CompletableFuture> ret = new CompletableFuture<>(); ++ ++ Consumer chunkHolderConsumer = (ChunkSerializer.InProgressChunkHolder holder) -> { ++ // Go into the chunk load queue and not server task queue so we can be popped out even faster. ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.queueChunkWaitTask(() -> { ++ try { ++ ret.complete(syncLoadComplete.apply(holder, null)); ++ } catch (Exception e) { ++ ret.completeExceptionally(e); ++ } ++ }); ++ }; ++ ++ CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z); ++ if (chunkSaveFuture != null) { ++ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); ++ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); ++ } else { ++ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); ++ } ++ return ret; ++ // Paper end + } + + private void markPositionReplaceable(ChunkPos chunkcoordintpair) { +@@ -890,6 +988,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public boolean save(ChunkAccess chunk) { ++ try (co.aikar.timings.Timing ignored = this.level.timings.chunkSave.startTiming()) { // Paper + this.poiManager.flush(chunk.getPos()); + if (!chunk.isUnsaved()) { + return false; +@@ -902,6 +1001,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ChunkStatus chunkstatus = chunk.getStatus(); + + if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) { ++ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveOverwriteCheck.startTiming()) { // Paper + if (this.isExistingChunkFull(chunkcoordintpair)) { + return false; + } +@@ -909,12 +1009,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::e)) { + return false; + } ++ } // Paper + } + + this.level.getProfiler().incrementCounter("chunkSave"); +- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk); ++ CompoundTag nbttagcompound; ++ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveDataSerialization.startTiming()) { // Paper ++ nbttagcompound = ChunkSerializer.write(this.level, chunk); ++ } // Paper + +- this.write(chunkcoordintpair, nbttagcompound); ++ ++ // Paper start - async chunk io ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkcoordintpair.x, chunkcoordintpair.z, ++ null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); ++ // Paper end - async chunk io + this.markPosition(chunkcoordintpair, chunkstatus.getChunkType()); + return true; + } catch (Exception exception) { +@@ -923,6 +1031,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return false; + } + } ++ } // Paper + } + + private boolean isExistingChunkFull(ChunkPos chunkcoordintpair) { +@@ -1052,6 +1161,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + ++ // Paper start - Asynchronous chunk io ++ @Nullable ++ @Override ++ public CompoundTag read(ChunkPos chunkcoordintpair) throws IOException { ++ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .loadChunkDataAsyncFuture(this.level, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), ++ false, true, true).join().chunkData; ++ ++ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) { ++ throw new IOException("See logs for further detail"); ++ } ++ return ret; ++ } ++ return super.read(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { ++ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( ++ this.level, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound, ++ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ return; ++ } ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + @Nullable + public CompoundTag readChunk(ChunkPos pos) throws IOException { // Paper - private -> public + CompoundTag nbttagcompound = this.read(pos); +@@ -1073,33 +1211,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + // Paper start - chunk status cache "api" + public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getRegionFileIfLoaded(chunkPos); ++ synchronized (this) { // Paper ++ RegionFile regionFile = this.regionFileCache.getRegionFileIfLoaded(chunkPos); + + return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } // Paper + } + + public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, true); ++ // Paper start - async chunk save for unload ++ ChunkAccess unloadingChunk = this.level.asyncChunkTaskManager.getChunkInSaveProgress(chunkPos.x, chunkPos.z); ++ if (unloadingChunk != null) { ++ return unloadingChunk.getStatus(); ++ } ++ // Paper end ++ // Paper start - async io ++ CompoundTag inProgressWrite = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .getPendingWrite(this.level, chunkPos.x, chunkPos.z, false); + +- if (regionFile == null || !regionFile.chunkExists(chunkPos)) { +- return null; ++ if (inProgressWrite != null) { ++ return ChunkSerializer.getStatus(inProgressWrite); + } ++ // Paper end ++ synchronized (this) { // Paper - async io ++ RegionFile regionFile = this.regionFileCache.getFile(chunkPos, true); ++ ++ if (regionFile == null || !regionFile.hasChunk(chunkPos)) { ++ return null; ++ } + +- ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); + +- if (status != null) { +- return status; ++ if (status != null) { ++ return status; ++ } ++ // Paper start - async io + } + +- this.readChunk(chunkPos); ++ CompoundTag compound = this.readChunk(chunkPos); + +- return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ return ChunkSerializer.getStatus(compound); ++ // Paper end + } + + public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ synchronized (this) { ++ RegionFile regionFile = this.regionFileCache.getFile(chunkPos, false); + +- regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); ++ } + } + + public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { +@@ -1108,6 +1268,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end + ++ ++ // Paper start - async io ++ // this function will not load chunk data off disk to check for status ++ // ret null for unknown, empty for empty status on disk or absent from disk ++ public ChunkStatus getStatusOnDiskNoLoad(int x, int z) { ++ // Paper start - async chunk save for unload ++ ChunkAccess unloadingChunk = this.level.asyncChunkTaskManager.getChunkInSaveProgress(x, z); ++ if (unloadingChunk != null) { ++ return unloadingChunk.getStatus(); ++ } ++ // Paper end ++ // Paper start - async io ++ CompoundTag inProgressWrite = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .getPendingWrite(this.level, x, z, false); ++ ++ if (inProgressWrite != null) { ++ return ChunkSerializer.getStatus(inProgressWrite); ++ } ++ // Paper end ++ // variant of PlayerChunkMap#getChunkStatusOnDisk that does not load data off disk, but loads the region file ++ ChunkPos chunkPos = new ChunkPos(x, z); ++ synchronized (level.getChunkSource().chunkMap) { ++ RegionFile file; ++ try { ++ file = level.getChunkSource().chunkMap.regionFileCache.getFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return !file.hasChunk(chunkPos) ? ChunkStatus.EMPTY : file.getStatusIfCached(x, z); ++ } ++ } ++ + boolean noPlayersCloseForSpawning(ChunkPos chunkcoordintpair) { + // Spigot start + return isOutsideOfRange(chunkcoordintpair, false); +@@ -1454,6 +1647,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + ++ public PoiManager getVillagePlace() { return this.getPoiManager(); } // Paper - OBFHELPER + protected PoiManager getPoiManager() { + return this.poiManager; + } +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index c1aa40c01a80a8870478193b8cd7354b0d71045c..120b604d91643248ab375969f95f62a74cbf6be7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -37,6 +37,7 @@ import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.chunk.ChunkSource; + import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; + import net.minecraft.world.level.storage.DimensionDataStorage; +@@ -332,11 +333,138 @@ public class ServerChunkCache extends ChunkSource { + return playerChunk.getAvailableChunkNow(); + + } ++ ++ private long asyncLoadSeqCounter; ++ ++ public CompletableFuture> getChunkAtAsynchronously(int x, int z, boolean gen, boolean isUrgent) { ++ if (Thread.currentThread() != this.mainThread) { ++ CompletableFuture> future = new CompletableFuture>(); ++ this.mainThreadProcessor.execute(() -> { ++ this.getChunkAtAsynchronously(x, z, gen, isUrgent).whenComplete((chunk, ex) -> { ++ if (ex != null) { ++ future.completeExceptionally(ex); ++ } else { ++ future.complete(chunk); ++ } ++ }); ++ }); ++ return future; ++ } ++ ++ if (!com.destroystokyo.paper.PaperConfig.asyncChunks) { ++ level.getWorld().loadChunk(x, z, gen); ++ LevelChunk chunk = getChunkAtIfLoadedMainThread(x, z); ++ return CompletableFuture.completedFuture(chunk != null ? Either.left(chunk) : ChunkHolder.UNLOADED_CHUNK); ++ } ++ ++ long k = ChunkPos.asLong(x, z); ++ ChunkPos chunkPos = new ChunkPos(x, z); ++ ++ ChunkAccess ichunkaccess; ++ ++ // try cache ++ for (int l = 0; l < 4; ++l) { ++ if (k == this.lastChunkPos[l] && ChunkStatus.FULL == this.lastChunkStatus[l]) { ++ ichunkaccess = this.lastChunk[l]; ++ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime ++ ++ // move to first in cache ++ ++ for (int i1 = 3; i1 > 0; --i1) { ++ this.lastChunkPos[i1] = this.lastChunkPos[i1 - 1]; ++ this.lastChunkStatus[i1] = this.lastChunkStatus[i1 - 1]; ++ this.lastChunk[i1] = this.lastChunk[i1 - 1]; ++ } ++ ++ this.lastChunkPos[0] = k; ++ this.lastChunkStatus[0] = ChunkStatus.FULL; ++ this.lastChunk[0] = ichunkaccess; ++ ++ return CompletableFuture.completedFuture(Either.left(ichunkaccess)); ++ } ++ } ++ } ++ ++ if (gen) { ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ ChunkAccess current = this.getChunkAtImmediately(x, z); // we want to bypass ticket restrictions ++ if (current != null) { ++ if (!(current instanceof ImposterProtoChunk) && !(current instanceof LevelChunk)) { ++ return CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK); ++ } ++ // we know the chunk is at full status here (either in read-only mode or the real thing) ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ ChunkStatus status = level.getChunkSource().chunkMap.getStatusOnDiskNoLoad(x, z); ++ ++ if (status != null && status != ChunkStatus.FULL) { ++ // does not exist on disk ++ return CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK); ++ } ++ ++ if (status == ChunkStatus.FULL) { ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ // status is null here ++ ++ // here we don't know what status it is and we're not supposed to generate ++ // so we asynchronously load empty status ++ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.EMPTY, isUrgent).thenCompose((either) -> { ++ ChunkAccess chunk = either.left().orElse(null); ++ if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof LevelChunk)) { ++ // the chunk on disk was not a full status chunk ++ return CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK); ++ } ++ ; // bring to full status if required ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ }); ++ } ++ ++ private CompletableFuture> bringToFullStatusAsync(int x, int z, ChunkPos chunkPos, boolean isUrgent) { ++ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.FULL, isUrgent); ++ } ++ ++ private CompletableFuture> bringToStatusAsync(int x, int z, ChunkPos chunkPos, ChunkStatus status, boolean isUrgent) { ++ CompletableFuture> future = this.getChunkFutureMainThread(x, z, status, true, isUrgent); ++ Long identifier = Long.valueOf(this.asyncLoadSeqCounter++); ++ int ticketLevel = MCUtil.getTicketLevelFor(status); ++ this.addTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier); ++ ++ return future.thenComposeAsync((Either either) -> { ++ // either left -> success ++ // either right -> failure ++ ++ this.removeTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier); ++ this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); // allow unloading ++ ++ Optional failure = either.right(); ++ ++ if (failure.isPresent()) { ++ // failure ++ throw new IllegalStateException("Chunk failed to load: " + failure.get().toString()); ++ } ++ ++ return CompletableFuture.completedFuture(either); ++ }, this.mainThreadProcessor); ++ } ++ ++ public void addTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { ++ this.distanceManager.addTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); ++ } ++ ++ public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { ++ this.distanceManager.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); ++ } + // Paper end + + @Nullable + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { ++ final int x1 = x; final int z1 = z; // Paper - conflict on variable change + if (Thread.currentThread() != this.mainThread) { + return (ChunkAccess) CompletableFuture.supplyAsync(() -> { + return this.getChunk(x, z, leastStatus, create); +@@ -359,11 +487,16 @@ public class ServerChunkCache extends ChunkSource { + } + + gameprofilerfiller.incrementCounter("getChunkCacheMiss"); +- CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); ++ CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper + + if (!completablefuture.isDone()) { // Paper ++ // Paper start - async chunk io/loading ++ this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); ++ // Paper end + this.level.timings.syncChunkLoad.startTiming(); // Paper + this.mainThreadProcessor.managedBlock(completablefuture::isDone); ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug + this.level.timings.syncChunkLoad.stopTiming(); // Paper + } // Paper + ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { +@@ -429,9 +562,14 @@ public class ServerChunkCache extends ChunkSource { + } + + private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { +- ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ); ++ // Paper start - add isUrgent - old sig left in place for dirty nms plugins ++ return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); ++ } ++ private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag, boolean isUrgent) { ++ // Paper end ++ ChunkPos chunkcoordintpair = new ChunkPos(i, j); + long k = chunkcoordintpair.toLong(); +- int l = 33 + ChunkStatus.getDistance(leastStatus); ++ int l = 33 + ChunkStatus.getDistance(chunkstatus); + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); + + // CraftBukkit start - don't add new ticket for currently unloading chunk +@@ -441,7 +579,7 @@ public class ServerChunkCache extends ChunkSource { + ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel()); + currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)); + } +- if (create && !currentlyUnloading) { ++ if (flag && !currentlyUnloading) { + // CraftBukkit end + this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); + if (this.chunkAbsent(playerchunk, l)) { +@@ -457,7 +595,7 @@ public class ServerChunkCache extends ChunkSource { + } + } + +- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); ++ return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); + } + + private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { +@@ -831,11 +969,12 @@ public class ServerChunkCache extends ChunkSource { + protected boolean pollTask() { + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task + try { ++ boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; + } else { + ServerChunkCache.this.lightEngine.tryScheduleUpdate(); +- return super.pollTask(); ++ return super.pollTask() || execChunkTask; // Paper + } + } finally { + chunkMap.callbackExecutor.run(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a811ced17721b70bb51837f47e466c2261db2466..95eff4f6165024d21e5c4268a9ae1b7a4268de4b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -51,6 +51,7 @@ import net.minecraft.core.RegistryAccess; + import net.minecraft.core.SectionPos; + import net.minecraft.core.Vec3i; + import net.minecraft.core.particles.ParticleOptions; ++import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.Packet; +@@ -122,6 +123,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.storage.RegionFile; + import net.minecraft.world.level.dimension.DimensionType; + import net.minecraft.world.level.dimension.end.EndDragonFight; + import net.minecraft.world.level.levelgen.Heightmap; +@@ -202,6 +204,79 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return this.chunkSource.getChunk(x, z, false); + } + ++ // Paper start - Asynchronous IO ++ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController poiDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() { ++ @Override ++ public void writeData(int x, int z, CompoundTag compound) throws java.io.IOException { ++ ServerLevel.this.getChunkSource().chunkMap.getVillagePlace().write(new ChunkPos(x, z), compound); ++ } ++ ++ @Override ++ public CompoundTag readData(int x, int z) throws java.io.IOException { ++ return ServerLevel.this.getChunkSource().chunkMap.getVillagePlace().read(new ChunkPos(x, z)); ++ } ++ ++ @Override ++ public T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (ServerLevel.this.getChunkSource().chunkMap.getVillagePlace()) { ++ RegionFile file; ++ ++ try { ++ file = ServerLevel.this.getChunkSource().chunkMap.getVillagePlace().getFile(new ChunkPos(chunkX, chunkZ), false); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return function.apply(file); ++ } ++ } ++ ++ @Override ++ public T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (ServerLevel.this.getChunkSource().chunkMap.getVillagePlace()) { ++ RegionFile file = ServerLevel.this.getChunkSource().chunkMap.getVillagePlace().getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ)); ++ return function.apply(file); ++ } ++ } ++ }; ++ ++ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController chunkDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() { ++ @Override ++ public void writeData(int x, int z, CompoundTag compound) throws java.io.IOException { ++ ServerLevel.this.getChunkSource().chunkMap.write(new ChunkPos(x, z), compound); ++ } ++ ++ @Override ++ public CompoundTag readData(int x, int z) throws java.io.IOException { ++ return ServerLevel.this.getChunkSource().chunkMap.read(new ChunkPos(x, z)); ++ } ++ ++ @Override ++ public T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (ServerLevel.this.getChunkSource().chunkMap) { ++ RegionFile file; ++ ++ try { ++ file = ServerLevel.this.getChunkSource().chunkMap.regionFileCache.getFile(new ChunkPos(chunkX, chunkZ), false); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return function.apply(file); ++ } ++ } ++ ++ @Override ++ public T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (ServerLevel.this.getChunkSource().chunkMap) { ++ RegionFile file = ServerLevel.this.getChunkSource().chunkMap.regionFileCache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ)); ++ return function.apply(file); ++ } ++ } ++ }; ++ public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; ++ // Paper end ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -249,6 +324,8 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.dragonFight = null; + } + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit ++ ++ this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper + } + + // CraftBukkit start +@@ -1737,7 +1814,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + MCUtil.getSpiralOutChunks(spawn, radiusInBlocks >> 4).forEach(pair -> { +- getChunkSource().getChunkAtMainThread(pair.x, pair.z); ++ getChunkSource().getChunkAtAsynchronously(pair.x, pair.z, true, false).exceptionally((ex) -> { ++ ex.printStackTrace(); ++ return null; ++ }); + }); + } + public void removeTicketsForSpawn(int radiusInBlocks, BlockPos spawn) { +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index cf3ced15c9a87e7a4dbccba17c57a7b32b77566c..d09e4857b6c40410d134fa81b48e95919a7373bd 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -26,6 +26,7 @@ public class TicketType { + public static final TicketType PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper ++ public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper + + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 4f99c3d06e3b994708c699395adf481a6828e097..5dd99709d6b0ed15bbcee184fe33a28bc1c19dac 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -728,6 +728,13 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper + return; + } ++ // Paper start ++ String str = packet.getCommand(); int index = -1; ++ if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ return; ++ } ++ // Paper end + // CraftBukkit end + StringReader stringreader = new StringReader(packet.getCommand()); + +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index e48fcfe2e4ff151258ae1d84cc0995d2cd54e9a6..a5ce61be7d6e85ac289730d9671e66a7190529f9 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -91,7 +91,7 @@ public abstract class BlockableEventLoop implements Processo + + } + +- protected void runAllTasks() { ++ public void runAllTasks() { // Paper - protected -> public + while (this.pollTask()) { + ; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index 33a8604fa6c6431ccc5f61e484c163e09f1625a0..d082af8cf4c0c7ca434598aa370712c62e05bb24 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -22,7 +22,9 @@ import java.util.stream.Stream; + import net.minecraft.Util; + import net.minecraft.core.BlockPos; + import net.minecraft.core.SectionPos; ++import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.level.SectionTracker; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LevelReader; +@@ -36,8 +38,16 @@ public class PoiManager extends SectionStorage { + private final PoiManager.DistanceTracker distanceTracker = new PoiManager.DistanceTracker(); + private final LongSet loadedChunks = new LongOpenHashSet(); + ++ private final ServerLevel world; // Paper ++ + public PoiManager(File directory, DataFixer datafixer, boolean flag) { +- super(directory, PoiSection::codec, PoiSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); ++ // Paper start - add world parameter ++ this(directory, datafixer, flag, null); ++ } ++ public PoiManager(File file, DataFixer datafixer, boolean flag, ServerLevel world) { ++ super(file, PoiSection::codec, PoiSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); ++ this.world = world; ++ // Paper end - add world parameter + } + + public void add(BlockPos pos, PoiType type) { +@@ -155,7 +165,23 @@ public class PoiManager extends SectionStorage { + + @Override + public void tick(BooleanSupplier shouldKeepTicking) { +- super.tick(shouldKeepTicking); ++ // Paper start - async chunk io ++ if (this.world == null) { ++ super.tick(shouldKeepTicking); ++ } else { ++ //super.a(booleansupplier); // re-implement below ++ while (!((SectionStorage)this).dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) { ++ ChunkPos chunkcoordintpair = SectionPos.of(((SectionStorage)this).dirty.firstLong()).chunk(); ++ ++ CompoundTag data; ++ try (co.aikar.timings.Timing ignored1 = this.world.timings.poiSaveDataSerialization.startTiming()) { ++ data = this.getData(chunkcoordintpair); ++ } ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, ++ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ } ++ } ++ // Paper end + this.distanceTracker.runAllUpdates(); + } + +@@ -255,6 +281,35 @@ public class PoiManager extends SectionStorage { + } + } + ++ // Paper start - Asynchronous chunk io ++ @javax.annotation.Nullable ++ @Override ++ public CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException { ++ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), ++ true, false, true).join().poiData; ++ ++ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) { ++ throw new java.io.IOException("See logs for further detail"); ++ } ++ return ret; ++ } ++ return super.read(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws java.io.IOException { ++ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( ++ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, ++ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ return; ++ } ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + public static enum Occupancy { + + HAS_SPACE(PoiRecord::hasSpace), IS_OCCUPIED(PoiRecord::isOccupied), ANY((villageplacerecord) -> { +diff --git a/src/main/java/net/minecraft/world/level/TickNextTickData.java b/src/main/java/net/minecraft/world/level/TickNextTickData.java +index d97e266b83bb331fcd4031046a5843d29ce53164..90833389022d7412bdda8868a356b84f62a00e03 100644 +--- a/src/main/java/net/minecraft/world/level/TickNextTickData.java ++++ b/src/main/java/net/minecraft/world/level/TickNextTickData.java +@@ -5,7 +5,7 @@ import net.minecraft.core.BlockPos; + + public class TickNextTickData { + +- private static long counter; ++ private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading + private final T type; + public final BlockPos pos; + public final long triggerTick; +@@ -17,7 +17,7 @@ public class TickNextTickData { + } + + public TickNextTickData(BlockPos pos, T t, long time, TickPriority priority) { +- this.c = (long) (TickNextTickData.counter++); ++ this.c = (long) (TickNextTickData.COUNTER.getAndIncrement()); // Paper - async chunk loading + this.pos = pos.immutable(); + this.type = t; + this.triggerTick = time; +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index 46d5a24332c1fd3c164b760ec2a2d5bf859b1ab6..3c85b0d39a3fc5c8ec073d92f48b360c0b0be245 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -170,6 +170,7 @@ public class ChunkStatus { + return ChunkStatus.STATUS_BY_RANGE.size(); + } + ++ public static int getTicketLevelOffset(ChunkStatus status) { return ChunkStatus.getDistance(status); } // Paper - OBFHELPER + public static int getDistance(ChunkStatus status) { + return ChunkStatus.RANGE_BY_STATUS.getInt(status.getIndex()); + } +@@ -185,6 +186,7 @@ public class ChunkStatus { + this.index = previous == null ? 0 : previous.getIndex() + 1; + } + ++ public final int getStatusIndex() { return getIndex(); } // Paper - OBFHELPER + public int getIndex() { + return this.index; + } +@@ -193,7 +195,7 @@ public class ChunkStatus { + return this.name; + } + +- public ChunkStatus getPreviousStatus() { return this.getParent(); } // Paper - OBFHELPER ++ public final ChunkStatus getPreviousStatus() { return this.getParent(); } // Paper - OBFHELPER + public ChunkStatus getParent() { + return this.parent; + } +@@ -206,6 +208,7 @@ public class ChunkStatus { + return this.loadingTask.doWork(this, world, structureManager, lightingProvider, function, chunk); + } + ++ public final int getNeighborRadius() { return this.getRange(); } // Paper - OBFHELPER + public int getRange() { + return this.range; + } +@@ -233,6 +236,7 @@ public class ChunkStatus { + return this.heightmapsAfter; + } + ++ public final boolean isAtLeastStatus(ChunkStatus chunkstatus) { return isOrAfter(chunkstatus); } // Paper - OBFHELPER + public boolean isOrAfter(ChunkStatus chunk) { + return this.getIndex() >= chunk.getIndex(); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +index 808f69a10589a4a7d6c238c05f6d3e0f272681d3..2b798f4e556302f6f79d54182a309f4716a84f04 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +@@ -73,6 +73,7 @@ public class DataLayer { + return this.data; + } + ++ public DataLayer copy() { return this.copy(); } // Paper - OBFHELPER + public DataLayer copy() { + return this.data == null ? new DataLayer() : new DataLayer((byte[]) this.data.clone()); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 8dbd1dc2de400ad0c6c2be49ba09dfc03216ffd2..be67dc16bf70e4517efd213ca9002f116f60b57c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.shorts.ShortList; + import it.unimi.dsi.fastutil.shorts.ShortListIterator; ++import java.util.ArrayDeque; // Paper + import java.util.Arrays; + import java.util.BitSet; + import java.util.EnumSet; +@@ -66,34 +67,58 @@ public class ChunkSerializer { + + private static final Logger LOGGER = LogManager.getLogger(); + ++ // Paper start ++ public static final class InProgressChunkHolder { ++ ++ public final ProtoChunk protoChunk; ++ public final ArrayDeque tasks; ++ ++ public CompoundTag poiData; ++ ++ public InProgressChunkHolder(final ProtoChunk protoChunk, final ArrayDeque tasks) { ++ this.protoChunk = protoChunk; ++ this.tasks = tasks; ++ } ++ } ++ + public static ProtoChunk read(ServerLevel world, StructureManager structureManager, PoiManager poiStorage, ChunkPos pos, CompoundTag tag) { +- ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); ++ InProgressChunkHolder holder = loadChunk(world, structureManager, poiStorage, pos, tag, true); ++ holder.tasks.forEach(Runnable::run); ++ return holder.protoChunk; ++ } ++ ++ public static InProgressChunkHolder loadChunk(ServerLevel worldserver, StructureManager definedstructuremanager, PoiManager villageplace, ChunkPos chunkcoordintpair, CompoundTag nbttagcompound, boolean distinguish) { ++ ArrayDeque tasksToExecuteOnMain = new ArrayDeque<>(); ++ // Paper end ++ ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator(); + BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource(); +- CompoundTag nbttagcompound1 = tag.getCompound("Level"); ++ CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Level"); + ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); + +- if (!Objects.equals(pos, chunkcoordintpair1)) { +- ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", pos, pos, chunkcoordintpair1); ++ if (!Objects.equals(chunkcoordintpair, chunkcoordintpair1)) { ++ ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", chunkcoordintpair, chunkcoordintpair, chunkcoordintpair1); + } + +- ChunkBiomeContainer biomestorage = new ChunkBiomeContainer(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), pos, worldchunkmanager, nbttagcompound1.contains("Biomes", 11) ? nbttagcompound1.getIntArray("Biomes") : null); ++ ChunkBiomeContainer biomestorage = new ChunkBiomeContainer(worldserver.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunkcoordintpair, worldchunkmanager, nbttagcompound1.contains("Biomes", 11) ? nbttagcompound1.getIntArray("Biomes") : null); + UpgradeData chunkconverter = nbttagcompound1.contains("UpgradeData", 10) ? new UpgradeData(nbttagcompound1.getCompound("UpgradeData")) : UpgradeData.EMPTY; + ProtoTickList protochunkticklist = new ProtoTickList<>((block) -> { + return block == null || block.defaultBlockState().isAir(); +- }, pos, nbttagcompound1.getList("ToBeTicked", 9)); ++ }, chunkcoordintpair, nbttagcompound1.getList("ToBeTicked", 9)); + ProtoTickList protochunkticklist1 = new ProtoTickList<>((fluidtype) -> { + return fluidtype == null || fluidtype == Fluids.EMPTY; +- }, pos, nbttagcompound1.getList("LiquidsToBeTicked", 9)); ++ }, chunkcoordintpair, nbttagcompound1.getList("LiquidsToBeTicked", 9)); + boolean flag = nbttagcompound1.getBoolean("isLightOn"); + ListTag nbttaglist = nbttagcompound1.getList("Sections", 10); + boolean flag1 = true; + LevelChunkSection[] achunksection = new LevelChunkSection[16]; +- boolean flag2 = world.dimensionType().hasSkyLight(); +- ServerChunkCache chunkproviderserver = world.getChunkSource(); ++ boolean flag2 = worldserver.dimensionType().hasSkyLight(); ++ ServerChunkCache chunkproviderserver = worldserver.getChunkSource(); + LevelLightEngine lightengine = chunkproviderserver.getLightEngine(); + + if (flag) { +- lightengine.retainData(pos, true); ++ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main ++ lightengine.retainData(chunkcoordintpair, true); ++ }); // Paper - delay this task since we're executing off-main + } + + for (int i = 0; i < nbttaglist.size(); ++i) { +@@ -101,7 +126,7 @@ public class ChunkSerializer { + byte b0 = nbttagcompound2.getByte("Y"); + + if (nbttagcompound2.contains("Palette", 9) && nbttagcompound2.contains("BlockStates", 12)) { +- LevelChunkSection chunksection = new LevelChunkSection(b0 << 4, null, world, false); // Paper - Anti-Xray - Add parameters ++ LevelChunkSection chunksection = new LevelChunkSection(b0 << 4, null, worldserver, false); // Paper - Anti-Xray - Add parameters + + chunksection.getStates().read(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates")); + chunksection.recalcBlockCounts(); +@@ -109,22 +134,34 @@ public class ChunkSerializer { + achunksection[b0] = chunksection; + } + +- poiStorage.checkConsistencyWithBlocks(pos, chunksection); ++ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main ++ villageplace.checkConsistencyWithBlocks(chunkcoordintpair, chunksection); ++ }); // Paper - delay this task since we're executing off-main + } + + if (flag) { + if (nbttagcompound2.contains("BlockLight", 7)) { +- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(pos, b0), new DataLayer(nbttagcompound2.getByteArray("BlockLight")), true); ++ // Paper start - delay this task since we're executing off-main ++ DataLayer blockLight = new DataLayer(nbttagcompound2.getByteArray("BlockLight")); ++ tasksToExecuteOnMain.add(() -> { ++ lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkcoordintpair, b0), blockLight, true); ++ }); ++ // Paper end - delay this task since we're executing off-main + } + + if (flag2 && nbttagcompound2.contains("SkyLight", 7)) { +- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(pos, b0), new DataLayer(nbttagcompound2.getByteArray("SkyLight")), true); ++ // Paper start - delay this task since we're executing off-main ++ DataLayer skyLight = new DataLayer(nbttagcompound2.getByteArray("SkyLight")); ++ tasksToExecuteOnMain.add(() -> { ++ lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkcoordintpair, b0), skyLight, true); ++ }); ++ // Paper end - delay this task since we're executing off-main + } + } + } + + long j = nbttagcompound1.getLong("InhabitedTime"); +- ChunkStatus.ChunkType chunkstatus_type = getChunkTypeFromTag(tag); ++ ChunkStatus.ChunkType chunkstatus_type = getChunkTypeFromTag(nbttagcompound); + Object object; + + if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { +@@ -155,7 +192,7 @@ public class ChunkSerializer { + object2 = protochunkticklist1; + } + +- object = new LevelChunk(world.getLevel(), pos, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, (chunk) -> { ++ object = new LevelChunk(worldserver.getLevel(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, (chunk) -> { + postLoadChunk(nbttagcompound1, chunk); + // CraftBukkit start - load chunk persistent data from nbt + net.minecraft.nbt.Tag persistentBase = nbttagcompound1.get("ChunkBukkitValues"); +@@ -165,7 +202,7 @@ public class ChunkSerializer { + // CraftBukkit end + }); + } else { +- ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world); // Paper - Anti-Xray - Add parameter ++ ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter + + protochunk.setBiomes(biomestorage); + object = protochunk; +@@ -176,7 +213,7 @@ public class ChunkSerializer { + } + + if (!flag && protochunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { +- Iterator iterator = BlockPos.betweenClosed(pos.getMinBlockX(), 0, pos.getMinBlockZ(), pos.getMaxBlockX(), 255, pos.getMaxBlockZ()).iterator(); ++ Iterator iterator = BlockPos.betweenClosed(chunkcoordintpair.getMinBlockX(), 0, chunkcoordintpair.getMinBlockZ(), chunkcoordintpair.getMaxBlockX(), 255, chunkcoordintpair.getMaxBlockZ()).iterator(); + + while (iterator.hasNext()) { + BlockPos blockposition = (BlockPos) iterator.next(); +@@ -207,8 +244,8 @@ public class ChunkSerializer { + Heightmap.primeHeightmaps((ChunkAccess) object, enumset); + CompoundTag nbttagcompound4 = nbttagcompound1.getCompound("Structures"); + +- ((ChunkAccess) object).setAllStarts(unpackStructureStart(structureManager, nbttagcompound4, world.getSeed())); +- ((ChunkAccess) object).setAllReferences(unpackStructureReferences(pos, nbttagcompound4)); ++ ((ChunkAccess) object).setAllStarts(unpackStructureStart(definedstructuremanager, nbttagcompound4, worldserver.getSeed())); ++ ((ChunkAccess) object).setAllReferences(unpackStructureReferences(chunkcoordintpair, nbttagcompound4)); + if (nbttagcompound1.getBoolean("shouldSave")) { + ((ChunkAccess) object).setUnsaved(true); + } +@@ -227,7 +264,7 @@ public class ChunkSerializer { + } + + if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { +- return new ImposterProtoChunk((LevelChunk) object); ++ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object), tasksToExecuteOnMain); // Paper - Async chunk loading + } else { + ProtoChunk protochunk1 = (ProtoChunk) object; + +@@ -266,12 +303,84 @@ public class ChunkSerializer { + protochunk1.setCarvingMask(worldgenstage_features, BitSet.valueOf(nbttagcompound5.getByteArray(s1))); + } + +- return protochunk1; ++ return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading + } + } + ++ // Paper start - async chunk save for unload ++ public static final class AsyncSaveData { ++ public final DataLayer[] blockLight; // null or size of 17 (for indices -1 through 15) ++ public final DataLayer[] skyLight; ++ ++ public final ListTag blockTickList; // non-null if we had to go to the server's tick list ++ public final ListTag fluidTickList; // non-null if we had to go to the server's tick list ++ ++ public final long worldTime; ++ ++ public AsyncSaveData(DataLayer[] blockLight, DataLayer[] skyLight, ListTag blockTickList, ListTag fluidTickList, ++ long worldTime) { ++ this.blockLight = blockLight; ++ this.skyLight = skyLight; ++ this.blockTickList = blockTickList; ++ this.fluidTickList = fluidTickList; ++ this.worldTime = worldTime; ++ } ++ } ++ ++ // must be called sync ++ public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) { ++ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save"); ++ ChunkPos chunkPos = chunk.getPos(); ++ ++ ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine(); ++ ++ DataLayer[] blockLight = new DataLayer[17 - (-1)]; ++ DataLayer[] skyLight = new DataLayer[17 - (-1)]; ++ ++ for (int i = -1; i < 17; ++i) { ++ DataLayer blockArray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i)); ++ DataLayer skyArray = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i)); ++ ++ // copy data for safety ++ if (blockArray != null) { ++ blockArray = blockArray.copy(); ++ } ++ if (skyArray != null) { ++ skyArray = skyArray.copy(); ++ } ++ ++ // apply offset of 1 for -1 starting index ++ blockLight[i + 1] = blockArray; ++ skyLight[i + 1] = skyArray; ++ } ++ ++ TickList blockTickList = chunk.getBlockTicks(); ++ ++ ListTag blockTickListSerialized; ++ if (blockTickList instanceof ProtoTickList || blockTickList instanceof ChunkTickList) { ++ blockTickListSerialized = null; ++ } else { ++ blockTickListSerialized = world.getBlockTicks().save(chunkPos); ++ } ++ ++ TickList fluidTickList = chunk.getLiquidTicks(); ++ ++ ListTag fluidTickListSerialized; ++ if (fluidTickList instanceof ProtoTickList || fluidTickList instanceof ChunkTickList) { ++ fluidTickListSerialized = null; ++ } else { ++ fluidTickListSerialized = world.getLiquidTicks().save(chunkPos); ++ } ++ ++ return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, world.getGameTime()); ++ } ++ + public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { +- ChunkPos chunkcoordintpair = chunk.getPos(); ++ return saveChunk(world, chunk, null); ++ } ++ public static CompoundTag saveChunk(ServerLevel worldserver, ChunkAccess ichunkaccess, AsyncSaveData asyncsavedata) { ++ // Paper end ++ ChunkPos chunkcoordintpair = ichunkaccess.getPos(); + CompoundTag nbttagcompound = new CompoundTag(); + CompoundTag nbttagcompound1 = new CompoundTag(); + +@@ -279,30 +388,38 @@ public class ChunkSerializer { + nbttagcompound.put("Level", nbttagcompound1); + nbttagcompound1.putInt("xPos", chunkcoordintpair.x); + nbttagcompound1.putInt("zPos", chunkcoordintpair.z); +- nbttagcompound1.putLong("LastUpdate", world.getGameTime()); +- nbttagcompound1.putLong("InhabitedTime", chunk.getInhabitedTime()); +- nbttagcompound1.putString("Status", chunk.getStatus().getName()); +- UpgradeData chunkconverter = chunk.getUpgradeData(); ++ nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getGameTime()); // Paper - async chunk unloading ++ nbttagcompound1.putLong("InhabitedTime", ichunkaccess.getInhabitedTime()); ++ nbttagcompound1.putString("Status", ichunkaccess.getStatus().getName()); ++ UpgradeData chunkconverter = ichunkaccess.getUpgradeData(); + + if (!chunkconverter.isEmpty()) { + nbttagcompound1.put("UpgradeData", chunkconverter.write()); + } + +- LevelChunkSection[] achunksection = chunk.getSections(); ++ LevelChunkSection[] achunksection = ichunkaccess.getSections(); + ListTag nbttaglist = new ListTag(); +- ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine(); +- boolean flag = chunk.isLightCorrect(); ++ ThreadedLevelLightEngine lightenginethreaded = worldserver.getChunkSource().getLightEngine(); ++ boolean flag = ichunkaccess.isLightCorrect(); + + CompoundTag nbttagcompound2; + +- for (int i = -1; i < 17; ++i) { ++ for (int i = -1; i < 17; ++i) { // Paper - conflict on loop parameter change + int finalI = i; // CraftBukkit - decompile errors + LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> { + return chunksection1 != null && chunksection1.bottomBlockY() >> 4 == finalI; // CraftBukkit - decompile errors + }).findFirst().orElse(LevelChunk.EMPTY_SECTION); +- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); +- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); +- ++ // Paper start - async chunk save for unload ++ DataLayer nibblearray; // block light ++ DataLayer nibblearray1; // sky light ++ if (asyncsavedata == null) { ++ nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) ++ nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) ++ } else { ++ nibblearray = asyncsavedata.blockLight[i + 1]; // +1 to offset the -1 starting index ++ nibblearray1 = asyncsavedata.skyLight[i + 1]; // +1 to offset the -1 starting index ++ } ++ // Paper end + if (chunksection != LevelChunk.EMPTY_SECTION || nibblearray != null || nibblearray1 != null) { + nbttagcompound2 = new CompoundTag(); + nbttagcompound2.putByte("Y", (byte) (i & 255)); +@@ -327,21 +444,21 @@ public class ChunkSerializer { + nbttagcompound1.putBoolean("isLightOn", true); + } + +- ChunkBiomeContainer biomestorage = chunk.getBiomes(); ++ ChunkBiomeContainer biomestorage = ichunkaccess.getBiomes(); + + if (biomestorage != null) { + nbttagcompound1.putIntArray("Biomes", biomestorage.writeBiomes()); + } + + ListTag nbttaglist1 = new ListTag(); +- Iterator iterator = chunk.getBlockEntitiesPos().iterator(); ++ Iterator iterator = ichunkaccess.getBlockEntitiesPos().iterator(); + + CompoundTag nbttagcompound3; + + while (iterator.hasNext()) { + BlockPos blockposition = (BlockPos) iterator.next(); + +- nbttagcompound3 = chunk.getBlockEntityNbtForSaving(blockposition); ++ nbttagcompound3 = ichunkaccess.getBlockEntityNbtForSaving(blockposition); + if (nbttagcompound3 != null) { + nbttaglist1.add(nbttagcompound3); + } +@@ -351,25 +468,25 @@ public class ChunkSerializer { + ListTag nbttaglist2 = new ListTag(); + + java.util.List toUpdate = new java.util.ArrayList<>(); // Paper +- if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) { +- LevelChunk chunk1 = (LevelChunk) chunk; ++ if (ichunkaccess.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) { ++ LevelChunk chunk = (LevelChunk) ichunkaccess; + + // CraftBukkit start - store chunk persistent data in nbt +- if (!chunk1.persistentDataContainer.isEmpty()) { +- nbttagcompound1.put("ChunkBukkitValues", chunk1.persistentDataContainer.toTagCompound()); ++ if (!chunk.persistentDataContainer.isEmpty()) { ++ nbttagcompound1.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound()); + } + // CraftBukkit end + +- chunk1.setLastSaveHadEntities(false); ++ chunk.setLastSaveHadEntities(false); + +- for (int j = 0; j < chunk1.getEntitySlices().length; ++j) { +- Iterator iterator1 = chunk1.getEntitySlices()[j].iterator(); ++ for (int j = 0; j < chunk.getEntitySlices().length; ++j) { ++ Iterator iterator1 = chunk.getEntitySlices()[j].iterator(); + + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); + CompoundTag nbttagcompound4 = new CompoundTag(); + // Paper start +- if ((int) Math.floor(entity.getX()) >> 4 != chunk1.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk1.getPos().z) { ++ if (asyncsavedata == null && !entity.removed && (int) Math.floor(entity.getX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk.getPos().z) { + toUpdate.add(entity); + continue; + } +@@ -378,7 +495,7 @@ public class ChunkSerializer { + } + // Paper end + if (entity.save(nbttagcompound4)) { +- chunk1.setLastSaveHadEntities(true); ++ chunk.setLastSaveHadEntities(true); + nbttaglist2.add(nbttagcompound4); + } + } +@@ -386,12 +503,12 @@ public class ChunkSerializer { + + // Paper start - move entities to the correct chunk + for (Entity entity : toUpdate) { +- world.updateChunkPos(entity); ++ worldserver.updateChunkPos(entity); + } + // Paper end + + } else { +- ProtoChunk protochunk = (ProtoChunk) chunk; ++ ProtoChunk protochunk = (ProtoChunk) ichunkaccess; + + nbttaglist2.addAll(protochunk.getEntities()); + nbttagcompound1.put("Lights", packOffsets(protochunk.getPackedLights())); +@@ -412,40 +529,48 @@ public class ChunkSerializer { + } + + nbttagcompound1.put("Entities", nbttaglist2); +- TickList ticklist = chunk.getBlockTicks(); ++ TickList ticklist = ichunkaccess.getBlockTicks(); // Paper - diff on method change (see getAsyncSaveData) + + if (ticklist instanceof ProtoTickList) { + nbttagcompound1.put("ToBeTicked", ((ProtoTickList) ticklist).save()); + } else if (ticklist instanceof ChunkTickList) { + nbttagcompound1.put("TileTicks", ((ChunkTickList) ticklist).save()); ++ // Paper start - async chunk save for unload ++ } else if (asyncsavedata != null) { ++ nbttagcompound1.put("TileTicks", asyncsavedata.blockTickList); ++ // Paper end + } else { +- nbttagcompound1.put("TileTicks", world.getBlockTicks().save(chunkcoordintpair)); ++ nbttagcompound1.put("TileTicks", worldserver.getBlockTicks().save(chunkcoordintpair)); // Paper - diff on method change (see getAsyncSaveData) + } + +- TickList ticklist1 = chunk.getLiquidTicks(); ++ TickList ticklist1 = ichunkaccess.getLiquidTicks(); // Paper - diff on method change (see getAsyncSaveData) + + if (ticklist1 instanceof ProtoTickList) { + nbttagcompound1.put("LiquidsToBeTicked", ((ProtoTickList) ticklist1).save()); + } else if (ticklist1 instanceof ChunkTickList) { + nbttagcompound1.put("LiquidTicks", ((ChunkTickList) ticklist1).save()); ++ // Paper start - async chunk save for unload ++ } else if (asyncsavedata != null) { ++ nbttagcompound1.put("LiquidTicks", asyncsavedata.fluidTickList); ++ // Paper end + } else { +- nbttagcompound1.put("LiquidTicks", world.getLiquidTicks().save(chunkcoordintpair)); ++ nbttagcompound1.put("LiquidTicks", worldserver.getLiquidTicks().save(chunkcoordintpair)); // Paper - diff on method change (see getAsyncSaveData) + } + +- nbttagcompound1.put("PostProcessing", packOffsets(chunk.getPostProcessing())); ++ nbttagcompound1.put("PostProcessing", packOffsets(ichunkaccess.getPostProcessing())); + nbttagcompound2 = new CompoundTag(); +- Iterator iterator2 = chunk.getHeightmaps().iterator(); ++ Iterator iterator2 = ichunkaccess.getHeightmaps().iterator(); + + while (iterator2.hasNext()) { + Entry entry = (Entry) iterator2.next(); + +- if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) { ++ if (ichunkaccess.getStatus().heightmapsAfter().contains(entry.getKey())) { + nbttagcompound2.put(((Heightmap.Types) entry.getKey()).getSerializationKey(), new LongArrayTag(((Heightmap) entry.getValue()).getRawData())); + } + } + + nbttagcompound1.put("Heightmaps", nbttagcompound2); +- nbttagcompound1.put("Structures", packStructureData(chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences())); ++ nbttagcompound1.put("Structures", packStructureData(chunkcoordintpair, ichunkaccess.getAllStarts(), ichunkaccess.getAllReferences())); + return nbttagcompound; + } + // Paper start - this is saved with the player +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 9cffef2098fbfba89ddd88a45bde33c07660497a..684442b7175e30b6d4cafb2f7d2d4c10517cc33d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -3,6 +3,10 @@ package net.minecraft.world.level.chunk.storage; + import com.mojang.datafixers.DataFixer; + import java.io.File; + import java.io.IOException; ++// Paper start ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++// Paper end + import java.util.function.Supplier; + import javax.annotation.Nullable; + import net.minecraft.SharedConstants; +@@ -21,32 +25,41 @@ import net.minecraft.world.level.storage.DimensionDataStorage; + + public class ChunkStorage implements AutoCloseable { + +- private final IOWorker worker; public IOWorker getIOWorker() { return worker; } // Paper - OBFHELPER ++ // Paper - OBFHELPER - nuke IOWorker + protected final DataFixer fixerUpper; + @Nullable +- private LegacyStructureDataHandler legacyStructureHandler; ++ private volatile LegacyStructureDataHandler legacyStructureHandler; // Paper - async chunk loading ++ ++ private final Object persistentDataLock = new Object(); // Paper ++ public final RegionFileStorage regionFileCache; + + public ChunkStorage(File file, DataFixer datafixer, boolean flag) { ++ this.regionFileCache = new RegionFileStorage(file, flag); // Paper - nuke IOWorker + this.fixerUpper = datafixer; +- this.worker = new IOWorker(file, flag, "chunk"); ++ // Paper - nuke IOWorker + } + + // CraftBukkit start + private boolean check(ServerChunkCache cps, int x, int z) throws IOException { + ChunkPos pos = new ChunkPos(x, z); + if (cps != null) { +- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); +- if (cps.hasChunk(x, z)) { ++ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe ++ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // Paper - isLoaded is a ticket level check, not a chunk loaded check! + return true; + } + } + +- CompoundTag nbt = read(pos); +- if (nbt != null) { +- CompoundTag level = nbt.getCompound("Level"); +- if (level.getBoolean("TerrainPopulated")) { +- return true; +- } ++ ++ // Paper start - prioritize ++ CompoundTag nbt = cps == null ? read(pos) : ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.loadChunkData((ServerLevel)cps.getLevel(), x, z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHER_PRIORITY, false, true).chunkData; ++ // Paper end ++ if (nbt != null) { ++ CompoundTag level = nbt.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated")) { ++ return true; ++ } + + ChunkStatus status = ChunkStatus.byName(level.getString("Status")); + if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) { +@@ -77,11 +90,13 @@ public class ChunkStorage implements AutoCloseable { + if (i < 1493) { + nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, i, 1493); + if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { ++ synchronized (this.persistentDataLock) { // Paper - Async chunk loading + if (this.legacyStructureHandler == null) { + this.legacyStructureHandler = LegacyStructureDataHandler.getLegacyStructureHandler(resourcekey, (DimensionDataStorage) supplier.get()); + } + + nbttagcompound = this.legacyStructureHandler.updateFromLegacy(nbttagcompound); ++ } // Paper - Async chunk loading + } + } + +@@ -99,22 +114,20 @@ public class ChunkStorage implements AutoCloseable { + + @Nullable + public CompoundTag read(ChunkPos chunkcoordintpair) throws IOException { +- return this.worker.load(chunkcoordintpair); ++ return this.regionFileCache.read(chunkcoordintpair); + } + +- public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) { +- this.worker.store(chunkcoordintpair, nbttagcompound); ++ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { write(chunkcoordintpair, nbttagcompound); } // Paper OBFHELPER ++ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { // Paper - OBFHELPER - (Switched around for safety) ++ this.regionFileCache.write(chunkcoordintpair, nbttagcompound); + if (this.legacyStructureHandler != null) { ++ synchronized (this.persistentDataLock) { // Paper - Async chunk loading + this.legacyStructureHandler.removeIndex(chunkcoordintpair.toLong()); ++ } // Paper - Async chunk loading} + } +- +- } +- +- public void flushWorker() { +- this.worker.synchronize().join(); + } + + public void close() throws IOException { +- this.worker.close(); ++ this.regionFileCache.close(); + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 4d96e5ed28c910387c0a4238c9036c7a12458f57..7ecde2cb15fa0b1b5195fc560c559f2c367e336f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -45,6 +45,8 @@ public class RegionFile implements AutoCloseable { + protected final RegionBitmap usedSectors; + public final File file; // Paper + ++ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper ++ + // Paper start - Cache chunk status + private final ChunkStatus[] statuses = new ChunkStatus[32 * 32]; + +@@ -251,7 +253,7 @@ public class RegionFile implements AutoCloseable { + return (byteCount + 4096 - 1) / 4096; + } + +- public boolean doesChunkExist(ChunkPos pos) { ++ public synchronized boolean doesChunkExist(ChunkPos pos) { // Paper - synchronized + int i = this.getOffset(pos); + + if (i == 0) { +@@ -411,6 +413,11 @@ public class RegionFile implements AutoCloseable { + } + + public void close() throws IOException { ++ // Paper start - Prevent regionfiles from being closed during use ++ this.fileLock.lock(); ++ synchronized (this) { ++ try { ++ // Paper end + this.closed = true; // Paper + try { + this.padToFullSector(); +@@ -421,6 +428,10 @@ public class RegionFile implements AutoCloseable { + this.file.close(); + } + } ++ } finally { // Paper start - Prevent regionfiles from being closed during use ++ this.fileLock.unlock(); ++ } ++ } // Paper end + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 6f1c96e4325caf6b4762700ad2286d9ea41515c9..0498982ac14f20145d68dbf64a46bcaacf5516ef 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -17,7 +17,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionCollector; + import net.minecraft.world.level.ChunkPos; + +-public final class RegionFileStorage implements AutoCloseable { ++public class RegionFileStorage implements AutoCloseable { // Paper - no final + + public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); + private final File folder; +@@ -30,16 +30,27 @@ public final class RegionFileStorage implements AutoCloseable { + + + // Paper start +- public RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { ++ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { // Paper - synchronize for async io + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); + } + + // Paper end +- public RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public ++ public synchronized RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public, synchronize ++ // Paper start - add lock parameter ++ return this.getFile(chunkcoordintpair, existingOnly, false); ++ } ++ public synchronized RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { ++ // Paper end + long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); + + if (regionfile != null) { ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile.fileLock.lock(); ++ } ++ // Paper end + return regionfile; + } else { + if (this.regionCache.size() >= com.destroystokyo.paper.PaperConfig.regionFileCacheSize) { // Paper - configurable +@@ -55,6 +66,12 @@ public final class RegionFileStorage implements AutoCloseable { + RegionFile regionfile1 = new RegionFile(file, this.folder, this.sync); + + this.regionCache.putAndMoveToFirst(i, regionfile1); ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile1.fileLock.lock(); ++ } ++ // Paper end + return regionfile1; + } + } +@@ -130,11 +147,12 @@ public final class RegionFileStorage implements AutoCloseable { + @Nullable + public CompoundTag read(ChunkPos pos) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getFile(pos, true); ++ RegionFile regionfile = this.getFile(pos, true, true); // Paper + if (regionfile == null) { + return null; + } + // CraftBukkit end ++ try { // Paper + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); + // Paper start + if (regionfile.isOversized(pos.x, pos.z)) { +@@ -172,10 +190,14 @@ public final class RegionFileStorage implements AutoCloseable { + } + + return nbttagcompound; ++ } finally { // Paper start ++ regionfile.fileLock.unlock(); ++ } // Paper end + } + + protected void write(ChunkPos pos, CompoundTag tag) throws IOException { +- RegionFile regionfile = this.getFile(pos, false); // CraftBukkit ++ RegionFile regionfile = this.getFile(pos, false, true); // CraftBukkit // Paper ++ try { // Paper + int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper + DataOutputStream dataoutputstream = regionfile.getChunkDataOutputStream(pos); + Throwable throwable = null; +@@ -214,9 +236,12 @@ public final class RegionFileStorage implements AutoCloseable { + MinecraftServer.LOGGER.error("Failed to save chunk", laste); + } + // Paper end ++ } finally { // Paper start ++ regionfile.fileLock.unlock(); ++ } // Paper end + } + +- public void close() throws IOException { ++ public synchronized void close() throws IOException { // Paper -> synchronized + ExceptionCollector exceptionsuppressor = new ExceptionCollector<>(); + ObjectIterator objectiterator = this.regionCache.values().iterator(); + +@@ -243,4 +268,12 @@ public final class RegionFileStorage implements AutoCloseable { + } + + } ++ ++ // CraftBukkit start ++ public synchronized boolean chunkExists(ChunkPos pos) throws IOException { // Paper - synchronize ++ RegionFile regionfile = getFile(pos, true); ++ ++ return regionfile != null ? regionfile.hasChunk(pos) : false; ++ } ++ // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 059a658aa87d19025daa66d98f78112d5f5be4e3..bb30fb085a6c5edb717ad006c0ab481723ca1b6b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -30,28 +30,29 @@ import net.minecraft.world.level.Level; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +-public class SectionStorage implements AutoCloseable { ++public class SectionStorage extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker + + private static final Logger LOGGER = LogManager.getLogger(); +- private final IOWorker worker; ++ // Paper - nuke IOWorker + private final Long2ObjectMap> storage = new Long2ObjectOpenHashMap(); +- private final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet(); ++ public final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet(); // Paper - private -> public + private final Function> codec; + private final Function factory; + private final DataFixer fixerUpper; + private final DataFixTypes type; + + public SectionStorage(File directory, Function> codecFactory, Function factory, DataFixer datafixer, DataFixTypes datafixtypes, boolean flag) { ++ super(directory, flag); // Paper - nuke IOWorker + this.codec = codecFactory; + this.factory = factory; + this.fixerUpper = datafixer; + this.type = datafixtypes; +- this.worker = new IOWorker(directory, flag, directory.getName()); ++ //this.b = new IOWorker(file, flag, file.getName()); // Paper - nuke IOWorker + } + + protected void tick(BooleanSupplier shouldKeepTicking) { + while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) { +- ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk(); ++ ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk(); // Paper - conflict here to avoid obfhelpers + + this.writeColumn(chunkcoordintpair); + } +@@ -105,13 +106,18 @@ public class SectionStorage implements AutoCloseable { + } + + private void readColumn(ChunkPos chunkcoordintpair) { +- this.readColumn(chunkcoordintpair, NbtOps.INSTANCE, this.tryRead(chunkcoordintpair)); ++ // Paper start - load data in function ++ this.loadInData(chunkcoordintpair, this.tryRead(chunkcoordintpair)); ++ } ++ public void loadInData(ChunkPos chunkPos, CompoundTag compound) { ++ this.readColumn(chunkPos, NbtOps.INSTANCE, compound); ++ // Paper end + } + + @Nullable + private CompoundTag tryRead(ChunkPos pos) { + try { +- return this.worker.load(pos); ++ return this.read(pos); // Paper - nuke IOWorker + } catch (IOException ioexception) { + SectionStorage.LOGGER.error("Error reading chunk {} data from disk", pos, ioexception); + return null; +@@ -157,17 +163,31 @@ public class SectionStorage implements AutoCloseable { + } + + private void writeColumn(ChunkPos chunkcoordintpair) { +- Dynamic dynamic = this.writeColumn(chunkcoordintpair, NbtOps.INSTANCE); ++ Dynamic dynamic = this.writeColumn(chunkcoordintpair, NbtOps.INSTANCE); // Paper - conflict here to avoid adding obfhelpers :) + Tag nbtbase = (Tag) dynamic.getValue(); + + if (nbtbase instanceof CompoundTag) { +- this.worker.store(chunkcoordintpair, (CompoundTag) nbtbase); ++ try { this.write(chunkcoordintpair, (CompoundTag) nbtbase); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker // TODO make this write async + } else { + SectionStorage.LOGGER.error("Expected compound tag, got {}", nbtbase); + } + + } + ++ // Paper start - internal get data function, copied from above ++ private CompoundTag getDataInternal(ChunkPos chunkcoordintpair) { ++ Dynamic dynamic = this.writeColumn(chunkcoordintpair, NbtOps.INSTANCE); ++ Tag nbtbase = (Tag) dynamic.getValue(); ++ ++ if (nbtbase instanceof CompoundTag) { ++ return (CompoundTag)nbtbase; ++ } else { ++ SectionStorage.LOGGER.error("Expected compound tag, got {}", nbtbase); ++ } ++ return null; ++ } ++ // Paper end ++ + private Dynamic writeColumn(ChunkPos chunkcoordintpair, DynamicOps dynamicops) { + Map map = Maps.newHashMap(); + +@@ -213,9 +233,9 @@ public class SectionStorage implements AutoCloseable { + public void flush(ChunkPos chunkcoordintpair) { + if (!this.dirty.isEmpty()) { + for (int i = 0; i < 16; ++i) { +- long j = SectionPos.of(chunkcoordintpair, i).asLong(); ++ long j = SectionPos.of(chunkcoordintpair, i).asLong(); // Paper - conflict here to avoid obfhelpers + +- if (this.dirty.contains(j)) { ++ if (this.dirty.contains(j)) { // Paper - conflict here to avoid obfhelpers + this.writeColumn(chunkcoordintpair); + return; + } +@@ -224,7 +244,26 @@ public class SectionStorage implements AutoCloseable { + + } + +- public void close() throws IOException { +- this.worker.close(); ++// Paper start - nuke IOWorker ++// public void close() throws IOException { ++// this.b.close(); ++// } ++// Paper end ++ ++ // Paper start - get data function ++ public CompoundTag getData(ChunkPos chunkcoordintpair) { ++ // Note: Copied from above ++ // This is checking if the data exists, then it builds it later in getDataInternal(ChunkCoordIntPair) ++ if (!this.dirty.isEmpty()) { ++ for (int i = 0; i < 16; ++i) { ++ long j = SectionPos.of(chunkcoordintpair, i).asLong(); ++ ++ if (this.dirty.contains(j)) { ++ return this.getDataInternal(chunkcoordintpair); ++ } ++ } ++ } ++ return null; + } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a0615e4ba015cca4fe074de63b87d0bff84b1a14..52444619a4bae80a12bf296fbe07fa811adf806e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -545,22 +545,23 @@ public class CraftWorld implements World { + return true; + } + +- net.minecraft.world.level.chunk.storage.RegionFile file; +- try { +- file = world.getChunkSource().chunkMap.getIOWorker().getRegionFileCache().getFile(chunkPos, false); +- } catch (IOException ex) { +- throw new RuntimeException(ex); +- } ++ ChunkStatus status = world.getChunkSource().chunkMap.getStatusOnDiskNoLoad(x, z); // Paper - async io - move to own method + +- ChunkStatus status = file.getStatusIfCached(x, z); +- if (!file.hasChunk(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ // Paper start - async io ++ if (status == ChunkStatus.EMPTY) { ++ // does not exist on disk + return false; + } + ++ if (status == null) { // at this stage we don't know what it is on disk + ChunkAccess chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.EMPTY, true); + if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof net.minecraft.world.level.chunk.LevelChunk)) { + return false; + } ++ } else if (status != ChunkStatus.FULL) { ++ return false; // not full status on disk ++ } ++ // Paper end + + // fall through to load + // we do this so we do not re-read the chunk data on disk +@@ -2483,6 +2484,34 @@ public class CraftWorld implements World { + public DragonBattle getEnderDragonBattle() { + return (getHandle().dragonFight() == null) ? null : new CraftDragonBattle(getHandle().dragonFight()); + } ++ // Paper start ++ @Override ++ public CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { ++ if (Bukkit.isPrimaryThread()) { ++ net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (immediate != null) { ++ return CompletableFuture.completedFuture(immediate.getBukkitChunk()); ++ } ++ } else { ++ CompletableFuture future = new CompletableFuture(); ++ world.getServer().execute(() -> { ++ getChunkAtAsync(x, z, gen, urgent).whenComplete((chunk, err) -> { ++ if (err != null) { ++ future.completeExceptionally(err); ++ } else { ++ future.complete(chunk); ++ } ++ }); ++ }); ++ return future; ++ } ++ ++ return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); ++ return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); ++ }, net.minecraft.server.MinecraftServer.getServer()); ++ } ++ // Paper end + + // Spigot start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 7ad4fb57af32cc1b8278688381e1b058ed8437db..76d652386806fd11961611486a1d0a12fe9616a4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -11,7 +11,9 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.Component; ++import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.TicketType; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.AreaEffectCloud; + import net.minecraft.world.entity.Entity; +@@ -508,6 +510,28 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + entity.setYHeadRot(yaw); + } + ++ @Override// Paper start ++ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ ChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkSource().chunkMap; ++ java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); ++ ++ loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { ++ ChunkCoordIntPair pair = new ChunkCoordIntPair(chunk.getX(), chunk.getZ()); ++ ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().addTicketAtLevel(TicketType.POST_TELEPORT, pair, 31, 0); ++ PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pair.pair()); ++ if (updatingChunk != null) { ++ return updatingChunk.getEntityTickingFuture(); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); ++ } ++ }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> { ++ future.completeExceptionally(ex); ++ return null; ++ }); ++ return future; ++ } ++ // Paper end ++ + @Override + public boolean teleport(Location location) { + return teleport(location, TeleportCause.PLUGIN); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 16f6163bb53e73aa4ab6e22365342613b6b38118..33a66322d253c7562ae5acbdbc6cc87f7d72a9af 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -6,6 +6,7 @@ import java.lang.management.ThreadInfo; + import java.util.logging.Level; + import java.util.logging.Logger; + import com.destroystokyo.paper.PaperConfig; ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; // Paper + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; + +@@ -116,6 +117,7 @@ public class WatchdogThread extends Thread + // Paper end - Different message for short timeout + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper ++ ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/Remapped-Spigot-Server-Patches/0369-Use-getChunkIfLoadedImmediately-in-places.patch b/Remapped-Spigot-Server-Patches/0369-Use-getChunkIfLoadedImmediately-in-places.patch new file mode 100644 index 000000000..3becb4b6e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0369-Use-getChunkIfLoadedImmediately-in-places.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 8 Jul 2019 00:13:36 -0700 +Subject: [PATCH] Use getChunkIfLoadedImmediately in places + +This prevents us from hitting chunk loads for chunks at or less-than +ticket level 33 (yes getChunkIfLoaded will actually perform a chunk +load in that case). + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 95eff4f6165024d21e5c4268a9ae1b7a4268de4b..9d4c81a4964317a0726171dc6d490d12bd959cc4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -201,7 +201,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI +- return this.chunkSource.getChunk(x, z, false); ++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper + } + + // Paper start - Asynchronous IO +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5dd99709d6b0ed15bbcee184fe33a28bc1c19dac..e45690b6197335ed1c07fa04c39b311b401724d7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1242,7 +1242,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + speed = player.abilities.walkingSpeed * 10f; + } + // Paper start - Prevent moving into unloaded chunks +- if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.hasChunk((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) { ++ if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately + this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.yRot, this.player.xRot, Collections.emptySet()); + return; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index d082af8cf4c0c7ca434598aa370712c62e05bb24..b9d32e3322c2cce1aca2a90df71b6175a6f8c548 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -284,10 +284,10 @@ public class PoiManager extends SectionStorage { + // Paper start - Asynchronous chunk io + @javax.annotation.Nullable + @Override +- public CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException { ++ public CompoundTag read(ChunkPos pos) throws java.io.IOException { + if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { + CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE +- .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), ++ .loadChunkDataAsyncFuture(this.world, pos.x, pos.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), + true, false, true).join().poiData; + + if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) { +@@ -295,18 +295,18 @@ public class PoiManager extends SectionStorage { + } + return ret; + } +- return super.read(chunkcoordintpair); ++ return super.read(pos); + } + + @Override +- public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws java.io.IOException { ++ public void write(ChunkPos pos, CompoundTag tag) throws java.io.IOException { + if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( +- this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, ++ this.world, pos.x, pos.z, tag, null, + com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); + return; + } +- super.write(chunkcoordintpair, nbttagcompound); ++ super.write(pos, tag); + } + // Paper end + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 1377465e3dc062f34be25cac10aa018776fb22e7..3a1b9f1ba19b28cebdafeb3b2476217d47213862 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -164,6 +164,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return (CraftServer) Bukkit.getServer(); + } + ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return ((ServerLevel)this).getChunkIfLoaded(chunkX, chunkZ) != null; ++ } ++ // Paper end ++ + public ResourceKey getTypeKey() { + return typeKey; + } +@@ -1190,7 +1197,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- LevelChunk chunk = ichunkprovider.getChunkNow(i1, j1); ++ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.getEntitiesOfClass(entityClass, box, list, predicate); +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 0a9bd85e0308e962df3b24a74bd5aac919744d6d..61f180a7c95d83bb88c7df4910c498d9bdf6785a 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -140,9 +140,10 @@ public class ActivationRange + { + for ( int j1 = k; j1 <= l; ++j1 ) + { +- if ( world.getWorld().isChunkLoaded( i1, j1 ) ) ++ LevelChunk chunk = (LevelChunk) world.getChunkIfLoadedImmediately( i1, j1 ); ++ if ( chunk != null ) + { +- activateChunkEntities( world.getChunk( i1, j1 ) ); ++ activateChunkEntities( chunk ); + } + } + } diff --git a/Remapped-Spigot-Server-Patches/0370-Reduce-sync-loads.patch b/Remapped-Spigot-Server-Patches/0370-Reduce-sync-loads.patch new file mode 100644 index 000000000..5349d4315 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0370-Reduce-sync-loads.patch @@ -0,0 +1,350 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Jul 2019 03:29:14 -0700 +Subject: [PATCH] Reduce sync loads + +This reduces calls to getChunkAt which would load chunks. + +This patch also adds a tool to find calls which are doing this, however +it must be enabled by setting the startup flag -Dpaper.debug-sync-loads=true + +To get a debug log for sync loads, the command is /paper syncloadinfo + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 62711d95db62221a2e4e6423c518afe13a6c7dbe..ff718bc7f521575e6a670e17fcf59a2d30841705 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -1,12 +1,18 @@ + package com.destroystokyo.paper; + + import com.destroystokyo.paper.io.chunk.ChunkTaskManager; ++import com.destroystokyo.paper.io.SyncLoadFinder; + import com.google.common.base.Functions; + import com.google.common.base.Joiner; + import com.google.common.collect.ImmutableSet; + import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerChunkCache; +@@ -14,7 +20,6 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.level.ChunkPos; +-import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; + import org.apache.commons.lang3.tuple.MutablePair; + import org.apache.commons.lang3.tuple.Pair; +@@ -29,6 +34,9 @@ import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.entity.Player; + + import java.io.File; ++import java.io.FileOutputStream; ++import java.io.PrintStream; ++import java.io.StringWriter; + import java.time.LocalDateTime; + import java.time.format.DateTimeFormatter; + import java.util.ArrayList; +@@ -44,7 +52,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build(); + + public PaperCommand(String name) { + super(name); +@@ -162,6 +170,9 @@ public class PaperCommand extends Command { + case "chunkinfo": + doChunkInfo(sender, args); + break; ++ case "syncloadinfo": ++ this.doSyncLoadInfo(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -179,6 +190,40 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doSyncLoadInfo(CommandSender sender, String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); ++ return; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ file.getParentFile().mkdirs(); ++ sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString()); ++ ++ ++ try { ++ final JsonObject data = SyncLoadFinder.serialize(); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try ( ++ PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8") ++ ) { ++ out.print(fileData); ++ } ++ sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!"); ++ } catch (Throwable thr) { ++ sender.sendMessage(ChatColor.RED + "Failed to write sync load information"); ++ thr.printStackTrace(); ++ } ++ } ++ + private void doChunkInfo(CommandSender sender, String[] args) { + List worlds; + if (args.length < 2 || args[1].equals("*")) { +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..524f33371b9de1d4dd6972fe59ffbe1804d7c5f3 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -0,0 +1,171 @@ ++package com.destroystokyo.paper.io; ++ ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.WeakHashMap; ++import net.minecraft.world.level.Level; ++ ++public class SyncLoadFinder { ++ ++ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads"); ++ ++ private static final WeakHashMap> SYNC_LOADS = new WeakHashMap<>(); ++ ++ private static final class SyncLoadInformation { ++ ++ public int times; ++ ++ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); ++ } ++ ++ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { ++ if (!ENABLED) { ++ return; ++ } ++ ++ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); ++ ++ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap map) -> { ++ if (map == null) { ++ map = new Object2ObjectOpenHashMap<>(); ++ } ++ ++ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> { ++ if (valueInMap == null) { ++ valueInMap = new SyncLoadInformation(); ++ } ++ ++ ++valueInMap.times; ++ ++ valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> { ++ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1); ++ }); ++ ++ return valueInMap; ++ }); ++ ++ return map; ++ }); ++ } ++ ++ public static JsonObject serialize() { ++ final JsonObject ret = new JsonObject(); ++ ++ final JsonArray worldsData = new JsonArray(); ++ ++ for (final Map.Entry> entry : SYNC_LOADS.entrySet()) { ++ final Level world = entry.getKey(); ++ ++ final JsonObject worldData = new JsonObject(); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ ++ final List> data = new ArrayList<>(); ++ ++ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> { ++ data.add(new Pair<>(stacktrace, times)); ++ }); ++ ++ data.sort((Pair pair1, Pair pair2) -> { ++ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order ++ }); ++ ++ final JsonArray stacktraces = new JsonArray(); ++ ++ for (Pair pair : data) { ++ final JsonObject stacktrace = new JsonObject(); ++ ++ stacktrace.addProperty("times", pair.getSecond().times); ++ ++ final JsonArray traces = new JsonArray(); ++ ++ for (StackTraceElement element : pair.getFirst().stacktrace) { ++ traces.add(String.valueOf(element)); ++ } ++ ++ stacktrace.add("stacktrace", traces); ++ ++ final JsonArray coordinates = new JsonArray(); ++ ++ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) { ++ final long key = coordinate.getLongKey(); ++ final int times = coordinate.getIntValue(); ++ coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times); ++ } ++ ++ stacktrace.add("coordinates", coordinates); ++ ++ stacktraces.add(stacktrace); ++ } ++ ++ ++ worldData.add("stacktraces", stacktraces); ++ worldsData.add(worldData); ++ } ++ ++ ret.add("worlds", worldsData); ++ ++ return ret; ++ } ++ ++ static final class ThrowableWithEquals { ++ ++ private final StackTraceElement[] stacktrace; ++ private final int hash; ++ ++ public ThrowableWithEquals(final StackTraceElement[] stacktrace) { ++ this.stacktrace = stacktrace; ++ this.hash = ThrowableWithEquals.hash(stacktrace); ++ } ++ ++ public static int hash(final StackTraceElement[] stacktrace) { ++ int hash = 0; ++ ++ for (int i = 0; i < stacktrace.length; ++i) { ++ hash *= 31; ++ hash += stacktrace[i].hashCode(); ++ } ++ ++ return hash; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (obj == null || obj.getClass() != this.getClass()) { ++ return false; ++ } ++ ++ final ThrowableWithEquals other = (ThrowableWithEquals)obj; ++ final StackTraceElement[] otherStackTrace = other.stacktrace; ++ ++ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) { ++ return false; ++ } ++ ++ if (this == obj) { ++ return true; ++ } ++ ++ for (int i = 0; i < this.stacktrace.length; ++i) { ++ if (!this.stacktrace[i].equals(otherStackTrace[i])) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 120b604d91643248ab375969f95f62a74cbf6be7..5e0d55c3821b1769d20514a8a6c5c74477019778 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -494,6 +494,7 @@ public class ServerChunkCache extends ChunkSource { + this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); + // Paper end ++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info + this.level.timings.syncChunkLoad.startTiming(); // Paper + this.mainThreadProcessor.managedBlock(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 9d4c81a4964317a0726171dc6d490d12bd959cc4..01f879a8dd0e1ffec380e02072567330152eaceb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -276,6 +276,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + }; + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; + // Paper end ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; ++ } ++ // Paper end + + // Add env and gen to constructor, WorldData -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 3a1b9f1ba19b28cebdafeb3b2476217d47213862..3e2cd6c7a34c1a792d7346019a8b039d1f4a7c04 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1130,7 +1130,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + for (int i1 = i; i1 <= j; ++i1) { + for (int j1 = k; j1 <= l; ++j1) { +- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false); ++ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.getEntities(except, box, list, predicate); +@@ -1151,7 +1151,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- LevelChunk chunk = this.getChunkSource().getChunk(i1, j1, false); ++ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.getEntities(type, box, list, predicate); +@@ -1174,7 +1174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false); ++ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.getEntitiesOfClass(entityClass, box, list, predicate); diff --git a/Remapped-Spigot-Server-Patches/0371-Implement-alternative-item-despawn-rate.patch b/Remapped-Spigot-Server-Patches/0371-Implement-alternative-item-despawn-rate.patch new file mode 100644 index 000000000..21aa2f962 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0371-Implement-alternative-item-despawn-rate.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 3 Jun 2019 02:02:39 -0400 +Subject: [PATCH] Implement alternative item-despawn-rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1ee2cced100626e48eb36ee14f84b9257c79a2f8..b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -1,10 +1,15 @@ + package com.destroystokyo.paper; + + import java.util.Arrays; ++import java.util.EnumMap; ++import java.util.HashMap; + import java.util.List; ++import java.util.Map; + + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; ++import org.bukkit.Material; ++import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -512,4 +517,52 @@ public class PaperWorldConfig { + private void disableRelativeProjectileVelocity() { + disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false); + } ++ ++ public boolean altItemDespawnRateEnabled; ++ public Map altItemDespawnRateMap; ++ private void altItemDespawnRate() { ++ String path = "alt-item-despawn-rate"; ++ ++ altItemDespawnRateEnabled = getBoolean(path + ".enabled", false); ++ ++ Map altItemDespawnRateMapDefault = new EnumMap<>(Material.class); ++ altItemDespawnRateMapDefault.put(Material.COBBLESTONE, 300); ++ for (Material key : altItemDespawnRateMapDefault.keySet()) { ++ config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key)); ++ } ++ ++ Map rawMap = new HashMap<>(); ++ try { ++ ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items"); ++ if (mapSection == null) { ++ mapSection = config.getConfigurationSection("world-settings.default." + path + ".items"); ++ } ++ for (String key : mapSection.getKeys(false)) { ++ int val = mapSection.getInt(key); ++ rawMap.put(key, val); ++ } ++ } ++ catch (Exception e) { ++ logError("alt-item-despawn-rate was malformatted"); ++ altItemDespawnRateEnabled = false; ++ } ++ ++ altItemDespawnRateMap = new EnumMap<>(Material.class); ++ if (!altItemDespawnRateEnabled) { ++ return; ++ } ++ ++ for(String key : rawMap.keySet()) { ++ try { ++ altItemDespawnRateMap.put(Material.valueOf(key), rawMap.get(key)); ++ } catch (Exception e) { ++ logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage()); ++ } ++ } ++ if(altItemDespawnRateEnabled) { ++ for(Material key : altItemDespawnRateMap.keySet()) { ++ log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key)); ++ } ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 281f5646980afc70890bdafd358ff9b20d32420d..96b8102773cbee2c3fe2711008ba1487084d67b0 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -32,6 +32,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.stats.Stats; ++import org.bukkit.Material; // Paper + import org.bukkit.event.entity.EntityPickupItemEvent; + import org.bukkit.event.player.PlayerPickupItemEvent; + // CraftBukkit end +@@ -160,7 +161,7 @@ public class ItemEntity extends Entity { + } + } + +- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -184,7 +185,7 @@ public class ItemEntity extends Entity { + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end + +- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -534,9 +535,16 @@ public class ItemEntity extends Entity { + + public void makeFakeItem() { + this.setNeverPickUp(); +- this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot ++ this.age = this.getDespawnRate() - 1; // Spigot // Paper + } + ++ // Paper start ++ public int getDespawnRate(){ ++ Material material = this.getItem().getBukkitStack().getType(); ++ return level.paperConfig.altItemDespawnRateMap.getOrDefault(material, level.spigotConfig.itemDespawnRate); ++ } ++ // Paper end ++ + @Override + public Packet getAddEntityPacket() { + return new ClientboundAddEntityPacket(this); diff --git a/Remapped-Spigot-Server-Patches/0372-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch b/Remapped-Spigot-Server-Patches/0372-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch new file mode 100644 index 000000000..eab9bd339 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0372-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 14 Jul 2019 21:05:03 -0500 +Subject: [PATCH] Do less work if we have a custom Bukkit generator + +If the Bukkit generator already has a spawn, use it immediately instead +of spending time generating one that we won't use + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 643d75b999c3da006eaaab11f4acd77e807683d4..753e6f609189c589514739bea80007bace3c89d2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -628,12 +628,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return biomebase.getMobSettings().playerSpawnFriendly(); +- }, random); +- ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition); ++ // Paper start - moved down + // CraftBukkit start + if (world.generator != null) { + Random rand = new Random(world.getSeed()); +@@ -649,6 +644,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ return biomebase.getMobSettings().playerSpawnFriendly(); ++ }, random); ++ ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition); ++ // Paper end + + if (blockposition == null) { + MinecraftServer.LOGGER.warn("Unable to find spawn biome"); diff --git a/Remapped-Spigot-Server-Patches/0373-Fix-MC-158900.patch b/Remapped-Spigot-Server-Patches/0373-Fix-MC-158900.patch new file mode 100644 index 000000000..585e674b4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0373-Fix-MC-158900.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 13 Aug 2019 06:35:17 -0700 +Subject: [PATCH] Fix MC-158900 + +The problem was we were checking isExpired() on the entry, but if it +was expired at that point, then it would be null. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7412765020854caabd32fb6f4fffcf7f4bf6dba7..e6eebb8f6f48cc55fc8fb114c959b8fbec4b8472 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -603,8 +603,10 @@ public abstract class PlayerList { + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress()); + +- if (getBans().isBanned(gameprofile) && !getBans().get(gameprofile).hasExpired()) { +- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile); ++ // Paper start - Fix MC-158900 ++ UserBanListEntry gameprofilebanentry; ++ if (getBans().isBanned(gameprofile) && (gameprofilebanentry = getBans().get(gameprofile)) != null) { ++ // Paper end + + chatmessage = new TranslatableComponent("multiplayer.disconnect.banned.reason", new Object[]{gameprofilebanentry.getReason()}); + if (gameprofilebanentry.getExpires() != null) { diff --git a/Remapped-Spigot-Server-Patches/0374-implement-optional-per-player-mob-spawns.patch b/Remapped-Spigot-Server-Patches/0374-implement-optional-per-player-mob-spawns.patch new file mode 100644 index 000000000..70bcd1b1b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0374-implement-optional-per-player-mob-spawns.patch @@ -0,0 +1,924 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 01:27:58 +0500 +Subject: [PATCH] implement optional per player mob spawns + + +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index 24eac9400fbf971742e89bbf47b0ba52b587c4eb..b818a7451d45d2ab7d4678f0065ada9017d8a631 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -58,6 +58,7 @@ public class WorldTimingsHandler { + + + public final Timing miscMobSpawning; ++ public final Timing playerMobDistanceMapUpdate; + + public final Timing poiUnload; + public final Timing chunkUnload; +@@ -123,6 +124,7 @@ public class WorldTimingsHandler { + + + miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update"); + + poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); + chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920..6aec502eb529d4090306e12e837117cde7e114eb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -565,4 +565,9 @@ public class PaperWorldConfig { + } + } + } ++ ++ public boolean perPlayerMobSpawns = false; ++ private void perPlayerMobSpawns() { ++ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2a87599922d7075a9f888f48a2deb35ed3eb7c54 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java +@@ -0,0 +1,252 @@ ++package com.destroystokyo.paper.util; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; ++import java.util.List; ++import java.util.Map; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.level.ChunkPos; ++import org.spigotmc.AsyncCatcher; ++import java.util.HashMap; ++ ++/** @author Spottedleaf */ ++public final class PlayerMobDistanceMap { ++ ++ private static final PooledHashSets.PooledObjectLinkedOpenHashSet EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>(); ++ ++ private final Map players = new HashMap<>(); ++ // we use linked for better iteration. ++ private final Long2ObjectOpenHashMap> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f); ++ private int viewDistance; ++ ++ private final PooledHashSets pooledHashSets = new PooledHashSets<>(); ++ ++ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final ChunkPos chunkPos) { ++ return this.getPlayersInRange(chunkPos.x, chunkPos.z); ++ } ++ ++ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final int chunkX, final int chunkZ) { ++ return this.playerMap.getOrDefault(ChunkPos.asLong(chunkX, chunkZ), EMPTY_SET); ++ } ++ ++ public void update(final List currentPlayers, final int newViewDistance) { ++ AsyncCatcher.catchOp("Distance map update"); ++ final ObjectLinkedOpenHashSet gone = new ObjectLinkedOpenHashSet<>(this.players.keySet()); ++ ++ final int oldViewDistance = this.viewDistance; ++ this.viewDistance = newViewDistance; ++ ++ for (final ServerPlayer player : currentPlayers) { ++ if (player.isSpectator() || !player.affectsSpawning) { ++ continue; // will be left in 'gone' (or not added at all) ++ } ++ ++ gone.remove(player); ++ ++ final SectionPos newPosition = player.getPlayerMapSection(); ++ final SectionPos oldPosition = this.players.put(player, newPosition); ++ ++ if (oldPosition == null) { ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ } else { ++ this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance); ++ } ++ //this.validatePlayer(player, newViewDistance); // debug only ++ } ++ ++ for (final ServerPlayer player : gone) { ++ final SectionPos oldPosition = this.players.remove(player); ++ if (oldPosition != null) { ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ } ++ } ++ } ++ ++ // expensive op, only for debug ++ private void validatePlayer(final ServerPlayer player, final int viewDistance) { ++ int entiesGot = 0; ++ int expectedEntries = (2 * viewDistance + 1); ++ expectedEntries *= expectedEntries; ++ ++ final SectionPos currPosition = player.getPlayerMapSection(); ++ ++ final int centerX = currPosition.getX(); ++ final int centerZ = currPosition.getZ(); ++ ++ for (final Long2ObjectLinkedOpenHashMap.Entry> entry : this.playerMap.long2ObjectEntrySet()) { ++ final long key = entry.getLongKey(); ++ final PooledHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); ++ ++ if (map.referenceCount == 0) { ++ throw new IllegalStateException("Invalid map"); ++ } ++ ++ if (map.set.contains(player)) { ++ ++entiesGot; ++ ++ final int chunkX = ChunkPos.getX(key); ++ final int chunkZ = ChunkPos.getZ(key); ++ ++ final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ)); ++ ++ if (dist > viewDistance) { ++ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); ++ } ++ } ++ } ++ ++ if (entiesGot != expectedEntries) { ++ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); ++ } ++ } ++ ++ private void addPlayerTo(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { ++ if (players == null) { ++ return player.cachedSingleMobDistanceMap; ++ } else { ++ return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player); ++ } ++ }); ++ } ++ ++ private void removePlayerFrom(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { ++ return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map ++ }); ++ } ++ ++ private void updatePlayer(final ServerPlayer player, final SectionPos oldPosition, final SectionPos newPosition, final int oldViewDistance, final int newViewDistance) { ++ final int toX = newPosition.getX(); ++ final int toZ = newPosition.getZ(); ++ final int fromX = oldPosition.getX(); ++ final int fromZ = oldPosition.getZ(); ++ ++ final int dx = toX - fromX; ++ final int dz = toZ - fromZ; ++ ++ final int totalX = Math.abs(fromX - toX); ++ final int totalZ = Math.abs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) { ++ // teleported? ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ return; ++ } ++ ++ // x axis is width ++ // z axis is height ++ // right refers to the x axis of where we moved ++ // top refers to the z axis of where we moved ++ ++ if (oldViewDistance == newViewDistance) { ++ // same view distance ++ ++ // used for relative positioning ++ final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise ++ final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise ++ ++ // The area excluded by overlapping the two view distance squares creates four rectangles: ++ // Two on the left, and two on the right. The ones on the left we consider the "removed" section ++ // and on the right the "added" section. ++ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually ++ // exclusive to the regions they surround. ++ ++ // 4 points of the rectangle ++ int maxX; // exclusive ++ int minX; // inclusive ++ int maxZ; // exclusive ++ int minZ; // inclusive ++ ++ if (dx != 0) { ++ // handle right addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX + (oldViewDistance * right) + right; // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addPlayerTo(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle up addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = toX - (oldViewDistance * right); // inclusive ++ maxZ = toZ + (oldViewDistance * up) + up; // exclusive ++ minZ = fromZ + (oldViewDistance * up) + up; // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addPlayerTo(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dx != 0) { ++ // handle left removal ++ ++ maxX = toX - (oldViewDistance * right); // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removePlayerFrom(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle down removal ++ ++ maxX = fromX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = toZ - (oldViewDistance * up); // exclusive ++ minZ = fromZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removePlayerFrom(player, currX, currZ); ++ } ++ } ++ } ++ } else { ++ // different view distance ++ // for now :) ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ } ++ } ++ ++ private void removePlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) { ++ final int x = position.getX(); ++ final int z = position.getZ(); ++ ++ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { ++ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { ++ this.removePlayerFrom(player, x + xoff, z + zoff); ++ } ++ } ++ } ++ ++ private void addNewPlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) { ++ final int x = position.getX(); ++ final int z = position.getZ(); ++ ++ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { ++ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { ++ this.addPlayerTo(player, x + xoff, z + zoff); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f13d3ff8391793a99f067189f854078334499c6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java +@@ -0,0 +1,241 @@ ++package com.destroystokyo.paper.util; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; ++import java.lang.ref.WeakReference; ++import java.util.Iterator; ++ ++/** @author Spottedleaf */ ++public class PooledHashSets { ++ ++ // we really want to avoid that equals() check as much as possible... ++ protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f); ++ ++ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { ++ if (current.referenceCount == 0) { ++ throw new IllegalStateException("Cannot decrement reference count for " + current); ++ } ++ if (current.referenceCount == -1 || --current.referenceCount > 0) { ++ return; ++ } ++ ++ this.mapPool.remove(current); ++ return; ++ } ++ ++ public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { ++ final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); ++ ++ if (cached != null) { ++ if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ decrementReferenceCount(current); ++ ++ return cached; ++ } ++ ++ if (!current.add(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.remove(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.remove(object); ++ } ++ ++ current.updateAddCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ // rets null if current.size() == 1 ++ public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { ++ if (current.set.size() == 1) { ++ decrementReferenceCount(current); ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); ++ ++ if (cached != null) { ++ if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ decrementReferenceCount(current); ++ ++ return cached; ++ } ++ ++ if (!current.remove(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.add(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.add(object); ++ } ++ ++ current.updateRemoveCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ public static final class PooledObjectLinkedOpenHashSet implements Iterable { ++ ++ private static final WeakReference NULL_REFERENCE = new WeakReference(null); ++ ++ final ObjectLinkedOpenHashSet set; ++ int referenceCount; // -1 if special ++ int hash; // optimize hashcode ++ ++ // add cache ++ WeakReference lastAddObject = NULL_REFERENCE; ++ WeakReference> lastAddMap = NULL_REFERENCE; ++ ++ // remove cache ++ WeakReference lastRemoveObject = NULL_REFERENCE; ++ WeakReference> lastRemoveMap = NULL_REFERENCE; ++ ++ public PooledObjectLinkedOpenHashSet() { ++ this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final E single) { ++ this(); ++ this.referenceCount = -1; ++ this.add(single); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { ++ this.set = other.set.clone(); ++ this.hash = other.hash; ++ } ++ ++ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java ++ // generated by https://github.com/skeeto/hash-prospector ++ static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public PooledObjectLinkedOpenHashSet getAddCache(final E element) { ++ final E currentAdd = this.lastAddObject.get(); ++ ++ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet map = this.lastAddMap.get(); ++ if (map == null || map.referenceCount == 0) { ++ // we need to ret null if ref count is zero as calling code will assume the map is in use ++ return null; ++ } ++ ++ return map; ++ } ++ ++ public PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { ++ final E currentRemove = this.lastRemoveObject.get(); ++ ++ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet map = this.lastRemoveMap.get(); ++ if (map == null || map.referenceCount == 0) { ++ // we need to ret null if ref count is zero as calling code will assume the map is in use ++ return null; ++ } ++ ++ return map; ++ } ++ ++ public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastAddObject = new WeakReference<>(element); ++ this.lastAddMap = new WeakReference<>(map); ++ } ++ ++ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastRemoveObject = new WeakReference<>(element); ++ this.lastRemoveMap = new WeakReference<>(map); ++ } ++ ++ boolean add(final E element) { ++ boolean added = this.set.add(element); ++ ++ if (added) { ++ this.hash += hash0(element.hashCode()); ++ } ++ ++ return added; ++ } ++ ++ boolean remove(Object element) { ++ boolean removed = this.set.remove(element); ++ ++ if (removed) { ++ this.hash -= hash0(element.hashCode()); ++ } ++ ++ return removed; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return this.set.iterator(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object other) { ++ if (!(other instanceof PooledObjectLinkedOpenHashSet)) { ++ return false; ++ } ++ if (this.referenceCount == 0) { ++ return other == this; ++ } else { ++ if (other == this) { ++ // Unfortunately we are never equal to our own instance while in use! ++ return false; ++ } ++ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + ++ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index c00f7c60ce7b497d697d1abdf230f91f327e2113..190ddd4d9ef3472c33d46c2ead72fa0dc918054a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -71,6 +71,7 @@ import net.minecraft.util.thread.ProcessorMailbox; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.Mob; ++import net.minecraft.world.entity.MobCategory; + import net.minecraft.world.entity.ai.village.poi.PoiManager; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.level.ChunkPos; +@@ -127,7 +128,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final Int2ObjectMap entityMap; + private final Long2ByteMap chunkTypeCache; + private final Queue unloadQueue; private final Queue getUnloadQueueTasks() { return this.unloadQueue; } // Paper - OBFHELPER +- private int viewDistance; ++ int viewDistance; // Paper - private -> package private ++ public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); +@@ -206,6 +208,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.overworldDataStorage = supplier; + this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper + this.setViewDistance(i); ++ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper ++ } ++ ++ public void updatePlayerMobTypeMap(Entity entity) { ++ if (!this.level.paperConfig.perPlayerMobSpawns) { ++ return; ++ } ++ int chunkX = (int)Math.floor(entity.getX()) >> 4; ++ int chunkZ = (int)Math.floor(entity.getZ()) >> 4; ++ int index = entity.getType().getEnumCreatureType().ordinal(); ++ ++ for (ServerPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) { ++ ++player.mobCounts[index]; ++ } ++ } ++ ++ public int getMobCountNear(ServerPlayer entityPlayer, MobCategory enumCreatureType) { ++ return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + + private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 5e0d55c3821b1769d20514a8a6c5c74477019778..eac5e799c4d26e53286a27c54b56899ba0b9ffb2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -768,7 +768,22 @@ public class ServerChunkCache extends ChunkSource { + this.level.getProfiler().push("naturalSpawnCount"); + this.level.timings.countNaturalMobs.startTiming(); // Paper - timings + int l = this.distanceManager.getNaturalSpawnChunkCount(); +- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk); ++ // Paper start - per player mob spawning ++ NaturalSpawner.SpawnState spawnercreature_d; // moved down ++ if (this.chunkMap.playerMobDistanceMap != null) { ++ // update distance map ++ this.level.timings.playerMobDistanceMapUpdate.startTiming(); ++ this.chunkMap.playerMobDistanceMap.update(this.level.players, this.chunkMap.viewDistance); ++ this.level.timings.playerMobDistanceMapUpdate.stopTiming(); ++ // re-set mob counts ++ for (ServerPlayer player : this.level.players) { ++ Arrays.fill(player.mobCounts, 0); ++ } ++ spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, true); ++ } else { ++ spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, false); ++ } ++ // Paper end + this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.lastSpawnState = spawnercreature_d; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d6cfe68be1a944ff5d5780666467f5fd8e2794e3..b0eed4e18fc183856613c05f378576eb19985c46 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -93,6 +93,7 @@ import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.HumanoidArm; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; ++import net.minecraft.world.entity.MobCategory; + import net.minecraft.world.entity.NeutralMob; + import net.minecraft.world.entity.animal.horse.AbstractHorse; + import net.minecraft.world.entity.item.ItemEntity; +@@ -216,6 +217,11 @@ public class ServerPlayer extends Player implements ContainerListener { + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; + // Paper end ++ // Paper start - mob spawning rework ++ public static final int ENUMCREATURETYPE_TOTAL_ENUMS = MobCategory.values().length; ++ public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper ++ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; ++ // Paper end + + // CraftBukkit start + public String displayName; +@@ -254,6 +260,7 @@ public class ServerPlayer extends Player implements ContainerListener { + this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper + this.canPickUpLoot = true; + this.maxHealthCache = this.getMaxHealth(); ++ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + } + + // Yes, this doesn't match Vanilla, but it's the best we can do for now. +@@ -2058,6 +2065,7 @@ public class ServerPlayer extends Player implements ContainerListener { + + } + ++ public final SectionPos getPlayerMapSection() { return this.getLastSectionPos(); } // Paper - OBFHELPER + public SectionPos getLastSectionPos() { + return this.lastSectionPos; + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index e39d950783599b01271bdb7e67fe68b46af0c49c..ae50030df7512c56c552e800b74ef4c69ec6d6d2 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -426,6 +426,7 @@ public class EntityType { + return this.canSpawnFarFromPlayer; + } + ++ public final MobCategory getEnumCreatureType() { return this.getCategory(); } // Paper - OBFHELPER + public MobCategory getCategory() { + return this.category; + } +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 0fb69f9194078e5e05e36ed909eb48424b6465b4..df271598f6036c8cab8a8811151a376dda46e44d 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -17,6 +17,7 @@ import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.FluidTags; + import net.minecraft.tags.Tag; +@@ -60,9 +61,14 @@ public final class NaturalSpawner { + }); + + public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource) { ++ // Paper start - add countMobs parameter ++ return countMobs(spawningChunkCount, entities, chunkSource, false); ++ } ++ public static NaturalSpawner.SpawnState countMobs(int i, Iterable iterable, NaturalSpawner.ChunkGetter spawnercreature_b, boolean countMobs) { ++ // Paper end - add countMobs parameter + PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator(); + Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); +- Iterator iterator = entities.iterator(); ++ Iterator iterator = iterable.iterator(); + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); +@@ -89,7 +95,7 @@ public final class NaturalSpawner { + BlockPos blockposition = entity.blockPosition(); + long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4); + +- chunkSource.query(j, (chunk) -> { ++ spawnercreature_b.query(j, (chunk) -> { + MobSpawnSettings.MobSpawnCost biomesettingsmobs_b = getRoughBiome(blockposition, chunk).getMobSettings().getMobSpawnCost(entity.getType()); + + if (biomesettingsmobs_b != null) { +@@ -97,11 +103,16 @@ public final class NaturalSpawner { + } + + object2intopenhashmap.addTo(enumcreaturetype, 1); ++ // Paper start ++ if (countMobs) { ++ ((ServerLevel)chunk.world).getChunkSource().chunkMap.updatePlayerMobTypeMap(entity); ++ } ++ // Paper end + }); + } + } + +- return new NaturalSpawner.SpawnState(spawningChunkCount, object2intopenhashmap, spawnercreatureprobabilities); ++ return new NaturalSpawner.SpawnState(i, object2intopenhashmap, spawnercreatureprobabilities); + } + + private static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) { +@@ -155,13 +166,31 @@ public final class NaturalSpawner { + continue; + } + +- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && info.a(enumcreaturetype, limit)) { ++ // Paper start - only allow spawns upto the limit per chunk and update count afterwards ++ int currEntityCount = info.getEntityCountsByType().getInt(enumcreaturetype); ++ int k1 = limit * info.getSpawnerChunks() / NaturalSpawner.MAGIC_NUMBER; ++ int difference = k1 - currEntityCount; ++ ++ if (world.paperConfig.perPlayerMobSpawns) { ++ int minDiff = Integer.MAX_VALUE; ++ for (ServerPlayer entityplayer : world.getChunkSource().chunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) { ++ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff); ++ } ++ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; ++ } ++ // Paper end ++ ++ // Paper start - per player mob spawning ++ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && difference > 0) { + // CraftBukkit end +- spawnCategoryForChunk(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> { ++ int spawnCount = spawnMobs(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> { + return info.canSpawn(entitytypes, blockposition, ichunkaccess); + }, (entityinsentient, ichunkaccess) -> { + info.afterSpawn(entityinsentient, ichunkaccess); +- }); ++ }, ++ difference, world.paperConfig.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); ++ info.getEntityCountsByType().mergeInt(enumcreaturetype, spawnCount, Integer::sum); ++ // Paper end - per player mob spawning + } + } + +@@ -170,31 +199,43 @@ public final class NaturalSpawner { + } + + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { +- BlockPos blockposition = getRandomPosWithin(world, chunk); ++ // Paper start - add parameters and int ret type ++ spawnMobs(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); ++ } ++ public static int spawnMobs(MobCategory enumcreaturetype, ServerLevel worldserver, LevelChunk chunk, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add parameters and int ret type ++ BlockPos blockposition = getRandomPosWithin(worldserver, chunk); + + if (blockposition.getY() >= 1) { +- spawnCategoryForPosition(group, world, (ChunkAccess) chunk, blockposition, checker, runner); ++ return spawnMobsInternal(enumcreaturetype, worldserver, (ChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity); + } ++ return 0; // Paper + } + + public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { +- StructureFeatureManager structuremanager = world.structureFeatureManager(); +- ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); +- int i = pos.getY(); +- BlockState iblockdata = world.getTypeIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn +- +- if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn ++ // Paper start - add maxSpawns parameter and return spawned mobs ++ spawnMobsInternal(group, world, chunk, pos, checker, runner, Integer.MAX_VALUE, null); ++ } ++ public static int spawnMobsInternal(MobCategory enumcreaturetype, ServerLevel worldserver, ChunkAccess ichunkaccess, BlockPos blockposition, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add maxSpawns parameter and return spawned mobs ++ StructureFeatureManager structuremanager = worldserver.structureFeatureManager(); ++ ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator(); ++ int i = blockposition.getY(); ++ BlockState iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn ++ int j = 0; // Paper - moved up ++ ++ if (iblockdata != null && !iblockdata.isRedstoneConductor(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); +- int j = 0; ++ // Paper - moved up + int k = 0; + + while (k < 3) { +- int l = pos.getX(); +- int i1 = pos.getZ(); ++ int l = blockposition.getX(); ++ int i1 = blockposition.getZ(); + boolean flag = true; + MobSpawnSettings.SpawnerData biomesettingsmobs_c = null; + SpawnGroupData groupdataentity = null; +- int j1 = Mth.ceil(world.random.nextFloat() * 4.0F); ++ int j1 = Mth.ceil(worldserver.random.nextFloat() * 4.0F); + int k1 = 0; + int l1 = 0; + +@@ -202,53 +243,58 @@ public final class NaturalSpawner { + if (l1 < j1) { + label53: + { +- l += world.random.nextInt(6) - world.random.nextInt(6); +- i1 += world.random.nextInt(6) - world.random.nextInt(6); ++ l += worldserver.random.nextInt(6) - worldserver.random.nextInt(6); ++ i1 += worldserver.random.nextInt(6) - worldserver.random.nextInt(6); + blockposition_mutableblockposition.set(l, i, i1); + double d0 = (double) l + 0.5D; + double d1 = (double) i1 + 0.5D; +- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); ++ Player entityhuman = worldserver.getNearestPlayer(d0, (double) i, d1, -1.0D, false); + + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); + +- if (isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2) && world.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn ++ if (isRightDistanceToPlayerAndSpawnPoint(worldserver, ichunkaccess, blockposition_mutableblockposition, d2) && worldserver.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { +- biomesettingsmobs_c = getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, (BlockPos) blockposition_mutableblockposition); ++ biomesettingsmobs_c = getRandomSpawnMobAt(worldserver, structuremanager, chunkgenerator, enumcreaturetype, worldserver.random, (BlockPos) blockposition_mutableblockposition); + if (biomesettingsmobs_c == null) { + break label53; + } + +- j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount); ++ j1 = biomesettingsmobs_c.minCount + worldserver.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount); + } + + // Paper start +- Boolean doSpawning = a(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); + if (doSpawning == null) { +- return; ++ return j; // Paper + } +- if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, ichunkaccess)) { + // Paper end +- Mob entityinsentient = getMobForSpawn(world, biomesettingsmobs_c.type); ++ Mob entityinsentient = getMobForSpawn(worldserver, biomesettingsmobs_c.type); + + + if (entityinsentient == null) { +- return; ++ return j; // Paper + } + +- entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); +- if (isValidPositionForMob(world, entityinsentient, d2)) { +- groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null); ++ entityinsentient.moveTo(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F); ++ if (isValidPositionForMob(worldserver, entityinsentient, d2)) { ++ groupdataentity = entityinsentient.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null); + // CraftBukkit start +- world.addAllEntities(entityinsentient, SpawnReason.NATURAL); ++ worldserver.addAllEntities(entityinsentient, SpawnReason.NATURAL); + if (!entityinsentient.removed) { +- ++j; ++ ++j; // Paper - force diff on name change - we expect this to be the total amount spawned + ++k1; +- runner.run(entityinsentient, chunk); ++ spawnercreature_a.run(entityinsentient, ichunkaccess); ++ // Paper start ++ if (trackEntity != null) { ++ trackEntity.accept(entityinsentient); ++ } ++ // Paper end + } + // CraftBukkit end +- if (j >= entityinsentient.getMaxSpawnClusterSize()) { +- return; ++ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper ++ return j; // Paper + } + + if (entityinsentient.isMaxGroupSizeReached(k1)) { +@@ -270,6 +316,7 @@ public final class NaturalSpawner { + } + + } ++ return j; // Paper + } + + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { +@@ -510,8 +557,8 @@ public final class NaturalSpawner { + + public static class SpawnState { + +- private final int spawnableChunkCount; +- private final Object2IntOpenHashMap mobCategoryCounts; ++ private final int spawnableChunkCount; final int getSpawnerChunks() { return this.spawnableChunkCount; } // Paper - OBFHELPER ++ private final Object2IntOpenHashMap mobCategoryCounts; final Object2IntMap getEntityCountsByType() { return this.mobCategoryCounts; } // Paper - OBFHELPER + private final PotentialCalculator spawnPotential; + private final Object2IntMap unmodifiableMobCategoryCounts; + @Nullable +@@ -572,7 +619,7 @@ public final class NaturalSpawner { + + // CraftBukkit start + private boolean a(MobCategory enumcreaturetype, int limit) { +- int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; // Paper - diff on change, needed in the spawn method + // CraftBukkit end + + return this.mobCategoryCounts.getInt(enumcreaturetype) < i; diff --git a/Remapped-Spigot-Server-Patches/0375-Prevent-consuming-the-wrong-itemstack.patch b/Remapped-Spigot-Server-Patches/0375-Prevent-consuming-the-wrong-itemstack.patch new file mode 100644 index 000000000..32aa3ddc1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0375-Prevent-consuming-the-wrong-itemstack.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 19:42:35 +0500 +Subject: [PATCH] Prevent consuming the wrong itemstack + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 046a05925739005080af35c4be984303b575bf68..f8917123547615dd624e3e428ec1bf6450c7b7d8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3198,15 +3198,18 @@ public abstract class LivingEntity extends Entity { + this.entityData.set(LivingEntity.DATA_LIVING_ENTITY_FLAGS, (byte) j); + } + +- public void startUsingItem(InteractionHand hand) { +- ItemStack itemstack = this.getItemInHand(hand); ++ // Paper start -- OBFHELPER and forwarder to method with forceUpdate parameter ++ public void startUsingItem(InteractionHand hand) { this.updateActiveItem(hand, false); } ++ public void updateActiveItem(InteractionHand enumhand, boolean forceUpdate) { ++ // Paper end ++ ItemStack itemstack = this.getItemInHand(enumhand); + +- if (!itemstack.isEmpty() && !this.isUsingItem()) { ++ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag + this.useItem = itemstack; + this.useItemRemaining = itemstack.getUseDuration(); + if (!this.level.isClientSide) { + this.setLivingEntityFlag(1, true); +- this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND); ++ this.setLivingEntityFlag(2, enumhand == InteractionHand.OFF_HAND); + } + + } +@@ -3279,6 +3282,7 @@ public abstract class LivingEntity extends Entity { + this.releaseUsingItem(); + } else { + if (!this.useItem.isEmpty() && this.isUsingItem()) { ++ this.updateActiveItem(this.getUsedItemHand(), true); // Paper + this.triggerItemUseEffects(this.useItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; +@@ -3313,8 +3317,8 @@ public abstract class LivingEntity extends Entity { + } + + this.stopUsingItem(); +- // Paper start - if the replacement is anything but the default, update the client inventory +- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ // Paper start ++ if (this instanceof ServerPlayer) { + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + } + // Paper end diff --git a/Remapped-Spigot-Server-Patches/0376-Generator-Settings.patch b/Remapped-Spigot-Server-Patches/0376-Generator-Settings.patch new file mode 100644 index 000000000..fe0b780b5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0376-Generator-Settings.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 02:17:54 -0600 +Subject: [PATCH] Generator Settings + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6aec502eb529d4090306e12e837117cde7e114eb..290e49cf0077909ad7ab8127c01ef93cf7b70b51 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -570,4 +570,9 @@ public class PaperWorldConfig { + private void perPlayerMobSpawns() { + perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); + } ++ ++ public boolean generateFlatBedrock; ++ private void generatorSettings() { ++ generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index e6303cdb433ee2b6782e2a0bd6b03e4f6ecb18ba..36c7ab3919d8818af96d50170aeb431051c5aabf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -25,6 +25,18 @@ import org.apache.logging.log4j.LogManager; + + public interface ChunkAccess extends BlockGetter, FeatureAccess { + ++ // Paper start ++ default boolean generateFlatBedrock() { ++ if (this instanceof ProtoChunk) { ++ return ((ProtoChunk)this).world.paperConfig.generateFlatBedrock; ++ } else if (this instanceof LevelChunk) { ++ return ((LevelChunk)this).world.paperConfig.generateFlatBedrock; ++ } else { ++ return false; ++ } ++ } ++ // Paper end ++ + BlockState getType(final int x, final int y, final int z); // Paper + @Nullable + BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 87fd585141ad9818fca0b697cb4c87248fe7ce11..5a94464b9628b74eefa1c1d8514cf267f4c8a11d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -64,7 +64,7 @@ public class ProtoChunk implements ChunkAccess { + private long inhabitedTime; + private final Map carvingMasks; + private volatile boolean isLightCorrect; +- private final Level world; // Paper - Anti-Xray - Add world ++ final Level world; // Paper - Anti-Xray - Add world // Paper - private -> default + + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData) { this(pos, upgradeData, null); } // Notice for updates: Please make sure this constructor isn't used anywhere +diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index b38a9c87fc996bd3107c38f6446a687fd093c617..04adec255e4650ead8d80bee32a681c98686fb95 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -408,8 +408,8 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + int i = chunk.getPos().getMinBlockX(); + int j = chunk.getPos().getMinBlockZ(); + NoiseGeneratorSettings generatorsettingbase = (NoiseGeneratorSettings) this.settings.get(); +- int k = generatorsettingbase.getBedrockFloorPosition(); +- int l = this.height - 1 - generatorsettingbase.getBedrockRoofPosition(); ++ int k = generatorsettingbase.getBedrockFloorPosition(); final int floorHeight = k; // Paper ++ int l = this.height - 1 - generatorsettingbase.getBedrockRoofPosition(); final int roofHeight = l; // Paper + boolean flag = true; + boolean flag1 = l + 4 >= 0 && l < this.height; + boolean flag2 = k + 4 >= 0 && k < this.height; +@@ -423,7 +423,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + + if (flag1) { + for (i1 = 0; i1 < 5; ++i1) { +- if (i1 <= random.nextInt(5)) { ++ if (i1 <= (chunk.generateFlatBedrock() ? roofHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock roof + chunk.setBlockState(blockposition_mutableblockposition.set(blockposition.getX(), l - i1, blockposition.getZ()), Blocks.BEDROCK.defaultBlockState(), false); + } + } +@@ -431,7 +431,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + + if (flag2) { + for (i1 = 4; i1 >= 0; --i1) { +- if (i1 <= random.nextInt(5)) { ++ if (i1 <= (chunk.generateFlatBedrock() ? floorHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock floor + chunk.setBlockState(blockposition_mutableblockposition.set(blockposition.getX(), k + i1, blockposition.getZ()), Blocks.BEDROCK.defaultBlockState(), false); + } + } diff --git a/Remapped-Spigot-Server-Patches/0377-Fix-MC-161754.patch b/Remapped-Spigot-Server-Patches/0377-Fix-MC-161754.patch new file mode 100644 index 000000000..c50817dd4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0377-Fix-MC-161754.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 24 Sep 2019 16:03:00 -0700 +Subject: [PATCH] Fix MC-161754 + +Fixes https://github.com/PaperMC/Paper/issues/2580 + +We can use an entity valid check since this method is invoked for +each inventory iteraction (thanks to CB) and on player tick (vanilla). + +diff --git a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java +index 7246b3a84415e303591adb08d81362201deebfce..e0237e821b2c31ba68168fddf1c1a4ebfcf10ca7 100644 +--- a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java +@@ -85,7 +85,7 @@ public class HorseInventoryMenu extends AbstractContainerMenu { + + @Override + public boolean stillValid(Player player) { +- return this.horseContainer.stillValid(player) && this.horse.isAlive() && this.horse.distanceTo((Entity) player) < 8.0F; ++ return this.horseContainer.stillValid(player) && (this.horse.isAlive() && this.horse.valid) && this.horse.distanceTo((Entity) player) < 8.0F; // Paper - Fix MC-161754 + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0378-Performance-improvement-for-Chunk.getEntities.patch b/Remapped-Spigot-Server-Patches/0378-Performance-improvement-for-Chunk.getEntities.patch new file mode 100644 index 000000000..439bca032 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0378-Performance-improvement-for-Chunk.getEntities.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wea_ondara +Date: Thu, 10 Oct 2019 11:29:42 +0200 +Subject: [PATCH] Performance improvement for Chunk.getEntities + +This patch aims to reduce performance cost used by collecting the +entities of a chunk. Previously the entitySlices were copied into an +extra array with List.toArray() with is a costly and unneccessary +operation. This patch will reduce the load of plugins which for example +implement custom moblimits and depend on Chunk.getEntities(). + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 74bad15034d9d55fb70931f38868f812160c6305..0f45f4b2486e910d11fd94b260bcd68e49eae31e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -116,14 +116,14 @@ public class CraftChunk implements Chunk { + Entity[] entities = new Entity[count]; + + for (int i = 0; i < 16; i++) { +- +- for (Object obj : chunk.entitySlices[i].toArray()) { +- if (!(obj instanceof net.minecraft.world.entity.Entity)) { ++ // Paper start - speed up (was with chunk.entitySlices[i].toArray() and cast checks which costs a lot of performance if called often) ++ for (net.minecraft.world.entity.Entity entity : chunk.entitySlices[i]) { ++ if (entity == null) { + continue; + } +- +- entities[index++] = ((net.minecraft.world.entity.Entity) obj).getBukkitEntity(); ++ entities[index++] = entity.getBukkitEntity(); + } ++ // Paper end + } + + return entities; diff --git a/Remapped-Spigot-Server-Patches/0379-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch b/Remapped-Spigot-Server-Patches/0379-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch new file mode 100644 index 000000000..548deb2cd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0379-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterErwin +Date: Wed, 30 Oct 2019 16:57:54 +0100 +Subject: [PATCH] Fix spawning of hanging entities that are not ItemFrames and + can not face UP or DOWN + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 52444619a4bae80a12bf296fbe07fa811adf806e..fb74bdcf4c2935b56e92717cc5a1504fbc853d0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1868,7 +1868,12 @@ public class CraftWorld implements World { + height = 9; + } + +- BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN}; ++ // Paper start - In addition to d65a2576e40e58c8e446b330febe6799d13a604f do not check UP/DOWN for non item frames ++ // BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN}; ++ BlockFace[] faces = (ItemFrame.class.isAssignableFrom(clazz)) ++ ? new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN} ++ : new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH}; ++ // Paper end + final BlockPos pos = new BlockPos(x, y, z); + for (BlockFace dir : faces) { + net.minecraft.world.level.block.state.BlockState nmsBlock = world.getBlockState(pos.relative(CraftBlock.blockFaceToNotch(dir))); diff --git a/Remapped-Spigot-Server-Patches/0380-Expose-the-internal-current-tick.patch b/Remapped-Spigot-Server-Patches/0380-Expose-the-internal-current-tick.patch new file mode 100644 index 000000000..9cf110529 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0380-Expose-the-internal-current-tick.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 20 Apr 2019 19:47:34 -0500 +Subject: [PATCH] Expose the internal current tick + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 96a3a4d89df858d4e46a36f110dd9ad3a2061433..3c0ba80bbba19f3725013e118cecdbac5612deec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2363,5 +2363,10 @@ public final class CraftServer implements Server { + } + return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); + } ++ ++ @Override ++ public int getCurrentTick() { ++ return net.minecraft.server.MinecraftServer.currentTick; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0381-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch b/Remapped-Spigot-Server-Patches/0381-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch new file mode 100644 index 000000000..169ff964a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0381-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 9 Oct 2019 21:51:43 -0500 +Subject: [PATCH] Fix stuck in sneak when changing worlds (MC-10657) + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index b0eed4e18fc183856613c05f378576eb19985c46..2ef273e3b917803f3e2ac3c6a22d92a15b9eb71a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1070,6 +1070,8 @@ public class ServerPlayer extends Player implements ContainerListener { + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; + ++ setShiftKeyDown(false); // Paper - fix MC-10657 ++ + // CraftBukkit start + PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); + this.level.getCraftServer().getPluginManager().callEvent(changeEvent); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e6eebb8f6f48cc55fc8fb114c959b8fbec4b8472..dfdde9722bc0d83916779014b7718eef2c01b3db 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -842,6 +842,8 @@ public abstract class PlayerList { + entityplayer.connection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobEffect)); + } + ++ entityplayer.setShiftKeyDown(false); // Paper - fix MC-10657 ++ + // Fire advancement trigger + entityplayer.triggerDimensionChangeTriggers(((CraftWorld) fromWorld).getHandle()); + diff --git a/Remapped-Spigot-Server-Patches/0382-Add-option-to-disable-pillager-patrols.patch b/Remapped-Spigot-Server-Patches/0382-Add-option-to-disable-pillager-patrols.patch new file mode 100644 index 000000000..2e772d88a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0382-Add-option-to-disable-pillager-patrols.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 9 Oct 2019 21:46:15 -0500 +Subject: [PATCH] Add option to disable pillager patrols + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 290e49cf0077909ad7ab8127c01ef93cf7b70b51..e726b6213cf2e8f5b326f05c0438b8f1ee2b73c5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -575,4 +575,9 @@ public class PaperWorldConfig { + private void generatorSettings() { + generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false); + } ++ ++ public boolean disablePillagerPatrols = false; ++ private void pillagerSettings() { ++ disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index caadd1a0fa6c4c446f84629088890a09e29622d9..48efe133d294bb1b17e8ac8b44eea8a29f15845f 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -26,6 +26,7 @@ public class PatrolSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ if (world.paperConfig.disablePillagerPatrols) return 0; // Paper + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { diff --git a/Remapped-Spigot-Server-Patches/0383-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch b/Remapped-Spigot-Server-Patches/0383-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch new file mode 100644 index 000000000..3d4d87429 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0383-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lukasz Derlatka +Date: Mon, 11 Nov 2019 16:08:13 +0100 +Subject: [PATCH] Fix AssertionError when player hand set to empty type + +Fixes an AssertionError when setting the player's item in hand to null or a new ItemStack of Air in PlayerInteractEvent +Fixes GH-2718 + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e45690b6197335ed1c07fa04c39b311b401724d7..2b79413bb8a592a7b7093e11d3a0cce895286c8f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1706,6 +1706,10 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 + return; + } ++ // Paper start ++ itemstack = this.player.getItemInHand(enumhand); ++ if (itemstack.isEmpty()) return; ++ // Paper end + InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); + + if (enuminteractionresult.shouldSwing()) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index f8917123547615dd624e3e428ec1bf6450c7b7d8..b49d4772932a58852b3195f5f56ff93dbcabf766 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2153,6 +2153,7 @@ public abstract class LivingEntity extends Entity { + return predicate.test(this.getMainHandItem().getItem()) || predicate.test(this.getOffhandItem().getItem()); + } + ++ public final ItemStack getItemInHand(InteractionHand enumhand) { return this.getItemInHand(enumhand); } // Paper - OBFHELPER + public ItemStack getItemInHand(InteractionHand hand) { + if (hand == InteractionHand.MAIN_HAND) { + return this.getItemBySlot(EquipmentSlot.MAINHAND); diff --git a/Remapped-Spigot-Server-Patches/0384-PlayerLaunchProjectileEvent.patch b/Remapped-Spigot-Server-Patches/0384-PlayerLaunchProjectileEvent.patch new file mode 100644 index 000000000..886004a31 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0384-PlayerLaunchProjectileEvent.patch @@ -0,0 +1,351 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 03:11:03 -0500 +Subject: [PATCH] PlayerLaunchProjectileEvent + + +diff --git a/src/main/java/net/minecraft/world/InteractionResultHolder.java b/src/main/java/net/minecraft/world/InteractionResultHolder.java +index 3bc22b977e00c1901a9025112e354e59fcc08a74..c12e2a65d30ca22db0c522e4b245009bcc38c4f4 100644 +--- a/src/main/java/net/minecraft/world/InteractionResultHolder.java ++++ b/src/main/java/net/minecraft/world/InteractionResultHolder.java +@@ -10,6 +10,7 @@ public class InteractionResultHolder { + this.object = value; + } + ++ public InteractionResult getResult() { return this.getResult(); } // Paper - OBFHELPER + public InteractionResult getResult() { + return this.result; + } +diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java +index d2c4241104343a2d2283c358ab2247e333cf0dbf..5d03dcaf79a14946934a6732c94a195d3d65c56d 100644 +--- a/src/main/java/net/minecraft/world/item/EggItem.java ++++ b/src/main/java/net/minecraft/world/item/EggItem.java +@@ -25,21 +25,35 @@ public class EggItem extends Item { + + entityegg.setItem(itemstack); + entityegg.shootFromRotation(user, user.xRot, user.yRot, 0.0F, 1.5F, 1.0F); +- // CraftBukkit start +- if (!world.addFreshEntity(entityegg)) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entityegg)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ itemstack.shrink(1); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { + if (user instanceof net.minecraft.server.level.ServerPlayer) { + ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); + } +- return InteractionResultHolder.fail(itemstack); ++ return new InteractionResultHolder(net.minecraft.world.InteractionResult.FAIL, itemstack); + } +- // CraftBukkit end ++ // Paper end ++ ++ + } + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (EggItem.random.nextFloat() * 0.4F + 0.8F)); // CraftBukkit - from above + +- user.awardStat(Stats.ITEM_USED.get(this)); +- if (!user.abilities.instabuild) { +- itemstack.shrink(1); ++ /* // Paper start - moved up ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); + } +diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java +index 347e95cc393e773de98b74e18cebbe05f612bdf6..a57c59b330766a2c784ddb7431719e9c1cc2fca8 100644 +--- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java +@@ -25,22 +25,37 @@ public class EnderpearlItem extends Item { + + entityenderpearl.setItem(itemstack); + entityenderpearl.shootFromRotation(user, user.xRot, user.yRot, 0.0F, 1.5F, 1.0F); +- if (!world.addFreshEntity(entityenderpearl)) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entityenderpearl)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ itemstack.shrink(1); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ user.getCooldowns().addCooldown(this, 20); ++ } else { ++ // Paper end + if (user instanceof net.minecraft.server.level.ServerPlayer) { + ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); + } +- return InteractionResultHolder.fail(itemstack); ++ return new InteractionResultHolder(net.minecraft.world.InteractionResult.FAIL, itemstack); + } + } + +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (EnderpearlItem.random.nextFloat() * 0.4F + 0.8F)); +- user.getCooldowns().addCooldown(this, 20); +- // CraftBukkit end +- +- user.awardStat(Stats.ITEM_USED.get(this)); +- if (!user.abilities.instabuild) { +- itemstack.shrink(1); +- } ++ // Paper start - moved up ++// world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemEnderPearl.RANDOM.nextFloat() * 0.4F + 0.8F)); ++// entityhuman.getCooldownTracker().setCooldown(this, 20); ++// // CraftBukkit end ++// ++// entityhuman.b(StatisticList.ITEM_USED.b(this)); ++// if (!entityhuman.abilities.canInstantlyBuild) { ++// itemstack.subtract(1); ++// } ++ // Paper end - moved up + + return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); + } +diff --git a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java +index 032c476892c58a873242b00ca71fe084c719dcaa..0bd65165eacf6a54cdcc348991cf9adb44077bee 100644 +--- a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java ++++ b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java +@@ -1,10 +1,13 @@ + package net.minecraft.world.item; + ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.stats.Stats; + import net.minecraft.world.InteractionHand; ++import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; ++import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.ThrownExperienceBottle; + import net.minecraft.world.level.Level; +@@ -24,19 +27,38 @@ public class ExperienceBottleItem extends Item { + public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { + ItemStack itemstack = user.getItemInHand(hand); + +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EXPERIENCE_BOTTLE_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (ExperienceBottleItem.random.nextFloat() * 0.4F + 0.8F)); ++ //world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EXPERIENCE_BOTTLE_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemExpBottle.RANDOM.nextFloat() * 0.4F + 0.8F)); // Paper - moved down + if (!world.isClientSide) { + ThrownExperienceBottle entitythrownexpbottle = new ThrownExperienceBottle(world, user); + + entitythrownexpbottle.setItem(itemstack); + entitythrownexpbottle.shootFromRotation(user, user.xRot, user.yRot, -20.0F, 0.7F, 1.0F); +- world.addFreshEntity(entitythrownexpbottle); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitythrownexpbottle.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entitythrownexpbottle)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ itemstack.shrink(1); ++ } else if (user instanceof ServerPlayer) { ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EXPERIENCE_BOTTLE_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ if (user instanceof ServerPlayer) { ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return new InteractionResultHolder(InteractionResult.FAIL, itemstack); ++ } ++ // Paper end + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- if (!user.abilities.instabuild) { +- itemstack.shrink(1); ++ /* // Paper start - moved up ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); + } +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index 28cffbe299acccc59c34d5dbd2cf458704be5ee5..4a70582bada607e1efcd826d58d725a0cc95e3c3 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import java.util.Arrays; + import java.util.Comparator; + import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; +@@ -29,8 +30,12 @@ public class FireworkRocketItem extends Item { + FireworkRocketEntity entityfireworks = new FireworkRocketEntity(world, context.getPlayer(), vec3d.x + (double) enumdirection.getStepX() * 0.15D, vec3d.y + (double) enumdirection.getStepY() * 0.15D, vec3d.z + (double) enumdirection.getStepZ() * 0.15D, itemstack); + entityfireworks.spawningEntity = context.getPlayer().getUUID(); // Paper + +- world.addFreshEntity(entityfireworks); +- itemstack.shrink(1); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Firework) entityfireworks.getBukkitEntity()); ++ if (!event.callEvent() || !world.addFreshEntity(entityfireworks)) return InteractionResult.PASS; ++ if (event.shouldConsume() && !context.getPlayer().abilities.instabuild) itemstack.shrink(1); ++ else if (context.getPlayer() instanceof ServerPlayer) ((ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory(); ++ // Paper end + } + + return InteractionResult.sidedSuccess(world.isClientSide); +@@ -50,9 +55,9 @@ public class FireworkRocketItem extends Item { + if (event.callEvent() && world.addFreshEntity(entityfireworks)) { + if (event.shouldConsume() && !user.abilities.instabuild) { + itemstack.shrink(1); +- } else ((EntityPlayer) user).getBukkitEntity().updateInventory(); +- } else if (user instanceof EntityPlayer) { +- ((EntityPlayer) user).getBukkitEntity().updateInventory(); ++ } else ((ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } else if (user instanceof ServerPlayer) { ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java +index 813a9c8464e253b9baa15af666111be104b4aa29..1957561f83645a57df5925b4b0b54153ebf4aeef 100644 +--- a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java ++++ b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.InteractionHand; ++import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; +@@ -15,7 +16,12 @@ public class LingeringPotionItem extends ThrowablePotionItem { + + @Override + public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.LINGERING_POTION_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (LingeringPotionItem.random.nextFloat() * 0.4F + 0.8F)); +- return super.use(world, user, hand); ++ // Paper start ++ InteractionResultHolder wrapper = super.use(world, user, hand); ++ if (wrapper.getResult() != InteractionResult.FAIL) { ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.LINGERING_POTION_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (LingeringPotionItem.random.nextFloat() * 0.4F + 0.8F)); ++ } ++ return wrapper; ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java +index 50adbfc5d57b2b5f25c0efdc51da1701197c66e6..b053d105a1513a6b138972cdb9d28116fc6b8c81 100644 +--- a/src/main/java/net/minecraft/world/item/SnowballItem.java ++++ b/src/main/java/net/minecraft/world/item/SnowballItem.java +@@ -26,14 +26,20 @@ public class SnowballItem extends Item { + + entitysnowball.setItem(itemstack); + entitysnowball.shootFromRotation(user, user.xRot, user.yRot, 0.0F, 1.5F, 1.0F); +- if (world.addFreshEntity(entitysnowball)) { +- if (!user.abilities.instabuild) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entitysnowball)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ // Paper end + itemstack.shrink(1); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { // Paper ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); // Paper + } + + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (SnowballItem.random.nextFloat() * 0.4F + 0.8F)); +- } else if (user instanceof net.minecraft.server.level.ServerPlayer) { +- ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } else { // Paper ++ if (user instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); // Paper ++ return new InteractionResultHolder(net.minecraft.world.InteractionResult.FAIL, itemstack); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/SplashPotionItem.java b/src/main/java/net/minecraft/world/item/SplashPotionItem.java +index 9c7547988fe90e9b87868636a1c1a7b4dff32622..6d3ab7fea3b5ebba4c304cdec5dd36dbea31f2c6 100644 +--- a/src/main/java/net/minecraft/world/item/SplashPotionItem.java ++++ b/src/main/java/net/minecraft/world/item/SplashPotionItem.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.InteractionHand; ++import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; +@@ -15,7 +16,12 @@ public class SplashPotionItem extends ThrowablePotionItem { + + @Override + public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { ++ // Paper start ++ InteractionResultHolder wrapper = super.use(world, user, hand); ++ if (wrapper.getResult() != InteractionResult.FAIL) { + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SPLASH_POTION_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (SplashPotionItem.random.nextFloat() * 0.4F + 0.8F)); +- return super.use(world, user, hand); ++ } ++ return wrapper; ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +index 3615bc9222db4489189537f7f5d7a7d338053d6d..e12c7ebc0a4ff4f132512dc1677db7f79db41b03 100644 +--- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java ++++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +@@ -1,7 +1,9 @@ + package net.minecraft.world.item; + ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.stats.Stats; + import net.minecraft.world.InteractionHand; ++import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.ThrownPotion; +@@ -22,13 +24,31 @@ public class ThrowablePotionItem extends PotionItem { + + entitypotion.setItem(itemstack); + entitypotion.shootFromRotation(user, user.xRot, user.yRot, -20.0F, 0.5F, 1.0F); +- world.addFreshEntity(entitypotion); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitypotion.getBukkitEntity()); ++ if (event.callEvent() && world.addFreshEntity(entitypotion)) { ++ if (event.shouldConsume() && !user.abilities.instabuild) { ++ itemstack.shrink(1); ++ } else if (user instanceof ServerPlayer) { ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ if (user instanceof ServerPlayer) { ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return new InteractionResultHolder(InteractionResult.FAIL, itemstack); ++ } ++ // Paper end + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- if (!user.abilities.instabuild) { +- itemstack.shrink(1); ++ /* // Paper start - moved up ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); + } diff --git a/Remapped-Spigot-Server-Patches/0385-Add-CraftMagicNumbers.isSupportedApiVersion.patch b/Remapped-Spigot-Server-Patches/0385-Add-CraftMagicNumbers.isSupportedApiVersion.patch new file mode 100644 index 000000000..509fc426f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0385-Add-CraftMagicNumbers.isSupportedApiVersion.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BlackHole +Date: Sun, 15 Dec 2019 19:12:39 +0100 +Subject: [PATCH] Add CraftMagicNumbers.isSupportedApiVersion() + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 21052d0e88351b075733331d71e07b086354b820..86b319337fc41a09dd45df466df60cadaed1343f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -373,6 +373,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } ++ ++ @Override ++ public boolean isSupportedApiVersion(String apiVersion) { ++ return apiVersion != null && SUPPORTED_API.contains(apiVersion); ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0386-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/Remapped-Spigot-Server-Patches/0386-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch new file mode 100644 index 000000000..32b045db4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0386-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Callahan +Date: Mon, 13 Jan 2020 23:47:28 -0600 +Subject: [PATCH] Prevent sync chunk loads when villagers try to find beds + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java +index 5151c794985a135d3bd794bbafdf524ab9f670de..9a582fb4b96b2d0406cc86e473e8bf8c4e488e37 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java +@@ -46,7 +46,8 @@ public class SleepInBed extends Behavior { + } + } + +- BlockState iblockdata = world.getBlockState(globalpos.getBlockPosition()); ++ BlockState iblockdata = world.getTypeIfLoaded(globalpos.getBlockPosition()); // Paper ++ if (iblockdata == null) { return false; } // Paper + + return globalpos.getBlockPosition().a((Position) entity.position(), 2.0D) && iblockdata.getBlock().is((Tag) BlockTags.BEDS) && !(Boolean) iblockdata.getValue(BedBlock.OCCUPIED); + } diff --git a/Remapped-Spigot-Server-Patches/0387-MC-145656-Fix-Follow-Range-Initial-Target.patch b/Remapped-Spigot-Server-Patches/0387-MC-145656-Fix-Follow-Range-Initial-Target.patch new file mode 100644 index 000000000..27d343fa1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0387-MC-145656-Fix-Follow-Range-Initial-Target.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 18 Dec 2019 22:21:35 -0600 +Subject: [PATCH] MC-145656 Fix Follow Range Initial Target + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e726b6213cf2e8f5b326f05c0438b8f1ee2b73c5..edda2121f8c1046478beaa77030ebb36d403b334 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -580,4 +580,9 @@ public class PaperWorldConfig { + private void pillagerSettings() { + disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); + } ++ ++ public boolean entitiesTargetWithFollowRange = false; ++ private void entitiesTargetWithFollowRange() { ++ entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +index 7bba2ac71a3cd34a06ec865a3c1828b10decd644..93845edab0e1b0e2ad300cad051b0182cadd46e5 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +@@ -32,6 +32,7 @@ public class NearestAttackableTargetGoal extends TargetG + this.randomInterval = reciprocalChance; + this.setFlags(EnumSet.of(Goal.Flag.TARGET)); + this.targetConditions = (new TargetingConditions()).range(this.getFollowDistance()).selector(targetPredicate); ++ if (mob.level.paperConfig.entitiesTargetWithFollowRange) this.targetConditions.useFollowRange(); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +index 1714507fa744b2767e8a66cdb5db7f43c21f5c56..e1a0104a3b52990a83e7732491029d8a20976dc3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java ++++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +@@ -4,6 +4,8 @@ import java.util.function.Predicate; + import javax.annotation.Nullable; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; ++import net.minecraft.world.entity.ai.attributes.AttributeInstance; ++import net.minecraft.world.entity.ai.attributes.Attributes; + + public class TargetingConditions { + +@@ -82,7 +84,7 @@ public class TargetingConditions { + + if (this.range > 0.0D) { + double d0 = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D; +- double d1 = Math.max(this.range * d0, 2.0D); ++ double d1 = Math.max((useFollowRange ? getFollowRange(baseEntity) : this.range) * d0, 2.0D); // Paper + double d2 = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + + if (d2 > d1 * d1) { +@@ -98,4 +100,18 @@ public class TargetingConditions { + return true; + } + } ++ ++ // Paper start ++ private boolean useFollowRange = false; ++ ++ public TargetingConditions useFollowRange() { ++ this.useFollowRange = true; ++ return this; ++ } ++ ++ private double getFollowRange(LivingEntity entityliving) { ++ AttributeInstance attributeinstance = entityliving.getAttribute(Attributes.FOLLOW_RANGE); ++ return attributeinstance == null ? 16.0D : attributeinstance.getValue(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0388-Optimize-Hoppers.patch b/Remapped-Spigot-Server-Patches/0388-Optimize-Hoppers.patch new file mode 100644 index 000000000..5d342ad55 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0388-Optimize-Hoppers.patch @@ -0,0 +1,605 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Apr 2016 22:09:52 -0400 +Subject: [PATCH] Optimize Hoppers + +* Removes unnecessary extra calls to .update() that are very expensive +* Lots of itemstack cloning removed. Only clone if the item is actually moved +* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. + However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. +* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory +* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration +* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried) +* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins) + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index edda2121f8c1046478beaa77030ebb36d403b334..7fbd501d70dccf869a4454e2789a5d68f2e15754 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -585,4 +585,13 @@ public class PaperWorldConfig { + private void entitiesTargetWithFollowRange() { + entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); + } ++ ++ public boolean cooldownHopperWhenFull = true; ++ public boolean disableHopperMoveEvents = false; ++ private void hopperOptimizations() { ++ cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull); ++ log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled")); ++ disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents); ++ log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled")); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 753e6f609189c589514739bea80007bace3c89d2..7038897b8fb4c18ca97b95a3b24c30b40b62b005 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -131,6 +131,7 @@ import net.minecraft.world.level.LevelSettings; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.biome.BiomeSource; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.HopperBlockEntity; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.dimension.DimensionType; +@@ -1360,6 +1361,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper ++ HopperBlockEntity.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 02bfa4fb8055e60a84e878ffbf18303c0ee25b1d..ac996d581925c8f92832009945c766962e5b51c5 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -538,11 +538,12 @@ public final class ItemStack { + return this.getItem().interactLivingEntity(this, user, entity, hand); + } + +- public ItemStack copy() { +- if (this.isEmpty()) { ++ public ItemStack copy() { return cloneItemStack(false); } // Paper ++ public ItemStack cloneItemStack(boolean origItem) { // Paper ++ if (!origItem && this.isEmpty()) { // Paper + return ItemStack.EMPTY; + } else { +- ItemStack itemstack = new ItemStack(this.getItem(), this.count); ++ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper + + itemstack.setPopTime(this.getPopTime()); + if (this.tag != null) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 3e2cd6c7a34c1a792d7346019a8b039d1f4a7c04..6b79f8cd9258af47afa6efa7b1f97c3780be58b0 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1162,8 +1162,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return list; + } + +- @Override +- public List getEntitiesOfClass(Class entityClass, AABB box, @Nullable Predicate predicate) { ++ public List getEntities(Class oclass, AABB axisalignedbb, @Nullable Predicate predicate) { return getEntitiesOfClass(oclass, axisalignedbb, predicate); } // Paper - OBFHELPER ++ @Override public List getEntitiesOfClass(Class entityClass, AABB box, @Nullable Predicate predicate) { + this.getProfiler().incrementCounter("getEntities"); + int i = Mth.floor((box.minX - 2.0D) / 16.0D); + int j = Mth.ceil((box.maxX + 2.0D) / 16.0D); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 84012c2d12817e657b046bc168cc8eddebcd3831..05fa76c02ce61e26891ad995fe89e925ea086557 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -77,6 +77,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + public void setCurrentChunk(LevelChunk chunk) { + this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; + } ++ static boolean IGNORE_TILE_UPDATES = false; + // Paper end + + @Nullable +@@ -155,6 +156,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + + public void setChanged() { + if (this.level != null) { ++ if (IGNORE_TILE_UPDATES) return; // Paper + this.blockState = this.level.getBlockState(this.worldPosition); + this.level.blockEntityChanged(this.worldPosition, this); + if (!this.blockState.isAir()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java +index f8e4a42bed265822666141683e36e6696694925b..fc8bb72f7d677f65db505016ad6a4cd6146de29f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.block.entity; + + import javax.annotation.Nullable; ++import net.minecraft.core.BlockPos; + import net.minecraft.world.Container; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; +@@ -17,12 +18,13 @@ public interface Hopper extends Container { + return Hopper.SUCK; + } + +- @Nullable ++ //@Nullable // Paper - it's annoying + Level getLevel(); ++ default BlockPos getBlockPosition() { return new BlockPos(getX(), getY(), getZ()); } // Paper + +- double getLevelX(); ++ double getLevelX(); default double getX() { return this.getLevelX(); } // Paper - OBFHELPER + +- double getLevelY(); ++ double getLevelY(); default double getY() { return this.getLevelY(); } // Paper - OBFHELPER + +- double getLevelZ(); ++ double getLevelZ(); default double getZ() { return this.getLevelZ(); } // Paper - OBFHELPER + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 04b0f0de43dfd95e82d402068da8a97bdb86f758..70718fcbaa6f671061479957b7608f7639dab54b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -193,6 +193,160 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return false; + } + ++ // Paper start - Optimize Hoppers ++ private static boolean skipPullModeEventFire = false; ++ private static boolean skipPushModeEventFire = false; ++ public static boolean skipHopperEvents = false; ++ ++ private boolean hopperPush(Container iinventory, Direction enumdirection) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < this.getContainerSize(); ++i) { ++ ItemStack item = this.getItem(i); ++ if (!item.isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = item; ++ ItemStack itemstack = origItemStack; ++ ++ final int origCount = origItemStack.getCount(); ++ final int moved = Math.min(level.spigotConfig.hopperAmount, origCount); ++ origItemStack.setCount(moved); ++ ++ // We only need to fire the event once to give protection plugins a chance to cancel this event ++ // Because nothing uses getItem, every event call should end up the same result. ++ if (!skipPushModeEventFire) { ++ itemstack = callPushMoveEvent(iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ return false; ++ } ++ } ++ final ItemStack itemstack2 = addItem(this, iinventory, itemstack, enumdirection); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(true); ++ origItemStack.setCount(origCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(origCount - moved + remaining); ++ } ++ this.setItem(i, origItemStack); ++ iinventory.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ } ++ } ++ if (foundItem && level.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown ++ this.setCooldown(level.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(Hopper ihopper, Container iinventory, ItemStack origItemStack, int i) { ++ ItemStack itemstack = origItemStack; ++ final int origCount = origItemStack.getCount(); ++ final Level world = ihopper.getLevel(); ++ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount); ++ itemstack.setCount(moved); ++ ++ if (!skipPullModeEventFire) { ++ itemstack = callPullMoveEvent(ihopper, iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ // Drastically improve performance by returning true. ++ // No plugin could of relied on the behavior of false as the other call ++ // site for IMIE did not exhibit the same behavior ++ return true; ++ } ++ } ++ ++ final ItemStack itemstack2 = addItem(iinventory, ihopper, itemstack, null); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(true); ++ origItemStack.setCount(origCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(origCount - moved + remaining); ++ } ++ IGNORE_TILE_UPDATES = true; ++ iinventory.setItem(i, origItemStack); ++ IGNORE_TILE_UPDATES = false; ++ iinventory.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ ++ if (world.paperConfig.cooldownHopperWhenFull) { ++ cooldownHopper(ihopper); ++ } ++ ++ return false; ++ } ++ ++ private ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack) { ++ Inventory destinationInventory = getInventory(iinventory); ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(this); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static ItemStack callPullMoveEvent(Hopper hopper, Container iinventory, ItemStack itemstack) { ++ Inventory sourceInventory = getInventory(iinventory); ++ Inventory destination = getInventory(hopper); ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, ++ // Mirror is safe as we no plugins ever use this item ++ CraftItemStack.asCraftMirror(itemstack), destination, false); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPullModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static Inventory getInventory(Container iinventory) { ++ Inventory sourceInventory;// Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else if (iinventory instanceof BlockEntity) { ++ sourceInventory = ((BlockEntity) iinventory).getOwner(false).getInventory(); ++ } else { ++ sourceInventory = iinventory.getOwner().getInventory(); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(Hopper hopper) { ++ if (hopper instanceof HopperBlockEntity) { ++ ((HopperBlockEntity) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer); ++ } else if (hopper instanceof MinecartHopper) { ++ ((MinecartHopper) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer / 2); ++ } ++ } ++ // Paper end ++ + private boolean ejectItems() { + Container iinventory = this.getAttachedContainer(); + +@@ -204,27 +358,28 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (this.isFullContainer(iinventory, enumdirection)) { + return false; + } else { +- for (int i = 0; i < this.getContainerSize(); ++i) { ++ return hopperPush(iinventory, enumdirection); /* // Paper - disable rest ++ for (int i = 0; i < this.getSize(); ++i) { + if (!this.getItem(i).isEmpty()) { +- ItemStack itemstack = this.getItem(i).copy(); ++ ItemStack itemstack = this.getItem(i).cloneItemStack(); + // ItemStack itemstack1 = addItem(this, iinventory, this.splitStack(i, 1), enumdirection); + + // CraftBukkit start - Call event when pushing items into other inventories +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.removeItem(i, level.spigotConfig.hopperAmount)); // Spigot ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.splitStack(i, world.spigotConfig.hopperAmount)); // Spigot + + Inventory destinationInventory; + // Have to special case large chests as they work oddly +- if (iinventory instanceof CompoundContainer) { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ if (iinventory instanceof InventoryLargeChest) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); + } else { + destinationInventory = iinventory.getOwner().getInventory(); + } + + InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); +- this.getLevel().getCraftServer().getPluginManager().callEvent(event); ++ this.getWorld().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.setItem(i, itemstack); +- this.setCooldown(level.spigotConfig.hopperTransfer); // Spigot ++ this.setCooldown(world.spigotConfig.hopperTransfer); // Spigot + return false; + } + int origCount = event.getItem().getAmount(); // Spigot +@@ -232,16 +387,16 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + // CraftBukkit end + + if (itemstack1.isEmpty()) { +- iinventory.setChanged(); ++ iinventory.update(); + return true; + } + +- itemstack.shrink(origCount - itemstack1.getCount()); // Spigot ++ itemstack.subtract(origCount - itemstack1.getCount()); // Spigot + this.setItem(i, itemstack); + } + } + +- return false; ++ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + } + } +@@ -250,18 +405,54 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return inventory instanceof WorldlyContainer ? IntStream.of(((WorldlyContainer) inventory).getSlotsForFace(side)) : IntStream.range(0, inventory.getContainerSize()); + } + +- private boolean isFullContainer(Container inv, Direction enumdirection) { +- return getSlots(inv, enumdirection).allMatch((i) -> { +- ItemStack itemstack = inv.getItem(i); ++ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } + +- return itemstack.getCount() >= itemstack.getMaxStackSize(); +- }); ++ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } ++ return true; ++ } ++ private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); ++ private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); ++ ++ // Paper end ++ ++ private boolean isFullContainer(Container inv, Direction enumdirection) { ++ // Paper start - no streams ++ return allMatch(inv, enumdirection, STACK_SIZE_TEST); ++ // Paper end + } + + private static boolean isEmptyContainer(Container inv, Direction facing) { +- return getSlots(inv, facing).allMatch((i) -> { +- return inv.getItem(i).isEmpty(); +- }); ++ return allMatch(inv, facing, IS_EMPTY_TEST); + } + + public static boolean suckInItems(Hopper hopper) { +@@ -270,9 +461,17 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (iinventory != null) { + Direction enumdirection = Direction.DOWN; + +- return isEmptyContainer(iinventory, enumdirection) ? false : getSlots(iinventory, enumdirection).anyMatch((i) -> { +- return tryTakeInItemFromSlot(hopper, iinventory, i, enumdirection); ++ // Paper start - optimize hoppers and remove streams ++ skipPullModeEventFire = skipHopperEvents; ++ return !isEmptyContainer(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> { ++ // Logic copied from below to avoid extra getItem calls ++ if (!item.isEmpty() && canTakeItem(iinventory, item, i, enumdirection)) { ++ return hopperPull(hopper, iinventory, item, i); ++ } else { ++ return false; ++ } + }); ++ // Paper end + } else { + Iterator iterator = getItemsAtAndAbove(hopper).iterator(); + +@@ -290,47 +489,48 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) { ++ private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) {// Paper - method unused as logic is inlined above + ItemStack itemstack = inventory.getItem(slot); + +- if (!itemstack.isEmpty() && canTakeItemFromContainer(inventory, itemstack, slot, side)) { +- ItemStack itemstack1 = itemstack.copy(); ++ if (!itemstack.isEmpty() && canTakeItemFromContainer(inventory, itemstack, slot, side)) { // If this logic changes, update above. this is left inused incase reflective plugins ++ return hopperPull(hopper, inventory, itemstack, slot); /* // Paper - disable rest ++ ItemStack itemstack1 = itemstack.cloneItemStack(); + // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null); + // CraftBukkit start - Call event on collection of items from inventories into the hopper +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(inventory.removeItem(slot, hopper.getLevel().spigotConfig.hopperAmount)); // Spigot ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.splitStack(i, ihopper.getWorld().spigotConfig.hopperAmount)); // Spigot + + Inventory sourceInventory; + // Have to special case large chests as they work oddly +- if (inventory instanceof CompoundContainer) { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) inventory); ++ if (iinventory instanceof InventoryLargeChest) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); + } else { +- sourceInventory = inventory.getOwner().getInventory(); ++ sourceInventory = iinventory.getOwner().getInventory(); + } + +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), hopper.getOwner().getInventory(), false); ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); + +- hopper.getLevel().getCraftServer().getPluginManager().callEvent(event); ++ ihopper.getWorld().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { +- inventory.setItem(slot, itemstack1); ++ iinventory.setItem(i, itemstack1); + +- if (hopper instanceof HopperBlockEntity) { +- ((HopperBlockEntity) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer); // Spigot +- } else if (hopper instanceof MinecartHopper) { +- ((MinecartHopper) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer / 2); // Spigot ++ if (ihopper instanceof TileEntityHopper) { ++ ((TileEntityHopper) ihopper).setCooldown(ihopper.getWorld().spigotConfig.hopperTransfer); // Spigot ++ } else if (ihopper instanceof EntityMinecartHopper) { ++ ((EntityMinecartHopper) ihopper).setCooldown(ihopper.getWorld().spigotConfig.hopperTransfer / 2); // Spigot + } + return false; + } + int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemstack2 = addItem(inventory, hopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ ItemStack itemstack2 = addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); + // CraftBukkit end + + if (itemstack2.isEmpty()) { +- inventory.setChanged(); ++ iinventory.update(); + return true; + } + +- itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot +- inventory.setItem(slot, itemstack1); ++ itemstack1.subtract(origCount - itemstack2.getCount()); // Spigot ++ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + + return false; +@@ -339,7 +539,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container inventory, ItemEntity itemEntity) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation + itemEntity.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; +@@ -381,6 +581,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return !inventory.canPlaceItem(slot, stack) ? false : !(inventory instanceof WorldlyContainer) || ((WorldlyContainer) inventory).canPlaceItemThroughFace(slot, stack, side); + } + ++ private static boolean canTakeItem(Container iinventory, ItemStack itemstack, int i, Direction enumdirection) { return canTakeItemFromContainer(iinventory, itemstack, i, enumdirection); } // Paper - OBFHELPER + private static boolean canTakeItemFromContainer(Container inv, ItemStack stack, int slot, Direction facing) { + return !(inv instanceof WorldlyContainer) || ((WorldlyContainer) inv).canTakeItemThroughFace(slot, stack, facing); + } +@@ -393,7 +594,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + boolean flag1 = to.isEmpty(); + + if (itemstack1.isEmpty()) { ++ IGNORE_TILE_UPDATES = true; // Paper + to.setItem(slot, stack); ++ IGNORE_TILE_UPDATES = false; // Paper + stack = ItemStack.EMPTY; + flag = true; + } else if (canMergeItems(itemstack1, stack)) { +@@ -444,20 +647,26 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + public static List getItemsAtAndAbove(Hopper ihopper) { +- return (List) ihopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { +- return ihopper.getLevel().getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(ihopper.getLevelX() - 0.5D, ihopper.getLevelY() - 0.5D, ihopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); +- }).collect(Collectors.toList()); ++ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?! ++ Level world = ihopper.getLevel(); ++ double d0 = ihopper.getX(); ++ double d1 = ihopper.getY(); ++ double d2 = ihopper.getZ(); ++ AABB bb = new AABB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D); ++ return world.getEntities(ItemEntity.class, bb, Entity::isAlive); ++ // Paper end + } + + @Nullable + public static Container getContainerAt(Level world, BlockPos blockposition) { +- return getContainerAt(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D); ++ return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, true); // Paper + } + + @Nullable +- public static Container getContainerAt(Level world, double x, double y, double z) { ++ public static Container getContainerAt(Level world, double x, double y, double z) { return a(world, x, y, z, false); } // Paper - overload to default false ++ public static Container a(Level world, double d0, double d1, double d2, boolean optimizeEntities) { // Paper + Object object = null; +- BlockPos blockposition = new BlockPos(x, y, z); ++ BlockPos blockposition = new BlockPos(d0, d1, d2); + if ( !world.hasChunkAt( blockposition ) ) return null; // Spigot + BlockState iblockdata = world.getBlockState(blockposition); + Block block = iblockdata.getBlock(); +@@ -475,8 +684,8 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- if (object == null) { +- List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); ++ if (object == null && (!optimizeEntities || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper ++ List list = world.getEntities((Entity) null, new AABB(d0 - 0.5D, d1 - 0.5D, d2 - 0.5D, d0 + 0.5D, d1 + 0.5D, d2 + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); + + if (!list.isEmpty()) { + object = (Container) list.get(world.random.nextInt(list.size())); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index 5ad419941ff1113ef29b9a4593f44d8f35ba8424..4525032232b5a89de13c6a46dc489a07428e3f21 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -97,12 +97,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + @Override + public boolean isEmpty() { + this.unpackLootTable((Player) null); +- return this.getItems().stream().allMatch(ItemStack::isEmpty); ++ // Paper start ++ for (ItemStack itemStack : this.getItems()) { ++ if (!itemStack.isEmpty()) { ++ return false; ++ } ++ } ++ // Paper end ++ return true; + } + + @Override + public ItemStack getItem(int slot) { +- this.unpackLootTable((Player) null); ++ if (slot == 0) this.unpackLootTable((Player) null); // Paper + return (ItemStack) this.getItems().get(slot); + } + diff --git a/Remapped-Spigot-Server-Patches/0389-PlayerDeathEvent-shouldDropExperience.patch b/Remapped-Spigot-Server-Patches/0389-PlayerDeathEvent-shouldDropExperience.patch new file mode 100644 index 000000000..4f453dc68 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0389-PlayerDeathEvent-shouldDropExperience.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 24 Dec 2019 00:35:42 +0000 +Subject: [PATCH] PlayerDeathEvent#shouldDropExperience + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 2ef273e3b917803f3e2ac3c6a22d92a15b9eb71a..7f4e81ee3339e90b8525541dccf6dea187853cf7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -819,7 +819,7 @@ public class ServerPlayer extends Player implements ContainerListener { + this.tellNeutralMobsThatIDied(); + } + // SPIGOT-5478 must be called manually now +- this.dropExperience(); ++ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { + // Paper start - replace logic diff --git a/Remapped-Spigot-Server-Patches/0390-Prevent-bees-loading-chunks-checking-hive-position.patch b/Remapped-Spigot-Server-Patches/0390-Prevent-bees-loading-chunks-checking-hive-position.patch new file mode 100644 index 000000000..9a427d5aa --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0390-Prevent-bees-loading-chunks-checking-hive-position.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Jan 2020 17:24:34 -0600 +Subject: [PATCH] Prevent bees loading chunks checking hive position + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index 32ee38142a3053091ab7b3fb3d608d268b07d4e3..edd6d63f715acef1a77eba0cf46ff8267228f4c6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -442,6 +442,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + if (!this.hasHive()) { + return false; + } else { ++ if (level.getChunkIfLoadedImmediately(hivePos.getX() >> 4, hivePos.getZ() >> 4) == null) return true; // Paper - just assume the hive is still there, no need to load the chunk(s) + BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); + + return tileentity != null && tileentity.getType() == BlockEntityType.BEEHIVE; diff --git a/Remapped-Spigot-Server-Patches/0391-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/Remapped-Spigot-Server-Patches/0391-Don-t-load-Chunks-from-Hoppers-and-other-things.patch new file mode 100644 index 000000000..f6952f625 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0391-Don-t-load-Chunks-from-Hoppers-and-other-things.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Nov 2016 20:28:12 -0400 +Subject: [PATCH] Don't load Chunks from Hoppers and other things + +Hoppers call this to I guess "get the primary side" of a double sided chest. + +If the double sided chest crosses chunk lines, it causes the chunk to load. +This will end up causing sync chunk loads, which will unload with Chunk GC, +only to be reloaded again the next tick. + +This of course is undesirable, so just return the loaded side as "primary" +and treat it as a single chest if the other sides are unloaded + +diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +index a4f16b2093c867e9fd1c2e07b67c49c3c5ec7506..df20b3616929657d2e8061159ed97f500b33d192 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java ++++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +@@ -29,7 +29,12 @@ public class DoubleBlockCombiner { + return new DoubleBlockCombiner.NeighborCombineResult.Single<>(s0); + } else { + BlockPos blockposition1 = pos.relative((Direction) function1.apply(state)); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ // Paper start ++ BlockState iblockdata1 = world.getTypeIfLoaded(blockposition1); ++ if (iblockdata1 == null) { ++ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(s0); ++ } ++ // Paper end + + if (iblockdata1.is(state.getBlock())) { + DoubleBlockCombiner.BlockType doubleblockfinder_blocktype1 = (DoubleBlockCombiner.BlockType) typeMapper.apply(iblockdata1); diff --git a/Remapped-Spigot-Server-Patches/0392-Guard-against-serializing-mismatching-chunk-coordina.patch b/Remapped-Spigot-Server-Patches/0392-Guard-against-serializing-mismatching-chunk-coordina.patch new file mode 100644 index 000000000..37dc33fc4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0392-Guard-against-serializing-mismatching-chunk-coordina.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 27 Dec 2019 09:42:26 -0800 +Subject: [PATCH] Guard against serializing mismatching chunk coordinate + +Should help if something dumb happens + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index be67dc16bf70e4517efd213ca9002f116f60b57c..6c28a611b9b79c3322ab07883972c07b3bfc3073 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -67,6 +67,13 @@ public class ChunkSerializer { + + private static final Logger LOGGER = LogManager.getLogger(); + ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkPos getChunkCoordinate(CompoundTag chunkData) { ++ CompoundTag levelData = chunkData.getCompound("Level"); ++ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } ++ // Paper end + // Paper start + public static final class InProgressChunkHolder { + +@@ -92,8 +99,8 @@ public class ChunkSerializer { + // Paper end + ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator(); + BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource(); +- CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Level"); +- ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); ++ CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Level"); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate ++ ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate + + if (!Objects.equals(chunkcoordintpair, chunkcoordintpair1)) { + ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", chunkcoordintpair, chunkcoordintpair, chunkcoordintpair1); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 684442b7175e30b6d4cafb2f7d2d4c10517cc33d..1af804c5c6fb2b20ea3f020610763c1d7dcee110 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -13,6 +13,7 @@ import net.minecraft.SharedConstants; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.datafix.DataFixTypes; +@@ -119,6 +120,13 @@ public class ChunkStorage implements AutoCloseable { + + public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { write(chunkcoordintpair, nbttagcompound); } // Paper OBFHELPER + public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { // Paper - OBFHELPER - (Switched around for safety) ++ // Paper start ++ if (!chunkcoordintpair.equals(ChunkSerializer.getChunkCoordinate(nbttagcompound))) { ++ String world = (this instanceof ChunkMap) ? ((ChunkMap)this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkcoordintpair.toString() ++ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbttagcompound).toString() + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ // Paper end + this.regionFileCache.write(chunkcoordintpair, nbttagcompound); + if (this.legacyStructureHandler != null) { + synchronized (this.persistentDataLock) { // Paper - Async chunk loading diff --git a/Remapped-Spigot-Server-Patches/0393-Optimise-IEntityAccess-getPlayerByUUID.patch b/Remapped-Spigot-Server-Patches/0393-Optimise-IEntityAccess-getPlayerByUUID.patch new file mode 100644 index 000000000..e478c7d03 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0393-Optimise-IEntityAccess-getPlayerByUUID.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jan 2020 21:50:56 -0800 +Subject: [PATCH] Optimise IEntityAccess#getPlayerByUUID + +Use the world entity map instead of iterating over all players + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 01f879a8dd0e1ffec380e02072567330152eaceb..40d7dbc4f1deda88d4a539b89d84b595217051b6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -283,6 +283,15 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + // Paper end + ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ Entity player = this.entitiesByUuid.get(uuid); ++ return (player instanceof Player) ? (Player)player : null; ++ } ++ // Paper end ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env, executor); // Paper pass executor +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 6a5430fe54a5c8ad119a0f3842961825a54d8d7a..b9606465ace8b320eafbbad3d60c01b87a859c44 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -277,6 +277,12 @@ public interface EntityGetter { + + @Nullable + default Player getPlayerByUUID(UUID uuid) { ++ // Paper start - allow WorldServer to override ++ return this.getPlayerByUUID(uuid); ++ } ++ @Nullable ++ default Player getPlayerByUUID(UUID uuid) { ++ // Paper end + for (int i = 0; i < this.players().size(); ++i) { + Player entityhuman = (Player) this.players().get(i); + diff --git a/Remapped-Spigot-Server-Patches/0394-Fix-items-not-falling-correctly.patch b/Remapped-Spigot-Server-Patches/0394-Fix-items-not-falling-correctly.patch new file mode 100644 index 000000000..f1ecdfe62 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0394-Fix-items-not-falling-correctly.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AJMFactsheets +Date: Fri, 17 Jan 2020 17:17:54 -0600 +Subject: [PATCH] Fix items not falling correctly + +Since 1.14, Mojang has added an optimization which skips checking if +an item should fall every fourth tick. + +However, Spigot's entity activation range class also has an +optimization which skips ticking active entities every fourth tick. +This can result in a state where an item will never properly fall +due to its move method never being called. + +This patch resolves the conflict by offsetting checking an item's +move method from Spigot's entity activation range check. + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 96b8102773cbee2c3fe2711008ba1487084d67b0..9311f9f411d09d4460f0be8235957fab9e195b7a 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -115,7 +115,7 @@ public class ItemEntity extends Entity { + } + } + +- if (!this.onGround || getHorizontalDistanceSqr(this.getDeltaMovement()) > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { ++ if (!this.onGround || getHorizontalDistanceSqr(this.getDeltaMovement()) > 9.999999747378752E-6D || this.tickCount % 4 == 0) { // Paper - Ensure checking item movement is always offset from Spigot's entity activation range check + this.move(MoverType.SELF, this.getDeltaMovement()); + float f1 = 0.98F; + diff --git a/Remapped-Spigot-Server-Patches/0395-Lag-compensate-eating.patch b/Remapped-Spigot-Server-Patches/0395-Lag-compensate-eating.patch new file mode 100644 index 000000000..cc90ba965 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0395-Lag-compensate-eating.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 14 Jan 2020 15:28:28 -0800 +Subject: [PATCH] Lag compensate eating + +When the server is lagging, players will wait longer when eating. +Change to also use a time check instead if it passes. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index b49d4772932a58852b3195f5f56ff93dbcabf766..016fcc4ae20e1e48728a848be28633e624ae49a7 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -211,7 +211,7 @@ public abstract class LivingEntity extends Entity { + private int noJumpDelay; + private float absorptionAmount; + public ItemStack useItem; // Paper - public +- protected int useItemRemaining; ++ protected int useItemRemaining; protected final int getEatTimeTicks() { return this.useItemRemaining; } protected final void setEatTimeTicks(int value) { this.useItemRemaining = value; } // Paper - OBFHELPER + protected int fallFlyTicks; + private BlockPos lastPos; + private Optional lastClimbablePos; +@@ -3148,6 +3148,11 @@ public abstract class LivingEntity extends Entity { + return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; + } + ++ // Paper start - lag compensate eating ++ protected long eatStartTime; ++ protected int totalEatTimeTicks; ++ // Paper end ++ + private void updatingUsingItem() { + if (this.isUsingItem()) { + if (ItemStack.isSameIgnoreDurability(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { +@@ -3157,7 +3162,12 @@ public abstract class LivingEntity extends Entity { + this.triggerItemUseEffects(this.useItem, 5); + } + +- if (--this.useItemRemaining == 0 && !this.level.isClientSide && !this.useItem.useOnRelease()) { ++ // Paper start - lag compensate eating ++ // we add 1 to the expected time to avoid lag compensating when we should not ++ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); ++ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level.isClientSide && !this.useItem.useOnRelease()) { ++ this.setEatTimeTicks(0); ++ // Paper end + this.completeUsingItem(); + } + } else { +@@ -3207,7 +3217,10 @@ public abstract class LivingEntity extends Entity { + + if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag + this.useItem = itemstack; +- this.useItemRemaining = itemstack.getUseDuration(); ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(); ++ this.eatStartTime = System.nanoTime(); ++ // Paper end + if (!this.level.isClientSide) { + this.setLivingEntityFlag(1, true); + this.setLivingEntityFlag(2, enumhand == InteractionHand.OFF_HAND); +@@ -3231,7 +3244,10 @@ public abstract class LivingEntity extends Entity { + } + } else if (!this.isUsingItem() && !this.useItem.isEmpty()) { + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + } + +@@ -3359,7 +3375,10 @@ public abstract class LivingEntity extends Entity { + } + + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + + public boolean isBlocking() { diff --git a/Remapped-Spigot-Server-Patches/0396-Optimize-call-to-getFluid-for-explosions.patch b/Remapped-Spigot-Server-Patches/0396-Optimize-call-to-getFluid-for-explosions.patch new file mode 100644 index 000000000..9e3f2b347 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0396-Optimize-call-to-getFluid-for-explosions.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Tue, 14 Jan 2020 17:49:03 -0500 +Subject: [PATCH] Optimize call to getFluid for explosions + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 45a75f7be308678336e192828becf6cf5c9047bc..667a6d645034c67639c01b8221591877bcb87b35 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -151,7 +151,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = new BlockPos(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); +- FluidState fluid = this.level.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper + Optional optional = this.damageCalculator.a(this, this.level, blockposition, iblockdata, fluid); + + if (optional.isPresent()) { diff --git a/Remapped-Spigot-Server-Patches/0397-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/Remapped-Spigot-Server-Patches/0397-Fix-last-firework-in-stack-not-having-effects-when-d.patch new file mode 100644 index 000000000..63271d423 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0397-Fix-last-firework-in-stack-not-having-effects-when-d.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 17 Jan 2020 18:44:55 -0800 +Subject: [PATCH] Fix last firework in stack not having effects when dispensed + - #2871 + +CB used the resulting item in the dispenser rather than the item +dispensed. The resulting item would have size == 0 and therefore +be convertered to air, hence why the effects disappeared. + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 67a894a185a3d4a53b3c7f90174b2604dff18257..67d140dff483bfc654a0390e0cdcd13aa658a62d 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -425,7 +425,7 @@ public interface DispenseItemBehavior { + } + + itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); +- FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), stack, pointer.x(), pointer.y(), pointer.x(), true); ++ FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), itemstack1, pointer.x(), pointer.y(), pointer.x(), true); // Paper - GH-2871 - fix last firework in stack having no effects when dispensed + + DispenseItemBehavior.setEntityPokingOutOfBlock(pointer, entityfireworks, enumdirection); + entityfireworks.shoot((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), 0.5F, 1.0F); diff --git a/Remapped-Spigot-Server-Patches/0398-Add-effect-to-block-break-naturally.patch b/Remapped-Spigot-Server-Patches/0398-Add-effect-to-block-break-naturally.patch new file mode 100644 index 000000000..e51ec69f4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0398-Add-effect-to-block-break-naturally.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jan 2020 12:25:07 -0600 +Subject: [PATCH] Add effect to block break naturally + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5bff313dbbb3049105874846d995883e827fbc00..05f0833f762436bf8f5f5875c7e3cfed1da11e1c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -630,6 +630,13 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally(ItemStack item) { ++ // Paper start ++ return breakNaturally(item, false); ++ } ++ ++ @Override ++ public boolean breakNaturally(ItemStack item, boolean triggerEffect) { ++ // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); +@@ -639,6 +646,7 @@ public class CraftBlock implements Block { + // Modelled off EntityHuman#hasBlock + if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { + net.minecraft.world.level.block.Block.dropResources(iblockdata, world.getLevel(), position, world.getBlockEntity(position), null, nmsItem); ++ if (triggerEffect) world.levelEvent(org.bukkit.Effect.STEP_SOUND.getId(), position, net.minecraft.world.level.block.Block.getId(block.defaultBlockState())); // Paper + result = true; + } + diff --git a/Remapped-Spigot-Server-Patches/0399-Tracking-Range-Improvements.patch b/Remapped-Spigot-Server-Patches/0399-Tracking-Range-Improvements.patch new file mode 100644 index 000000000..ec1d0a178 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0399-Tracking-Range-Improvements.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 21 Dec 2019 15:22:09 -0500 +Subject: [PATCH] Tracking Range Improvements + +Sets tracking range of watermobs to animals instead of misc and simplifies code + +Also ignores Enderdragon, defaulting it to Mojang's setting + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 190ddd4d9ef3472c33d46c2ead72fa0dc918054a..6da406c8403797a1cd9276ac06577c3c080a8a22 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1795,6 +1795,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getType().clientTrackingRange() * 16; ++ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + + if (j > i) { + i = j; +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 8e3e36a8739a7dea1feb3785e96b7b9f19720446..b03fa9024c7f0238e1379f6ae4486db5300a70e9 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -6,7 +6,6 @@ import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.monster.Ghast; + + public class TrackingRange + { +@@ -25,26 +24,26 @@ public class TrackingRange + if ( entity instanceof ServerPlayer ) + { + return config.playerTrackingRange; +- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) +- { +- return config.monsterTrackingRange; +- } else if ( entity instanceof Ghast ) +- { +- if ( config.monsterTrackingRange > config.monsterActivationRange ) +- { ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: + return config.monsterTrackingRange; +- } else +- { +- return config.monsterActivationRange; +- } +- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) +- { +- return config.animalTrackingRange; +- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return config.animalTrackingRange; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end + { + return config.miscTrackingRange; + } else + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } diff --git a/Remapped-Spigot-Server-Patches/0400-Entity-Activation-Range-2.0.patch b/Remapped-Spigot-Server-Patches/0400-Entity-Activation-Range-2.0.patch new file mode 100644 index 000000000..ad4d82992 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0400-Entity-Activation-Range-2.0.patch @@ -0,0 +1,908 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 13 May 2016 01:38:06 -0400 +Subject: [PATCH] Entity Activation Range 2.0 + +Optimizes performance of Activation Range + +Adds many new configurations and a new wake up inactive system + +Fixes and adds new Immunities to improve gameplay behavior + +Adds water Mobs to activation range config and nerfs fish +Adds flying monsters to control ghast and phantoms +Adds villagers as separate config + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 40d7dbc4f1deda88d4a539b89d84b595217051b6..bf1bb1530037ebcacc8d5a491789909bddb8b697 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -855,17 +855,17 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper +- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { +- entity.tickCount++; +- timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings ++ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below ++ entity.ticksLived++; ++ timer = entity.getEntityType().inactiveTickTimer.startTiming(); try { // Paper - timings + entity.inactiveTick(); + } finally { timer.stopTiming(); } // Paper + return; +- } ++ }*/ // Paper - comment out EAR 2 + // Spigot end + // Paper start- timings +- TimingHistory.activatedEntityTicks++; +- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); ++ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper + try { + // Paper end - timings + entity.setPosAndOldPos(entity.getX(), entity.getY(), entity.getZ()); +@@ -879,12 +879,16 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return Registry.ENTITY_TYPE.getKey(entity.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickNonPassenger"); ++ if (isActive) { // Paper - EAR 2 ++ TimingHistory.activatedEntityTicks++; // Paper + entity.tick(); + entity.postTick(); // CraftBukkit ++ } else { entity.inactiveTick(); } // Paper - EAR 2 + gameprofilerfiller.pop(); + } + + this.updateChunkPos(entity); ++ } finally { timer.stopTiming(); } // Paper - timings + if (entity.inChunk) { + Iterator iterator = entity.getPassengers().iterator(); + +@@ -894,7 +898,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.tickPassenger(entity, entity1); + } + } +- } finally { timer.stopTiming(); } // Paper - timings ++ //} finally { timer.stopTiming(); } // Paper - timings - move up + + } + } +@@ -902,6 +906,11 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public void tickPassenger(Entity vehicle, Entity passenger) { + if (!passenger.removed && passenger.getVehicle() == vehicle) { + if (passenger instanceof Player || this.getChunkSource().isEntityTickingChunk(passenger)) { ++ // Paper - EAR 2 ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); ++ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper ++ try { ++ // Paper end + passenger.setPosAndOldPos(passenger.getX(), passenger.getY(), passenger.getZ()); + passenger.yRotO = passenger.yRot; + passenger.xRotO = passenger.xRot; +@@ -913,8 +922,17 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return Registry.ENTITY_TYPE.getKey(passenger.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickPassenger"); ++ // Paper start - EAR 2 ++ if (isActive) { + passenger.rideTick(); + passenger.postTick(); // CraftBukkit ++ } else { ++ passenger.setDeltaMovement(Vec3.ZERO); ++ passenger.inactiveTick(); ++ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary ++ vehicle.syncPositionOf(passenger); ++ } ++ // Paper end - EAR 2 + gameprofilerfiller.pop(); + } + +@@ -927,7 +945,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + this.tickPassenger(passenger, entity2); + } +- } ++ } } finally { timer.stopTiming(); } // Paper - EAR2 timings + + } + } else { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index af86c370c6f834514115a8e40659f5e1aaabec75..c6881a9a5da2caed77dea30e4906d2dd99a624c1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -67,6 +67,7 @@ import net.minecraft.world.entity.animal.AbstractFish; + import net.minecraft.world.entity.animal.Animal; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.entity.vehicle.AbstractMinecart; + import net.minecraft.world.entity.vehicle.Boat; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.enchantment.EnchantmentHelper; +@@ -250,7 +251,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public boolean noCulling; + public boolean hasImpulse; + public int portalCooldown; +- protected boolean isInsidePortal; ++ public boolean isInsidePortal; // Paper - public + protected int portalTime; + protected BlockPos portalEntrancePos; + private boolean invulnerable; +@@ -274,6 +275,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ public boolean isTemporarilyActive = false; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + protected int numCollisions = 0; // Paper + public void inactiveTick() { } +@@ -664,6 +666,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.setLocationFromBoundingbox(); + } else { + if (type == MoverType.PISTON) { ++ this.activatedTick = MinecraftServer.currentTick + 20; // Paper + movement = this.limitPistonMovement(movement); + if (movement.equals(Vec3.ZERO)) { + return; +@@ -676,6 +679,13 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.stuckSpeedMultiplier = Vec3.ZERO; + this.setDeltaMovement(Vec3.ZERO); + } ++ // Paper start - ignore movement changes while inactive. ++ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof AbstractMinecart) && movement == getDeltaMovement() && type == MoverType.SELF) { ++ setDeltaMovement(Vec3.ZERO); ++ this.level.getProfiler().pop(); ++ return; ++ } ++ // Paper end + + movement = this.maybeBackOffFromEdge(movement, type); + Vec3 vec3d1 = this.collide(movement); +@@ -2011,6 +2021,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + } + ++ public void syncPositionOf(Entity entity) { positionRider(entity); } // Paper - OBFHELPER + public void positionRider(Entity passenger) { + this.positionRider(passenger, Entity::setPos); + } +@@ -2821,6 +2832,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.stringUUID; + } + ++ public final boolean isPushedByWater() { return this.isPushedByFluid(); } // Paper - OBFHELPER - the below is not an obfhelper, don't use it! + public boolean isPushedByFluid() { + // Paper start + return this.pushedByWater(); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 016fcc4ae20e1e48728a848be28633e624ae49a7..b84dab1043c56e2deb58aec8639226f98db165d1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -189,7 +189,7 @@ public abstract class LivingEntity extends Entity { + protected float rotOffs; + protected int deathScore;protected int getKillCount() { return this.deathScore; } // Paper - OBFHELPER + public float lastHurt; +- protected boolean jumping; ++ public boolean jumping; // Paper protected -> public + public float xxa; + public float yya; + public float zza; +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 29a2eeee9f2011ed6fcc44f19041f616decfdb38..40ab66f888f30a5506e3aa96a4b32485452e8978 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -113,7 +113,7 @@ public abstract class Mob extends LivingEntity { + public ResourceLocation lootTable; + public long lootTableSeed; + @Nullable +- private Entity leashHolder; ++ public Entity leashHolder; // Paper - private -> public + private int delayedLeashHolderId; + @Nullable + private CompoundTag leashInfoTag; +@@ -194,6 +194,19 @@ public abstract class Mob extends LivingEntity { + return this.lookControl; + } + ++ // Paper start ++ @Override ++ public void inactiveTick() { ++ super.inactiveTick(); ++ if (this.goalSelector.inactiveTick()) { ++ this.goalSelector.tick(); ++ } ++ if (this.targetSelector.inactiveTick()) { ++ this.targetSelector.tick(); ++ } ++ } ++ // Paper end ++ + public MoveControl getMoveControl() { + if (this.isPassenger() && this.getVehicle() instanceof Mob) { + Mob entityinsentient = (Mob) this.getVehicle(); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 920ae9af8985705a0ada7da5b7085a1ed8ca7f27..7c82d453388a27b69207d051dec316fc14715e2b 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -13,6 +13,7 @@ import org.bukkit.event.entity.EntityUnleashEvent; + public abstract class PathfinderMob extends Mob { + + public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper ++ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper + + protected PathfinderMob(EntityType type, Level world) { + super(type, world); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index d44a5b7f6cf62d5e9acacad25d47cb0d44761cfa..558dd72c47930f6993952467f83b5a54ead95d92 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -20,7 +20,10 @@ public abstract class Goal { + + public void start() {} + +- public void stop() {} ++ public void stop() { ++ onTaskReset(); // Paper ++ } ++ public void onTaskReset() {} // Paper + + public void tick() {} + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 9066db5c9a76cfb9665bef77b36172f1ea6ba931..9bd2ee05a0de6678ad8933a8ffbe0ae66bd073b4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -26,10 +26,11 @@ public class GoalSelector { + } + }; + private final Map lockedFlags = new EnumMap(Goal.Flag.class); +- private final Set availableGoals = Sets.newLinkedHashSet(); ++ private final Set availableGoals = Sets.newLinkedHashSet(); private Set getTasks() { return availableGoals; }// Paper - OBFHELPER + private final Supplier profiler; + private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); +- private int newGoalRate = 3; ++ private int newGoalRate = 3;private int getTickRate() { return newGoalRate; } // Paper - OBFHELPER ++ private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + + public GoalSelector(Supplier profiler) { + this.profiler = profiler; +@@ -39,6 +40,21 @@ public class GoalSelector { + this.availableGoals.add(new WrappedGoal(priority, goal)); + } + ++ // Paper start ++ public boolean inactiveTick() { ++ incRate(); ++ return getCurRate() % getTickRate() == 0; ++ } ++ public boolean hasTasks() { ++ for (WrappedGoal task : getTasks()) { ++ if (task.isRunning()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end ++ + public void removeGoal(Goal goal) { + this.availableGoals.stream().filter((pathfindergoalwrapped) -> { + return pathfindergoalwrapped.getGoal() == goal; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index c8680e795deeb68e0662eac7c760a103d1c767b4..e83cb412d8549b86d0348a2aa37c79201a5930be 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -9,12 +9,12 @@ import net.minecraft.world.level.LevelReader; + + public abstract class MoveToBlockGoal extends Goal { + +- protected final PathfinderMob mob; ++ protected final PathfinderMob mob;public PathfinderMob getEntity() { return mob; } // Paper - OBFHELPER + public final double speedModifier; + protected int nextStartTick; + protected int tryTicks; + private int maxStayTicks; +- protected BlockPos blockPos;public final BlockPos getTargetPosition() { return this.blockPos; } // Paper - OBFHELPER ++ protected BlockPos blockPos; public final BlockPos getTargetPosition() { return this.blockPos; } public void setTargetPosition(BlockPos pos) { this.blockPos = pos; getEntity().movingTarget = pos != BlockPos.ZERO ? pos : null; } // Paper - OBFHELPER + private boolean reachedTarget; + private final int searchRange; + private final int verticalSearchRange; +@@ -23,6 +23,13 @@ public abstract class MoveToBlockGoal extends Goal { + public MoveToBlockGoal(PathfinderMob mob, double speed, int range) { + this(mob, speed, range, 1); + } ++ // Paper start - activation range improvements ++ @Override ++ public void onTaskReset() { ++ super.onTaskReset(); ++ setTargetPosition(BlockPos.ZERO); ++ } ++ // Paper end + + public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) { + this.blockPos = BlockPos.ZERO; +@@ -111,6 +118,7 @@ public abstract class MoveToBlockGoal extends Goal { + blockposition_mutableblockposition.setWithOffset((Vec3i) blockposition, i1, k - 1, j1); + if (this.mob.isWithinRestriction((BlockPos) blockposition_mutableblockposition) && this.isValidTarget(this.mob.level, blockposition_mutableblockposition)) { + this.blockPos = blockposition_mutableblockposition; ++ setTargetPosition(blockposition_mutableblockposition.immutable()); // Paper + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +index 9921adf9292e0eff77515841d1b109a07b489367..81b4618a7979ee8dd25e1749c084de9262318ef4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +@@ -64,6 +64,7 @@ public class WrappedGoal extends Goal { + return this.goal.getFlags(); + } + ++ public boolean isRunning() { return this.isRunning(); } // Paper - OBFHELPER + public boolean isRunning() { + return this.isRunning; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index 5a7310bb48c1b8a72ad3c5d82c44fff8800995a2..a24af0600ad3e7d189581aa06a8e998f6a12e0fc 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -454,6 +454,7 @@ public class Llama extends AbstractChestedHorse implements RangedAttackMob { + return this.caravanTail != null; + } + ++ public final boolean inCaravan() { return this.inCaravan(); } // Paper - OBFHELPER + public boolean inCaravan() { + return this.caravanHead != null; + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index ae8f850baa14a4f4277da5b6fdb1e5ccb44c4f35..9eee68a5a84e121698d26bd54212a72c75e16251 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -70,10 +70,12 @@ public abstract class AbstractVillager extends AgableMob implements Npc, Merchan + return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityTag); + } + ++ public final int getUnhappy() { return getUnhappyCounter(); } // Paper - OBFHELPER + public int getUnhappyCounter() { + return (Integer) this.entityData.get(AbstractVillager.DATA_UNHAPPY_COUNTER); + } + ++ public final void setUnhappy(int i) { setUnhappyCounter(i); } // Paper - OBFHELPER + public void setUnhappyCounter(int ticks) { + this.entityData.set(AbstractVillager.DATA_UNHAPPY_COUNTER, ticks); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index eed6265dc8275921a18fc5f4970ba131ba782132..4aa34320ef7d6c62ccb17734bfa61d406190b919 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -212,17 +212,29 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void inactiveTick() { + // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( +- if (level.spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { +- this.customServerAiStep(); ++ // Paper start ++ if (this.getUnhappy() > 0) { ++ this.setUnhappy(this.getUnhappy() - 1); + } ++ if (this.isEffectiveAi()) { ++ if (level.spigotConfig.tickInactiveVillagers) { ++ this.customServerAiStep(); ++ } else { ++ this.mobTick(true); ++ } ++ } ++ doReputationTick(); ++ // Paper end ++ + super.inactiveTick(); + } + // Spigot End + + @Override +- protected void customServerAiStep() { ++ protected void customServerAiStep() { mobTick(false); } ++ protected void mobTick(boolean inactive) { + this.level.getProfiler().push("villagerBrain"); +- this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error ++ if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error // Paper + this.level.getProfiler().pop(); + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; +@@ -246,7 +258,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.lastTradedPlayer = null; + } + +- if (!this.isNoAi() && this.random.nextInt(100) == 0) { ++ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper + Raid raid = ((ServerLevel) this.level).getRaidAt(this.blockPosition()); + + if (raid != null && raid.isActive() && !raid.isOver()) { +@@ -257,6 +269,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) { + this.stopTrading(); + } ++ if (inactive) return; // Paper + + super.customServerAiStep(); + } +@@ -900,6 +913,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + } + ++ private void doReputationTick() { maybeDecayGossip(); } // Paper - OBFHELPER + private void maybeDecayGossip() { + long i = this.level.getGameTime(); + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 6b79f8cd9258af47afa6efa7b1f97c3780be58b0..1d536d77518a70bdc1a23924aea99df1042b3cd5 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -142,6 +142,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public long ticksPerWaterSpawns; + public long ticksPerWaterAmbientSpawns; + public long ticksPerAmbientSpawns; ++ // Paper start ++ public int wakeupInactiveRemainingAnimals; ++ public int wakeupInactiveRemainingFlying; ++ public int wakeupInactiveRemainingMonsters; ++ public int wakeupInactiveRemainingVillagers; ++ // Paper end + public boolean populating; + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 61f180a7c95d83bb88c7df4910c498d9bdf6785a..8cbafad53d20366a36493f22160c4fa3e4ac3eaf 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -1,41 +1,55 @@ + package org.spigotmc; + + import java.util.Collection; ++import net.minecraft.core.BlockPos; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.FlyingMob; + import net.minecraft.world.entity.LightningBolt; + import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.ai.Brain; + import net.minecraft.world.entity.ambient.AmbientCreature; + import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Bee; + import net.minecraft.world.entity.animal.Sheep; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.horse.Llama; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EndCrystal; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.entity.boss.wither.WitherBoss; ++import net.minecraft.world.entity.item.FallingBlockEntity; + import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.monster.Creeper; +-import net.minecraft.world.entity.monster.Monster; +-import net.minecraft.world.entity.monster.Slime; ++import net.minecraft.world.entity.monster.Enemy; ++import net.minecraft.world.entity.monster.Pillager; + import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; ++import net.minecraft.world.entity.projectile.EyeOfEnder; + import net.minecraft.world.entity.projectile.FireworkRocketEntity; + import net.minecraft.world.entity.projectile.ThrowableProjectile; + import net.minecraft.world.entity.projectile.ThrownTrident; + import net.minecraft.world.entity.raid.Raider; ++import co.aikar.timings.MinecraftTimings; ++import net.minecraft.world.entity.schedule.Activity; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.phys.AABB; +-import co.aikar.timings.MinecraftTimings; + + public class ActivationRange + { + + public enum ActivationType + { ++ WATER, // Paper ++ FLYING_MONSTER, // Paper ++ VILLAGER, // Paper + MONSTER, + ANIMAL, + RAIDER, +@@ -43,6 +57,43 @@ public class ActivationRange + + AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 ); + } ++ // Paper start ++ ++ static Activity[] VILLAGER_PANIC_IMMUNITIES = { ++ Activity.HIDE, ++ Activity.PRE_RAID, ++ Activity.RAID, ++ Activity.PANIC ++ }; ++ ++ private static int checkInactiveWakeup(Entity entity) { ++ Level world = entity.level; ++ SpigotWorldConfig config = world.spigotConfig; ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ if (entity.activationType == ActivationType.VILLAGER) { ++ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { ++ world.wakeupInactiveRemainingVillagers--; ++ return config.wakeUpInactiveVillagersFor; ++ } ++ } else if (entity.activationType == ActivationType.ANIMAL) { ++ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { ++ world.wakeupInactiveRemainingAnimals--; ++ return config.wakeUpInactiveAnimalsFor; ++ } ++ } else if (entity.activationType == ActivationType.FLYING_MONSTER) { ++ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { ++ world.wakeupInactiveRemainingFlying--; ++ return config.wakeUpInactiveFlyingFor; ++ } ++ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { ++ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { ++ world.wakeupInactiveRemainingMonsters--; ++ return config.wakeUpInactiveMonstersFor; ++ } ++ } ++ return -1; ++ } ++ // Paper end + + static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 ); + +@@ -55,10 +106,13 @@ public class ActivationRange + */ + public static ActivationType initializeEntityActivationType(Entity entity) + { ++ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper ++ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper ++ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future + if ( entity instanceof Raider ) + { + return ActivationType.RAIDER; +- } else if ( entity instanceof Monster || entity instanceof Slime ) ++ } else if ( entity instanceof Enemy ) // Paper - correct monster check + { + return ActivationType.MONSTER; + } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature ) +@@ -79,10 +133,14 @@ public class ActivationRange + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { +- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) +- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) +- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) +- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) ++ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper ++ || entity instanceof EyeOfEnder // Paper + || entity instanceof Player + || entity instanceof ThrowableProjectile + || entity instanceof EnderDragon +@@ -91,7 +149,7 @@ public class ActivationRange + || entity instanceof AbstractHurtingProjectile + || entity instanceof LightningBolt + || entity instanceof PrimedTnt +- || entity instanceof EntityFallingBlock // Paper - Always tick falling blocks ++ || entity instanceof FallingBlockEntity // Paper - Always tick falling blocks + || entity instanceof EndCrystal + || entity instanceof FireworkRocketEntity + || entity instanceof ThrownTrident ) +@@ -115,10 +173,25 @@ public class ActivationRange + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; ++ // Paper start ++ final int waterActivationRange = world.spigotConfig.waterActivationRange; ++ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; ++ final int villagerActivationRange = world.spigotConfig.villagerActivationRange; ++ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); ++ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); ++ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); ++ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); ++ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource(); ++ // Paper end + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, raiderActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); ++ // Paper start ++ maxRange = Math.max( maxRange, flyingActivationRange ); ++ maxRange = Math.max( maxRange, waterActivationRange ); ++ maxRange = Math.max( maxRange, villagerActivationRange ); ++ // Paper end + maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); + + for ( Player player : world.players() ) +@@ -130,6 +203,11 @@ public class ActivationRange + ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); + ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); + ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); ++ // Paper start ++ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, 256, waterActivationRange ); ++ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, 256, flyingActivationRange ); ++ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, 256, waterActivationRange ); ++ // Paper end + + int i = Mth.floor( maxBB.minX / 16.0D ); + int j = Mth.floor( maxBB.maxX / 16.0D ); +@@ -140,7 +218,7 @@ public class ActivationRange + { + for ( int j1 = k; j1 <= l; ++j1 ) + { +- LevelChunk chunk = (LevelChunk) world.getChunkIfLoadedImmediately( i1, j1 ); ++ LevelChunk chunk = chunkProvider.getChunkAtIfLoadedMainThreadNoCache( i1, j1 ); // Paper + if ( chunk != null ) + { + activateChunkEntities( chunk ); +@@ -158,19 +236,15 @@ public class ActivationRange + */ + private static void activateChunkEntities(LevelChunk chunk) + { +- for ( java.util.List slice : chunk.entitySlices ) +- { +- for ( Entity entity : (Collection) slice ) ++ // Paper start ++ Entity[] rawData = chunk.entities.getRawData(); ++ for (int i = 0; i < chunk.entities.size(); i++) { ++ Entity entity = rawData[i]; ++ //for ( Entity entity : (Collection) slice ) ++ // Paper end + { +- if ( MinecraftServer.currentTick > entity.activatedTick ) +- { +- if ( entity.defaultActivationState ) +- { +- entity.activatedTick = MinecraftServer.currentTick; +- continue; +- } +- if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) +- { ++ if (MinecraftServer.currentTick > entity.activatedTick) { ++ if (entity.defaultActivationState || entity.activationType.boundingBox.intersects(entity.getBoundingBox())) { // Paper + entity.activatedTick = MinecraftServer.currentTick; + } + } +@@ -185,56 +259,105 @@ public class ActivationRange + * @param entity + * @return + */ +- public static boolean checkEntityImmunities(Entity entity) ++ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity + { ++ // Paper start ++ SpigotWorldConfig config = entity.level.spigotConfig; ++ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); ++ if (inactiveWakeUpImmunity > -1) { ++ return inactiveWakeUpImmunity; ++ } ++ if (entity.remainingFireTicks > 0) { ++ return 2; ++ } ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ // Paper end + // quick checks. +- if ( entity.wasTouchingWater || entity.remainingFireTicks > 0 ) ++ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByWater()) ) // Paper + { +- return true; ++ return 100; // Paper + } + if ( !( entity instanceof AbstractArrow ) ) + { +- if ( !entity.isOnGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) ++ if ( (!entity.isOnGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic + { +- return true; ++ return 10; // Paper + } + } else if ( !( (AbstractArrow) entity ).inGround ) + { +- return true; ++ return 1; // Paper + } + // special cases. + if ( entity instanceof LivingEntity ) + { + LivingEntity living = (LivingEntity) entity; +- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 ) ++ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper + { +- return true; ++ return 1; // Paper + } +- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null ) ++ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper + { +- return true; ++ return 20; // Paper ++ } ++ // Paper start ++ if (entity instanceof Bee) { ++ Bee bee = (Bee)entity; ++ BlockPos movingTarget = bee.getMovingTarget(); ++ if (bee.isAngry() || ++ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || ++ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget)) ++ ) { ++ return 20; ++ } ++ } ++ if ( entity instanceof Villager ) { ++ Brain behaviorController = ((Villager) entity).getBrain(); ++ ++ if (config.villagersActiveForPanic) { ++ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { ++ if (behaviorController.isActive(activity)) { ++ return 20*5; ++ } ++ } ++ } ++ ++ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { ++ if (behaviorController.isActive(Activity.WORK)) { ++ return config.villagersWorkImmunityFor; ++ } ++ } + } +- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() ) ++ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() ) + { +- return true; ++ return 1; + } ++ // Paper end + if ( entity instanceof Animal ) + { + Animal animal = (Animal) entity; + if ( animal.isBaby() || animal.isInLove() ) + { +- return true; ++ return 5; // Paper + } + if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() ) + { +- return true; ++ return 1; // Paper + } + } + if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive +- return true; ++ return 20; // Paper + } ++ // Paper start ++ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) { ++ return 0; ++ } ++ if (entity instanceof Pillager) { ++ Pillager pillager = (Pillager) entity; ++ // TODO:? ++ } ++ // Paper end + } +- return false; ++ return -1; // Paper + } + + /** +@@ -249,8 +372,19 @@ public class ActivationRange + if ( !entity.inChunk || entity instanceof FireworkRocketEntity ) { + return true; + } ++ // Paper start - special case always immunities ++ // immunize brand new entities, dead entities, and portal scenarios ++ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) { ++ return true; ++ } ++ // immunize leashed entities ++ if (entity instanceof Mob && ((Mob)entity).leashHolder instanceof Player) { ++ return true; ++ } ++ // Paper end + +- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; ++ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick; ++ entity.isTemporarilyActive = false; // Paper + + // Should this entity tick? + if ( !isActive ) +@@ -258,15 +392,19 @@ public class ActivationRange + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. +- if ( checkEntityImmunities( entity ) ) +- { +- // Triggered some sort of immunity, give 20 full ticks before we check again. +- entity.activatedTick = MinecraftServer.currentTick + 20; ++ // Paper start ++ int immunity = checkEntityImmunities(entity); ++ if (immunity >= 0) { ++ entity.activatedTick = MinecraftServer.currentTick + immunity; ++ } else { ++ entity.isTemporarilyActive = true; + } ++ // Paper end + isActive = true; ++ + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. +- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !checkEntityImmunities( entity ) ) ++ } else if (entity.tickCount % 4 == 0 && checkEntityImmunities( entity) < 0 ) // Paper + { + isActive = false; + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 34ee684901906fc2ef5f0d09680d2686b813e52b..6b015c1f26facb4e82d75b252164dec05731ca6c 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -180,13 +180,59 @@ public class SpigotWorldConfig + public int monsterActivationRange = 32; + public int raiderActivationRange = 48; + public int miscActivationRange = 16; ++ // Paper start ++ public int flyingMonsterActivationRange = 32; ++ public int waterActivationRange = 16; ++ public int villagerActivationRange = 32; ++ public int wakeUpInactiveAnimals = 4; ++ public int wakeUpInactiveAnimalsEvery = 60*20; ++ public int wakeUpInactiveAnimalsFor = 5*20; ++ public int wakeUpInactiveMonsters = 8; ++ public int wakeUpInactiveMonstersEvery = 20*20; ++ public int wakeUpInactiveMonstersFor = 5*20; ++ public int wakeUpInactiveVillagers = 4; ++ public int wakeUpInactiveVillagersEvery = 30*20; ++ public int wakeUpInactiveVillagersFor = 5*20; ++ public int wakeUpInactiveFlying = 8; ++ public int wakeUpInactiveFlyingEvery = 10*20; ++ public int wakeUpInactiveFlyingFor = 5*20; ++ public int villagersWorkImmunityAfter = 5*20; ++ public int villagersWorkImmunityFor = 20; ++ public boolean villagersActiveForPanic = true; ++ // Paper end + public boolean tickInactiveVillagers = true; + private void activationRange() + { ++ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", animalActivationRange) != animalActivationRange; // Paper + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + raiderActivationRange = getInt( "entity-activation-range.raiders", raiderActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); ++ // Paper start ++ waterActivationRange = getInt( "entity-activation-range.water", waterActivationRange ); ++ villagerActivationRange = getInt( "entity-activation-range.villagers", hasAnimalsConfig ? animalActivationRange : villagerActivationRange ); ++ flyingMonsterActivationRange = getInt( "entity-activation-range.flying-monsters", flyingMonsterActivationRange ); ++ ++ wakeUpInactiveAnimals = getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", wakeUpInactiveAnimals); ++ wakeUpInactiveAnimalsEvery = getInt("entity-activation-range.wake-up-inactive.animals-every", wakeUpInactiveAnimalsEvery); ++ wakeUpInactiveAnimalsFor = getInt("entity-activation-range.wake-up-inactive.animals-for", wakeUpInactiveAnimalsFor); ++ ++ wakeUpInactiveMonsters = getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", wakeUpInactiveMonsters); ++ wakeUpInactiveMonstersEvery = getInt("entity-activation-range.wake-up-inactive.monsters-every", wakeUpInactiveMonstersEvery); ++ wakeUpInactiveMonstersFor = getInt("entity-activation-range.wake-up-inactive.monsters-for", wakeUpInactiveMonstersFor); ++ ++ wakeUpInactiveVillagers = getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", wakeUpInactiveVillagers); ++ wakeUpInactiveVillagersEvery = getInt("entity-activation-range.wake-up-inactive.villagers-every", wakeUpInactiveVillagersEvery); ++ wakeUpInactiveVillagersFor = getInt("entity-activation-range.wake-up-inactive.villagers-for", wakeUpInactiveVillagersFor); ++ ++ wakeUpInactiveFlying = getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", wakeUpInactiveFlying); ++ wakeUpInactiveFlyingEvery = getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", wakeUpInactiveFlyingEvery); ++ wakeUpInactiveFlyingFor = getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", wakeUpInactiveFlyingFor); ++ ++ villagersWorkImmunityAfter = getInt( "entity-activation-range.villagers-work-immunity-after", villagersWorkImmunityAfter ); ++ villagersWorkImmunityFor = getInt( "entity-activation-range.villagers-work-immunity-for", villagersWorkImmunityFor ); ++ villagersActiveForPanic = getBoolean( "entity-activation-range.villagers-active-for-panic", villagersActiveForPanic ); ++ // Paper end + tickInactiveVillagers = getBoolean( "entity-activation-range.tick-inactive-villagers", tickInactiveVillagers ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Ra " + raiderActivationRange + " / Mi " + miscActivationRange + " / Tiv " + tickInactiveVillagers ); + } diff --git a/Remapped-Spigot-Server-Patches/0401-Fix-items-vanishing-through-end-portal.patch b/Remapped-Spigot-Server-Patches/0401-Fix-items-vanishing-through-end-portal.patch new file mode 100644 index 000000000..283ad7000 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0401-Fix-items-vanishing-through-end-portal.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AJMFactsheets +Date: Wed, 22 Jan 2020 19:52:28 -0600 +Subject: [PATCH] Fix items vanishing through end portal + +If the Paper configuration option "keep-spawn-loaded" is set to false, +items entering the overworld from the end will spawn at Y = 0. + +This is due to logic in the getHighestBlockYAt method in World.java +only searching the heightmap if the chunk is loaded. + +Quickly loading the exact world spawn chunk before searching the +heightmap resolves the issue without having to load all spawn chunks. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c6881a9a5da2caed77dea30e4906d2dd99a624c1..efc9cb6def2f4ee327329dc090d2918ff60d8e19 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2734,6 +2734,9 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + BlockPos blockposition1; + + if (flag1) { ++ // Paper start - Ensure spawn chunk is always loaded before calculating Y coordinate ++ this.level.getChunkAt(this.level.getSpawn()); ++ // Paper end + blockposition1 = ServerLevel.END_SPAWN_POINT; + } else { + blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSpawn()); diff --git a/Remapped-Spigot-Server-Patches/0402-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/Remapped-Spigot-Server-Patches/0402-Bees-get-gravity-in-void.-Fixes-MC-167279.patch new file mode 100644 index 000000000..f19be75b5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0402-Bees-get-gravity-in-void.-Fixes-MC-167279.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 Jan 2020 16:30:19 -0600 +Subject: [PATCH] Bees get gravity in void. Fixes MC-167279 + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/FlyingMoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/FlyingMoveControl.java +index 2c9e3dd1b9dd7bb8825a2eb9fecc2b2be348d055..868e9cdeb3c7effb398cef6f6f9c1e4fffa2db8c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/FlyingMoveControl.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/FlyingMoveControl.java +@@ -16,7 +16,7 @@ public class FlyingMoveControl extends MoveControl { + } + + @Override +- public void tick() { ++ public void tick() { tick(); } public void tick() { // Paper - OBFHELPER + if (this.operation == MoveControl.Operation.MOVE_TO) { + this.operation = MoveControl.Operation.WAIT; + this.mob.setNoGravity(true); +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java +index ab65f0327766463a5e53fdd723e243464319fdbe..f4984d601d14c684e75f887f5f5d2f5a29326b15 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java +@@ -16,7 +16,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + + public class MoveControl { + +- protected final Mob mob; ++ protected final Mob mob; public final Mob getEntity() { return mob; } // Paper - OBFHELPER + protected double wantedX; + protected double wantedY; + protected double wantedZ; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index edd6d63f715acef1a77eba0cf46ff8267228f4c6..9b68809b91910d2bbb82cafe23d1de5dfff4221c 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -111,7 +111,17 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + public Bee(EntityType type, Level world) { + super(type, world); +- this.moveControl = new FlyingMoveControl(this, 20, true); ++ // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 ++ this.moveControl = new FlyingMoveControl(this, 20, true) { ++ @Override ++ public void tick() { ++ if (getEntity().getY() <= 0) { ++ getEntity().setNoGravity(false); ++ } ++ super.tick(); ++ } ++ }; ++ // Paper end + this.lookControl = new Bee.BeeLookControl(this); + this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); + this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); diff --git a/Remapped-Spigot-Server-Patches/0403-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/Remapped-Spigot-Server-Patches/0403-Optimise-getChunkAt-calls-for-loaded-chunks.patch new file mode 100644 index 000000000..009a0fad6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0403-Optimise-getChunkAt-calls-for-loaded-chunks.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 25 Jan 2020 17:04:35 -0800 +Subject: [PATCH] Optimise getChunkAt calls for loaded chunks + +bypass the need to get a player chunk, then get the either, +then unwrap it... + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index eac5e799c4d26e53286a27c54b56899ba0b9ffb2..3aeb8426b0461ec572c1499116be80f968bb4104 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -470,6 +470,12 @@ public class ServerChunkCache extends ChunkSource { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); + } else { ++ // Paper start - optimise for loaded chunks ++ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ // Paper end + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + + gameprofilerfiller.incrementCounter("getChunk"); +@@ -520,39 +526,7 @@ public class ServerChunkCache extends ChunkSource { + if (Thread.currentThread() != this.mainThread) { + return null; + } else { +- this.level.getProfiler().incrementCounter("getChunkNow"); +- long k = ChunkPos.asLong(chunkX, chunkZ); +- +- for (int l = 0; l < 4; ++l) { +- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) { +- ChunkAccess ichunkaccess = this.lastChunk[l]; +- +- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null; +- } +- } +- +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); +- +- if (playerchunk == null) { +- return null; +- } else { +- Either either = (Either) playerchunk.getFutureIfPresent(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error +- +- if (either == null) { +- return null; +- } else { +- ChunkAccess ichunkaccess1 = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error +- +- if (ichunkaccess1 != null) { +- this.storeInCache(k, ichunkaccess1, ChunkStatus.FULL); +- if (ichunkaccess1 instanceof LevelChunk) { +- return (LevelChunk) ichunkaccess1; +- } +- } +- +- return null; +- } +- } ++ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks + } + } + diff --git a/Remapped-Spigot-Server-Patches/0404-Allow-overriding-the-java-version-check.patch b/Remapped-Spigot-Server-Patches/0404-Allow-overriding-the-java-version-check.patch new file mode 100644 index 000000000..04082e9a3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0404-Allow-overriding-the-java-version-check.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 8 Feb 2020 18:02:24 -0600 +Subject: [PATCH] Allow overriding the java version check + +-DPaper.IgnoreJavaVersion=true + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 808a7688ed81bdfef623ee0a151ff8f94df2a3d7..c519ceca6f7788ca7c5d74ad1001dbc09f62681c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -181,7 +181,7 @@ public class Main { + float javaVersion = Float.parseFloat(System.getProperty("java.class.version")); + if (javaVersion > 60.0) { + System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 16 is supported."); +- return; ++ if (!Boolean.getBoolean("Paper.IgnoreJavaVersion")) return; // Paper + } + + try { diff --git a/Remapped-Spigot-Server-Patches/0405-Add-ThrownEggHatchEvent.patch b/Remapped-Spigot-Server-Patches/0405-Add-ThrownEggHatchEvent.patch new file mode 100644 index 000000000..5bc9d2e68 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0405-Add-ThrownEggHatchEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 9 Feb 2020 00:19:05 -0600 +Subject: [PATCH] Add ThrownEggHatchEvent + +Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement +(dispensers can throw eggs to hatch them, too). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +index f3808a5e9155e1bf6c6219fc494864bb7dc61117..520eace73b569c2c77e76e0dfd18eb9c7188ec30 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -63,6 +63,16 @@ public class ThrownEgg extends ThrowableItemProjectile { + hatchingType = event.getHatchingType(); + } + ++ // Paper start ++ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType); ++ event.callEvent(); ++ ++ b0 = event.getNumHatches(); ++ hatching = event.isHatching(); ++ hatchingType = event.getHatchingType(); ++ // Paper end ++ ++ + if (hatching) { + for (int i = 0; i < b0; ++i) { + Entity entity = level.getWorld().createEntity(new org.bukkit.Location(level.getWorld(), this.getX(), this.getY(), this.getZ(), this.yRot, 0.0F), hatchingType.getEntityClass()); diff --git a/Remapped-Spigot-Server-Patches/0406-Optimise-random-block-ticking.patch b/Remapped-Spigot-Server-Patches/0406-Optimise-random-block-ticking.patch new file mode 100644 index 000000000..881f7be3f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0406-Optimise-random-block-ticking.patch @@ -0,0 +1,407 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Jan 2020 21:28:00 -0800 +Subject: [PATCH] Optimise random block ticking + +Massive performance improvement for random block ticking. +The performance increase comes from the fact that the vast +majority of attempted block ticks (~95% in my testing) fail +because the randomly selected block is not tickable. + +Now only tickable blocks are targeted, however this means that +the maximum number of block ticks occurs per chunk. However, +not all chunks are going to be targeted. The percent chance +of a chunk being targeted is based on how many tickable blocks +are in the chunk. +This means that while block ticks are spread out less, the +total number of blocks ticked per world tick remains the same. +Therefore, the chance of a random tickable block being ticked +remains the same. + +diff --git a/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3edc8e52e06a62ce9f8cc734fd7458b37cfaad91 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java +@@ -0,0 +1,46 @@ ++package com.destroystokyo.paper.util.math; ++ ++import java.util.Random; ++ ++public final class ThreadUnsafeRandom extends Random { ++ ++ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. ++ private static final long multiplier = 0x5DEECE66DL; ++ private static final long addend = 0xBL; ++ private static final long mask = (1L << 48) - 1; ++ ++ private static long initialScramble(long seed) { ++ return (seed ^ multiplier) & mask; ++ } ++ ++ private long seed; ++ ++ @Override ++ public void setSeed(long seed) { ++ // note: called by Random constructor ++ this.seed = initialScramble(seed); ++ } ++ ++ @Override ++ protected int next(int bits) { ++ // avoid the expensive CAS logic used by superclass ++ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); ++ } ++ ++ // Taken from ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c ++ // Original license is public domain ++ public static int fastRandomBounded(final long randomInteger, final long limit) { ++ // randomInteger must be [0, pow(2, 32)) ++ // limit must be [0, pow(2, 32)) ++ return (int)((randomInteger * limit) >>> 32); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ // yes this breaks random's spec ++ // however there's nothing that uses this class that relies on it ++ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index b13e5d05d862ea8c6031b8071f525f00bc48f7e7..3db77d9eda98eacb099135643aff5e94751f4c7c 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -468,6 +468,7 @@ public class BlockPos extends Vec3i { + return this.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + } + ++ public final BlockPos.MutableBlockPos setValues(final Vec3i baseblockposition) { return this.set(baseblockposition); } // Paper - OBFHELPER + public BlockPos.MutableBlockPos set(Vec3i pos) { + return this.set(pos.getX(), pos.getY(), pos.getZ()); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bf1bb1530037ebcacc8d5a491789909bddb8b697..5d85895456b5d65954889cadf932027ea23b400b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -669,7 +669,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + }); + } + +- public void tickChunk(LevelChunk chunk, int randomTickSpeed) { ++ // Paper start - optimise random block ticking ++ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); ++ private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom(); ++ // Paper end ++ ++ public void tickChunk(LevelChunk chunk, int randomTickSpeed) { final int randomTickSpeed1 = randomTickSpeed; // Paper + ChunkPos chunkcoordintpair = chunk.getPos(); + boolean flag = this.isRaining(); + int j = chunkcoordintpair.getMinBlockX(); +@@ -677,10 +682,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push("thunder"); +- BlockPos blockposition; ++ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change + + if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder +- blockposition = this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15)); ++ blockposition.setValues(this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); + boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance; // Paper +@@ -703,59 +708,77 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + gameprofilerfiller.popPush("iceandsnow"); +- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow +- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15)); +- BlockPos blockposition1 = blockposition.below(); ++ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking ++ // Paper start - optimise chunk ticking ++ this.getRandomBlockPosition(j, 0, k, 15, blockposition); ++ int normalY = chunk.getHighestBlockY(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15); ++ int downY = normalY - 1; ++ blockposition.setY(normalY); ++ // Paper end + Biome biomebase = this.getBiome(blockposition); + +- if (biomebase.shouldFreeze(this, blockposition1)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ // Paper start - optimise chunk ticking ++ blockposition.setY(downY); ++ if (biomebase.shouldFreeze(this, blockposition)) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ // Paper end + } + ++ blockposition.setY(normalY); // Paper + if (flag && biomebase.shouldSnow(this, blockposition)) { + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit + } + +- if (flag && this.getBiome(blockposition1).getPrecipitation() == Biome.Precipitation.RAIN) { +- this.getBlockState(blockposition1).getBlock().handleRain((net.minecraft.world.level.Level) this, blockposition1); ++ // Paper start - optimise chunk ticking ++ blockposition.setY(downY); ++ if (flag && this.getBiome(blockposition).getPrecipitation() == Biome.Precipitation.RAIN) { ++ chunk.getBlockState(blockposition).getBlock().handleRain((net.minecraft.world.level.Level) this, blockposition); ++ // Paper end + } + } + +- gameprofilerfiller.popPush("tickBlocks"); +- timings.chunkTicksBlocks.startTiming(); // Paper ++ // Paper start - optimise random block ticking ++ gameprofilerfiller.pop(); + if (randomTickSpeed > 0) { +- LevelChunkSection[] achunksection = chunk.getSections(); +- int l = achunksection.length; ++ gameprofilerfiller.push("randomTick"); ++ timings.chunkTicksBlocks.startTiming(); // Paper + +- for (int i1 = 0; i1 < l; ++i1) { +- LevelChunkSection chunksection = achunksection[i1]; ++ LevelChunkSection[] sections = chunk.getSections(); + +- if (chunksection != LevelChunk.EMPTY_SECTION && chunksection.isRandomlyTicking()) { +- int j1 = chunksection.bottomBlockY(); ++ for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) { ++ LevelChunkSection section = sections[sectionIndex]; ++ if (section == null || section.tickingList.size() == 0) { ++ continue; ++ } + +- for (int k1 = 0; k1 < randomTickSpeed; ++k1) { +- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15); ++ int yPos = sectionIndex << 4; + +- gameprofilerfiller.push("randomTick"); +- BlockState iblockdata = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k); ++ for (int a = 0; a < randomTickSpeed1; ++a) { ++ int tickingBlocks = section.tickingList.size(); ++ int index = this.randomTickRandom.nextInt(16 * 16 * 16); ++ if (index >= tickingBlocks) { ++ continue; ++ } + +- if (iblockdata.isRandomlyTicking()) { +- iblockdata.randomTick(this, blockposition2, this.random); +- } ++ long raw = section.tickingList.getRaw(index); ++ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); ++ int randomX = location & 15; ++ int randomY = ((location >>> (4 + 4)) & 255) | yPos; ++ int randomZ = (location >>> 4) & 15; + +- FluidState fluid = iblockdata.getFluidState(); ++ BlockPos blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ); ++ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- if (fluid.isRandomlyTicking()) { +- fluid.randomTick(this, blockposition2, this.random); +- } ++ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); + +- gameprofilerfiller.pop(); +- } ++ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method. ++ // TODO CHECK ON UPDATE + } + } ++ gameprofilerfiller.pop(); ++ timings.chunkTicksBlocks.stopTiming(); // Paper ++ // Paper end + } +- timings.chunkTicksBlocks.stopTiming(); // Paper +- gameprofilerfiller.pop(); + } + + protected BlockPos findLightingTargetAround(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index dd84984f28484cf7129c294222696784e128221a..9ea72751354e893cd3820befaa5df3e5e503de6e 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -112,4 +112,32 @@ public class BitStorage { + } + + } ++ ++ // Paper start ++ public final void forEach(DataBitConsumer consumer) { ++ int i = 0; ++ long[] along = this.data; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { ++ consumer.accept(i, (int) (l & this.mask)); ++ l >>= this.bits; ++ ++i; ++ if (i >= this.size) { ++ return; ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface DataBitConsumer { ++ ++ void accept(int location, int data); ++ ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 42b636c4ebb6eb83c8a9f3f5f9a766d37d065dc3..0e15ca2fb9cd1aeb4a075b8d50350dd7fd463c72 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -91,7 +91,7 @@ public class Turtle extends Animal { + } + + public void setHomePos(BlockPos pos) { +- this.entityData.set(Turtle.HOME_POS, pos); ++ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... + } + + public BlockPos getHomePos() { // Paper - public +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 1d536d77518a70bdc1a23924aea99df1042b3cd5..632f32405053fbcff2fd26fa99f98c6add9f9dc7 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1472,10 +1472,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public abstract TagContainer getTagManager(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int l) { ++ // Paper start - allow use of mutable pos ++ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); ++ this.getRandomBlockPosition(x, y, z, l, ret); ++ return ret.immutable(); ++ } ++ public final BlockPos.MutableBlockPos getRandomBlockPosition(int i, int j, int k, int l, BlockPos.MutableBlockPos out) { ++ // Paper end + this.randValue = this.randValue * 3 + 1013904223; + int i1 = this.randValue >> 2; + +- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); ++ out.setValues(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15)); // Paper - change to setValues call ++ return out; // Paper + } + + public boolean noSave() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 4fef3abe4b416cbebe1b456468b5c3e162de18f1..87d7a87a2925f2c062658e960bb5128738828d9f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -639,8 +639,8 @@ public class LevelChunk implements ChunkAccess { + this.entities.remove(entity); // Paper + } + +- @Override +- public int getHeight(Heightmap.Types type, int x, int z) { ++ public final int getHighestBlockY(Heightmap.Types heightmap_type, int i, int j) { return this.getHeight(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1 ++ @Override public int getHeight(Heightmap.Types type, int x, int z) { // Paper + return ((Heightmap) this.heightmaps.get(type)).getFirstAvailable(x & 15, z & 15) - 1; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 38c7c5f18fc84d4a1de2da1ddc6d3ac37c25f341..c44d32f966c61497b4a8892eb51da3a71ad031d9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -14,12 +14,14 @@ import net.minecraft.world.level.material.FluidState; + public class LevelChunkSection { + + public static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState()); +- private final int bottomBlockY; ++ final int bottomBlockY; // Paper - private -> package-private + short nonEmptyBlockCount; // Paper - package-private +- private short tickingBlockCount; ++ short tickingBlockCount; // Paper - private -> package-private + private short tickingFluidCount; + final PalettedContainer states; // Paper - package-private + ++ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper ++ + // Paper start - Anti-Xray - Add parameters + @Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public LevelChunkSection(int i, ChunkAccess chunk, Level world, boolean initializeBlocks) { +@@ -74,6 +76,9 @@ public class LevelChunkSection { + --this.nonEmptyBlockCount; + if (iblockdata1.isRandomlyTicking()) { + --this.tickingBlockCount; ++ // Paper start ++ this.tickingList.remove(x, y, z); ++ // Paper end + } + } + +@@ -85,6 +90,9 @@ public class LevelChunkSection { + ++this.nonEmptyBlockCount; + if (state.isRandomlyTicking()) { + ++this.tickingBlockCount; ++ // Paper start ++ this.tickingList.add(x, y, z, state); ++ // Paper end + } + } + +@@ -120,23 +128,29 @@ public class LevelChunkSection { + } + + public void recalcBlockCounts() { ++ // Paper start ++ this.tickingList.clear(); ++ // Paper end + this.nonEmptyBlockCount = 0; + this.tickingBlockCount = 0; + this.tickingFluidCount = 0; +- this.states.count((iblockdata, i) -> { ++ this.states.forEachLocation((iblockdata, location) -> { // Paper + FluidState fluid = iblockdata.getFluidState(); + + if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (iblockdata.isRandomlyTicking()) { +- this.tickingBlockCount = (short) (this.tickingBlockCount + i); ++ this.tickingBlockCount = (short) (this.tickingBlockCount + 1); ++ // Paper start ++ this.tickingList.add(location, iblockdata); ++ // Paper end + } + } + + if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (fluid.isRandomlyTicking()) { +- this.tickingFluidCount = (short) (this.tickingFluidCount + i); ++ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); + } + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index dd252372e1e380674b1191e9ea265cbb10de437b..f93316b3ae5cd5fb960fa24f8c921b5b9276d9f3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -285,6 +285,14 @@ public class PalettedContainer implements PaletteResize { + }); + } + ++ // Paper start ++ public void forEachLocation(PalettedContainer.CountConsumer datapaletteblock_a) { ++ this.getDataBits().forEach((int location, int data) -> { ++ datapaletteblock_a.accept(this.getDataPalette().getObject(data), location); ++ }); ++ } ++ // Paper end ++ + @FunctionalInterface + public interface CountConsumer { + diff --git a/Remapped-Spigot-Server-Patches/0407-Entity-Jump-API.patch b/Remapped-Spigot-Server-Patches/0407-Entity-Jump-API.patch new file mode 100644 index 000000000..15c22b27a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0407-Entity-Jump-API.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Feb 2020 23:26:11 -0600 +Subject: [PATCH] Entity Jump API + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index b84dab1043c56e2deb58aec8639226f98db165d1..43fbe7d220f61802ae0cb0620ad078c5df7b69bc 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2873,8 +2873,10 @@ public abstract class LivingEntity extends Entity { + } else if (this.isInLava() && (!this.onGround || d7 > d8)) { + this.jumpInLiquid((Tag) FluidTags.LAVA); + } else if ((this.onGround || flag && d7 <= d8) && this.noJumpDelay == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + this.jumpFromGround(); + this.noJumpDelay = 10; ++ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop + } + } else { + this.noJumpDelay = 0; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 1621d8748e96c6e1abb33b699a1273bb698f67d2..423bdbe25b6f2261cb5092378b0564a82faeecb4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -489,7 +489,9 @@ public class Panda extends Animal { + Panda entitypanda = (Panda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround && !entitypanda.isInWater() && entitypanda.canPerformAction()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + entitypanda.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index a01bd035846df0e2e28dc55e2ef2f5f35b83f905..5dac3bf5a117bfbf57798238f0614558deafcd1b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -792,5 +792,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public org.bukkit.inventory.EquipmentSlot getHandRaised() { + return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; + } ++ ++ @Override ++ public boolean isJumping() { ++ return getHandle().jumping; ++ } ++ ++ @Override ++ public void setJumping(boolean jumping) { ++ getHandle().setJumping(jumping); ++ if (jumping && getHandle() instanceof Mob) { ++ // this is needed to actually make a mob jump ++ ((Mob) getHandle()).getJumpControl().jump(); ++ } ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0408-Add-option-to-nerf-pigmen-from-nether-portals.patch b/Remapped-Spigot-Server-Patches/0408-Add-option-to-nerf-pigmen-from-nether-portals.patch new file mode 100644 index 000000000..d66918d01 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0408-Add-option-to-nerf-pigmen-from-nether-portals.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Feb 2020 14:36:56 -0600 +Subject: [PATCH] Add option to nerf pigmen from nether portals + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 7fbd501d70dccf869a4454e2789a5d68f2e15754..9e4591ddc4b755f4ff5a6f1078b51cb13db80480 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -594,4 +594,9 @@ public class PaperWorldConfig { + disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents); + log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled")); + } ++ ++ public boolean nerfNetherPortalPigmen = false; ++ private void nerfNetherPortalPigmen() { ++ nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index efc9cb6def2f4ee327329dc090d2918ff60d8e19..43f77d01fceab107d3502d282205aa579d64cc4b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -277,6 +277,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public long activatedTick = Integer.MIN_VALUE; + public boolean isTemporarilyActive = false; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one ++ public boolean fromNetherPortal; // Paper + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end +@@ -1693,6 +1694,9 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + if (spawnedViaMobSpawner) { + tag.putBoolean("Paper.FromMobSpawner", true); + } ++ if (fromNetherPortal) { ++ tag.putBoolean("Paper.FromNetherPortal", true); ++ } + // Paper end + return tag; + } catch (Throwable throwable) { +@@ -1827,6 +1831,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + spawnedViaMobSpawner = tag.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = tag.getBoolean("Paper.FromNetherPortal"); + if (tag.contains("Paper.SpawnReason")) { + String spawnReasonName = tag.getString("Paper.SpawnReason"); + try { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index 805d83a93bce20910d17c3f419bc085251b6cfc1..ae58929886921d0714bf811de92f99dc0dc120dc 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -8,6 +8,7 @@ import net.minecraft.network.chat.Component; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.MobSpawnType; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.BlockGetter; +@@ -61,6 +62,8 @@ public class NetherPortalBlock extends Block { + + if (entity != null) { + entity.setPortalCooldown(); ++ entity.fromNetherPortal = true; // Paper ++ if (world.paperConfig.nerfNetherPortalPigmen) ((Mob) entity).aware = false; // Paper + } + } + } diff --git a/Remapped-Spigot-Server-Patches/0409-Make-the-GUI-graph-fancier.patch b/Remapped-Spigot-Server-Patches/0409-Make-the-GUI-graph-fancier.patch new file mode 100644 index 000000000..c98d2918e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0409-Make-the-GUI-graph-fancier.patch @@ -0,0 +1,439 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 2 Feb 2020 04:00:40 -0600 +Subject: [PATCH] Make the GUI graph fancier + + +diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphColor.java b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4e641fdcccd3efcd1a2865dc6dc28d50671b995 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java +@@ -0,0 +1,44 @@ ++package com.destroystokyo.paper.gui; ++ ++import java.awt.Color; ++ ++public class GraphColor { ++ private static final Color[] colorLine = new Color[101]; ++ private static final Color[] colorFill = new Color[101]; ++ ++ static { ++ for (int i = 0; i < 101; i++) { ++ Color color = createColor(i); ++ colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); ++ colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125); ++ } ++ } ++ ++ public static Color getLineColor(int percent) { ++ return colorLine[percent]; ++ } ++ ++ public static Color getFillColor(int percent) { ++ return colorFill[percent]; ++ } ++ ++ private static Color createColor(int percent) { ++ if (percent <= 50) { ++ return new Color(0X00FF00); ++ } ++ ++ int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510); ++ ++ int red, green; ++ if (value < 255) { ++ red = 255; ++ green = (int) (Math.sqrt(value) * 16); ++ } else { ++ green = 255; ++ value = value - 255; ++ red = 255 - (value * value / 255); ++ } ++ ++ return new Color(red, green, 0); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphData.java b/src/main/java/com/destroystokyo/paper/gui/GraphData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..186fc722965e403f76b1480e1c2381fc34e29049 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GraphData.java +@@ -0,0 +1,47 @@ ++package com.destroystokyo.paper.gui; ++ ++import java.awt.Color; ++ ++public class GraphData { ++ private long total; ++ private long free; ++ private long max; ++ private long usedMem; ++ private int usedPercent; ++ ++ public GraphData(long total, long free, long max) { ++ this.total = total; ++ this.free = free; ++ this.max = max; ++ this.usedMem = total - free; ++ this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max); ++ } ++ ++ public long getTotal() { ++ return total; ++ } ++ ++ public long getFree() { ++ return free; ++ } ++ ++ public long getMax() { ++ return max; ++ } ++ ++ public long getUsedMem() { ++ return usedMem; ++ } ++ ++ public int getUsedPercent() { ++ return usedPercent; ++ } ++ ++ public Color getFillColor() { ++ return GraphColor.getFillColor(usedPercent); ++ } ++ ++ public Color getLineColor() { ++ return GraphColor.getLineColor(usedPercent); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f29ad583e798c09b2fe3f568ed50cbc719e40e2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.gui; ++ ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.JPanel; ++import javax.swing.Timer; ++import java.awt.BorderLayout; ++import java.awt.Dimension; ++ ++public class GuiStatsComponent extends JPanel { ++ private final Timer timer; ++ private final RAMGraph ramGraph; ++ ++ public GuiStatsComponent(MinecraftServer server) { ++ super(new BorderLayout()); ++ ++ setOpaque(false); ++ ++ ramGraph = new RAMGraph(); ++ RAMDetails ramDetails = new RAMDetails(server); ++ ++ add(ramGraph, "North"); ++ add(ramDetails, "Center"); ++ ++ timer = new Timer(500, (event) -> { ++ ramGraph.update(); ++ ramDetails.update(); ++ }); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 200); ++ } ++ ++ public void stop() { a(); } public void a() { ++ timer.stop(); ++ ramGraph.stop(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0923ec75ecced2e0a1c0d3ec2c046d69af3e9a9 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.gui; ++ ++import net.minecraft.Util; ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.DefaultListCellRenderer; ++import javax.swing.DefaultListSelectionModel; ++import javax.swing.JList; ++import javax.swing.border.EmptyBorder; ++import java.awt.Dimension; ++import java.text.DecimalFormat; ++import java.text.DecimalFormatSymbols; ++import java.util.Locale; ++import java.util.Vector; ++ ++public class RAMDetails extends JList { ++ public static final DecimalFormat DECIMAL_FORMAT = Util.peek(new DecimalFormat("########0.000"), (format) ++ -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); ++ ++ private final MinecraftServer server; ++ ++ public RAMDetails(MinecraftServer server) { ++ this.server = server; ++ ++ setBorder(new EmptyBorder(0, 10, 0, 0)); ++ setFixedCellHeight(20); ++ setOpaque(false); ++ ++ DefaultListCellRenderer renderer = new DefaultListCellRenderer(); ++ renderer.setOpaque(false); ++ setCellRenderer(renderer); ++ ++ setSelectionModel(new DefaultListSelectionModel() { ++ @Override ++ public void setAnchorSelectionIndex(final int anchorIndex) { ++ } ++ ++ @Override ++ public void setLeadAnchorNotificationEnabled(final boolean flag) { ++ } ++ ++ @Override ++ public void setLeadSelectionIndex(final int leadIndex) { ++ } ++ ++ @Override ++ public void setSelectionInterval(final int index0, final int index1) { ++ } ++ }); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 100); ++ } ++ ++ public void update() { ++ GraphData data = RAMGraph.DATA.peekLast(); ++ Vector vector = new Vector<>(); ++ vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); ++ vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); ++ vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.getTickTimes())) + " ms"); ++ setListData(vector); ++ } ++ ++ public double getAverage(long[] tickTimes) { ++ long total = 0L; ++ for (long value : tickTimes) { ++ total += value; ++ } ++ return ((double) total / (double) tickTimes.length) * 1.0E-6D; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3e54da4ab6440811aab2f9dd1e218802ac13285 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +@@ -0,0 +1,144 @@ ++package com.destroystokyo.paper.gui; ++ ++import javax.swing.JComponent; ++import javax.swing.SwingUtilities; ++import javax.swing.Timer; ++import javax.swing.ToolTipManager; ++import java.awt.Color; ++import java.awt.Dimension; ++import java.awt.Graphics; ++import java.awt.MouseInfo; ++import java.awt.Point; ++import java.awt.PointerInfo; ++import java.awt.event.MouseAdapter; ++import java.awt.event.MouseEvent; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.LinkedList; ++import java.util.concurrent.TimeUnit; ++ ++public class RAMGraph extends JComponent { ++ public static final LinkedList DATA = new LinkedList() { ++ @Override ++ public boolean add(GraphData data) { ++ if (size() >= 348) { ++ remove(); ++ } ++ return super.add(data); ++ } ++ }; ++ ++ static { ++ GraphData empty = new GraphData(0, 0, 0); ++ for (int i = 0; i < 350; i++) { ++ DATA.add(empty); ++ } ++ } ++ ++ private final Timer timer; ++ private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); ++ ++ private int currentTick; ++ ++ public RAMGraph() { ++ ToolTipManager.sharedInstance().setInitialDelay(0); ++ ++ addMouseListener(new MouseAdapter() { ++ final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); ++ final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); ++ ++ @Override ++ public void mouseEntered(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes); ++ } ++ ++ @Override ++ public void mouseExited(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); ++ } ++ }); ++ ++ timer = new Timer(50, (event) -> repaint()); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 110); ++ } ++ ++ public void update() { ++ Runtime jvm = Runtime.getRuntime(); ++ DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory())); ++ ++ PointerInfo pointerInfo = MouseInfo.getPointerInfo(); ++ if (pointerInfo != null) { ++ Point point = pointerInfo.getLocation(); ++ if (point != null) { ++ Point loc = new Point(point); ++ SwingUtilities.convertPointFromScreen(loc, this); ++ if (this.contains(loc)) { ++ ToolTipManager.sharedInstance().mouseMoved( ++ new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y, ++ point.x, point.y, 0, false, 0)); ++ } ++ } ++ } ++ ++ currentTick++; ++ } ++ ++ @Override ++ public void paint(Graphics graphics) { ++ graphics.setColor(new Color(0xFFFFFFFF)); ++ graphics.fillRect(0, 0, 350, 100); ++ ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(1, 25, 348, 25); ++ graphics.drawLine(1, 50, 348, 50); ++ graphics.drawLine(1, 75, 348, 75); ++ ++ int i = 0; ++ for (GraphData data : DATA) { ++ i++; ++ if ((i + currentTick) % 120 == 0) { ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(i, 1, i, 99); ++ } ++ int used = data.getUsedPercent(); ++ if (used > 0) { ++ Color color = data.getLineColor(); ++ graphics.setColor(data.getFillColor()); ++ graphics.fillRect(i, 100 - used, 1, used); ++ graphics.setColor(color); ++ graphics.fillRect(i, 100 - used, 1, 1); ++ } ++ } ++ ++ graphics.setColor(new Color(0xFF000000)); ++ graphics.drawRect(0, 0, 348, 100); ++ ++ Point m = getMousePosition(); ++ if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) { ++ GraphData data = DATA.get(m.x); ++ int used = data.getUsedPercent(); ++ graphics.setColor(new Color(0x000000)); ++ graphics.drawLine(m.x, 1, m.x, 99); ++ graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5); ++ graphics.setColor(data.getLineColor()); ++ graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); ++ setToolTipText(String.format("Used: %s mb (%s%%)
    %s", ++ Math.round(data.getUsedMem() / 1024F / 1024F), ++ used, getTime(m.x))); ++ } ++ } ++ ++ public String getTime(int halfSeconds) { ++ int millis = (348 - halfSeconds) / 2 * 1000; ++ return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis))); ++ } ++ ++ public void stop() { ++ timer.stop(); ++ } ++} +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index cec5ad5052c8cf6059e9b117117846bdb217748f..c2f747226f10479c826849af898538610a2dd659 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -259,6 +259,7 @@ public class Util { + return factory.get(); + } + ++ public static T peek(T t0, Consumer consumer) { return make(t0, consumer); } // Paper - OBFHELPER + public static T make(T object, Consumer initializer) { + initializer.accept(object); + return object; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7038897b8fb4c18ca97b95a3b24c30b40b62b005..e33189dc8375a3034910087654607fb531061636 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -216,7 +216,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ private static final DecimalFormat DECIMAL_FORMAT = (DecimalFormat) Util.make(new DecimalFormat("########0.000"), (decimalformat) -> { // Paper - decompile error + decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); + }); + private final int[] values = new int[256]; diff --git a/Remapped-Spigot-Server-Patches/0410-add-hand-to-BlockMultiPlaceEvent.patch b/Remapped-Spigot-Server-Patches/0410-add-hand-to-BlockMultiPlaceEvent.patch new file mode 100644 index 000000000..e93352bf0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0410-add-hand-to-BlockMultiPlaceEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sun, 1 Mar 2020 22:43:24 +0100 +Subject: [PATCH] add hand to BlockMultiPlaceEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e696d2e52532df25d74a1f559e2c9ca0f3d5058d..7d9a3b65b2d6b294d3a11414289e64fac88665f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -337,13 +337,18 @@ public class CraftEventFactory { + } + + org.bukkit.inventory.ItemStack item; ++ //Paper start - add hand to BlockMultiPlaceEvent ++ EquipmentSlot equipmentSlot; + if (hand == InteractionHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); ++ equipmentSlot = EquipmentSlot.HAND; + } else { + item = player.getInventory().getItemInOffHand(); ++ equipmentSlot = EquipmentSlot.OFF_HAND; + } + +- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); ++ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); ++ //Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/Remapped-Spigot-Server-Patches/0411-Prevent-teleporting-dead-entities.patch b/Remapped-Spigot-Server-Patches/0411-Prevent-teleporting-dead-entities.patch new file mode 100644 index 000000000..ef6164bd2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0411-Prevent-teleporting-dead-entities.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 3 Mar 2020 05:26:40 +0000 +Subject: [PATCH] Prevent teleporting dead entities + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2b79413bb8a592a7b7093e11d3a0cce895286c8f..09a663cc53cdf8ae45352b280200c8170dbbcdfc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1468,6 +1468,10 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + + private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { ++ if (player.removed) { ++ LOGGER.info("Attempt to teleport dead player {} restricted", player.getScoreboardName()); ++ return; ++ } + // CraftBukkit start + if (Float.isNaN(f)) { + f = 0; diff --git a/Remapped-Spigot-Server-Patches/0412-Validate-tripwire-hook-placement-before-update.patch b/Remapped-Spigot-Server-Patches/0412-Validate-tripwire-hook-placement-before-update.patch new file mode 100644 index 000000000..8fbefb7ae --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0412-Validate-tripwire-hook-placement-before-update.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 7 Mar 2020 00:07:51 +0000 +Subject: [PATCH] Validate tripwire hook placement before update + + +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index f44d4809fe87c832e4d3bda429af2254e8c746d6..20428aff54527b664d988a6a0f54236b693fda89 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -169,6 +169,7 @@ public class TripWireHookBlock extends Block { + + this.playSound(world, pos, flag4, flag5, flag2, flag3); + if (!beingRemoved) { ++ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - validate + world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); + if (flag1) { + this.notifyNeighbors(world, pos, enumdirection); diff --git a/Remapped-Spigot-Server-Patches/0413-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/Remapped-Spigot-Server-Patches/0413-Add-option-to-allow-iron-golems-to-spawn-in-air.patch new file mode 100644 index 000000000..163f5b924 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0413-Add-option-to-allow-iron-golems-to-spawn-in-air.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 13 Apr 2019 16:50:58 -0500 +Subject: [PATCH] Add option to allow iron golems to spawn in air + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9e4591ddc4b755f4ff5a6f1078b51cb13db80480..fe1c9dd8258ec8c3fdf343d4a44de2be2ae3d35f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -387,6 +387,11 @@ public class PaperWorldConfig { + scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); + } + ++ public boolean ironGolemsCanSpawnInAir = false; ++ private void ironGolemsCanSpawnInAir() { ++ ironGolemsCanSpawnInAir = getBoolean("iron-golems-can-spawn-in-air", ironGolemsCanSpawnInAir); ++ } ++ + public boolean armorStandEntityLookups = true; + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); +diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +index 9cedac83304847bdc92c2e97c4e6e119664c3bd0..59f0224b6a743408a03b642dd7cbe545f406c57e 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +@@ -297,7 +297,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata = world.getBlockState(blockposition1); + +- if (!iblockdata.entityCanStandOn((BlockGetter) world, blockposition1, (Entity) this)) { ++ if (!iblockdata.entityCanStandOn((BlockGetter) world, blockposition1, (Entity) this) && !level.paperConfig.ironGolemsCanSpawnInAir) { // Paper + return false; + } else { + for (int i = 1; i < 3; ++i) { diff --git a/Remapped-Spigot-Server-Patches/0414-Configurable-chance-of-villager-zombie-infection.patch b/Remapped-Spigot-Server-Patches/0414-Configurable-chance-of-villager-zombie-infection.patch new file mode 100644 index 000000000..1bf93843e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0414-Configurable-chance-of-villager-zombie-infection.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zero +Date: Sat, 22 Feb 2020 16:10:31 -0500 +Subject: [PATCH] Configurable chance of villager zombie infection + +This allows you to solve an issue in vanilla behavior where: +* On easy difficulty your villagers will NEVER get infected, meaning they will always die. +* On normal difficulty they will have a 50% of getting infected or dying. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fe1c9dd8258ec8c3fdf343d4a44de2be2ae3d35f..525d702d78a609af987ebd2c32169b873e5c05ed 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -604,4 +604,9 @@ public class PaperWorldConfig { + private void nerfNetherPortalPigmen() { + nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen); + } ++ ++ public double zombieVillagerInfectionChance = -1.0; ++ private void zombieVillagerInfectionChance() { ++ zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 6fecf4188fc0247143edc688c03e645376960687..1e7c2c603b967c8c606efd94ce95a17c856f78d7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -447,10 +447,14 @@ public class Zombie extends Monster { + @Override + public void killed(ServerLevel worldserver, LivingEntity entityliving) { + super.killed(worldserver, entityliving); +- if ((worldserver.getDifficulty() == Difficulty.NORMAL || worldserver.getDifficulty() == Difficulty.HARD) && entityliving instanceof Villager) { +- if (worldserver.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { ++ // Paper start ++ if (level.paperConfig.zombieVillagerInfectionChance != 0.0 && (level.paperConfig.zombieVillagerInfectionChance != -1.0 || worldserver.getDifficulty() == Difficulty.NORMAL || worldserver.getDifficulty() == Difficulty.HARD) && entityliving instanceof Villager) { ++ if (level.paperConfig.zombieVillagerInfectionChance == -1.0 && worldserver.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { + return; + } ++ if (level.paperConfig.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > level.paperConfig.zombieVillagerInfectionChance) { ++ return; ++ } // Paper end + + Villager entityvillager = (Villager) entityliving; + // CraftBukkit start diff --git a/Remapped-Spigot-Server-Patches/0415-Optimise-Chunk-getFluid.patch b/Remapped-Spigot-Server-Patches/0415-Optimise-Chunk-getFluid.patch new file mode 100644 index 000000000..2ba043c07 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0415-Optimise-Chunk-getFluid.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 14 Jan 2020 14:59:08 -0800 +Subject: [PATCH] Optimise Chunk#getFluid + +Removing the try catch and generally reducing ops should make it +faster on its own, however removing the try catch makes it +easier to inline due to code size + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 87d7a87a2925f2c062658e960bb5128738828d9f..8a14bdda4a408ec1e2b51efeb35467835f62b42c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -430,25 +430,29 @@ public class LevelChunk implements ChunkAccess { + } + + public FluidState getFluidState(int x, int y, int z) { +- try { +- if (y >= 0 && y >> 4 < this.sections.length) { +- LevelChunkSection chunksection = this.sections[y >> 4]; +- +- if (!LevelChunkSection.isEmpty(chunksection)) { +- return chunksection.getFluidState(x & 15, y & 15, z & 15); ++ //try { // Paper - remove try catch ++ // Paper start - reduce the number of ops in this call ++ int index = y >> 4; ++ if (index >= 0 && index < this.sections.length) { ++ LevelChunkSection chunksection = this.sections[index]; ++ ++ if (chunksection != null) { ++ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); + } ++ // Paper end + } + + return Fluids.EMPTY.defaultFluidState(); +- } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); ++ /*} catch (Throwable throwable) { // Paper - remove try catch ++ CrashReport crashreport = CrashReport.a(throwable, "Getting fluid state"); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being got"); + +- crashreportsystemdetails.setDetail("Location", () -> { +- return CrashReportCategory.formatLocation(x, y, z); ++ crashreportsystemdetails.a("Location", () -> { ++ return CrashReportSystemDetails.a(i, j, k); + }); + throw new ReportedException(crashreport); + } ++ */ // Paper - remove try catch + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index c44d32f966c61497b4a8892eb51da3a71ad031d9..5e7f6000df129100ef306703f325af9f60da8ae6 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -45,7 +45,7 @@ public class LevelChunkSection { + } + + public FluidState getFluidState(int x, int y, int z) { +- return ((BlockState) this.states.get(x, y, z)).getFluidState(); ++ return ((BlockState) this.states.get(x, y, z)).getFluidState(); // Paper - diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid. + } + + public void acquire() { diff --git a/Remapped-Spigot-Server-Patches/0416-Optimise-TickListServer-by-rewriting-it.patch b/Remapped-Spigot-Server-Patches/0416-Optimise-TickListServer-by-rewriting-it.patch new file mode 100644 index 000000000..6cf945010 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0416-Optimise-TickListServer-by-rewriting-it.patch @@ -0,0 +1,1301 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 14 Feb 2020 01:24:39 -0800 +Subject: [PATCH] Optimise TickListServer by rewriting it + +In my profiling TickListServer showed up as +~10% for saving chunks and ~5% for the scheduling +of items on a server with ~90 players at +view distance = 5. Most of the performance +loss is unneccessary. + +TickListServer has numerous performance issues: + 1. Handling scheduled items is O(nlogn) + 2. Getting scheduled items for a chunk is O(n), + with n being the the number of scheduled items + for all chunks (hits saving very hard) + 3. Checking if an item is scheduled for the current tick is O(n), + with n being the number of items scheduled for current tick + 4. Items not in ticking chunks are churned in the scheduler + +The biggest issues are 4 & 2. + +We solve 1 by splitting up scheduled items into short and long scheduled, +where we expect the vast majority of our entries to be in the short scheduled +set. Handling short scheduled items is O(n) due to how the comparison +process is reduced to mapping. See TickListServerInterval. However, +this isn't memory-efficient - which is why long scheduled exists. +Long scheduled is handled the same as TickListServer. + +2 is solved by mapping what entries are in what chunks. + +3 is solved by mapping what blocks have what scheduled for them. + +4 is solved by moving the items that are not in ticking chunks +into a map of entries for that chunk. Once the chunk is moved +to ticking, the items are re-scheduled. + +This patch has also added two flags to debug excessive tick delays: +-Dpaper.ticklist-warn-on-excessive-delay=true (false by default) +and -Dpaper.ticklist-excessive-delay-threshold=ticks which +sets the excessive tick delay to the specified ticks (defaults to +60 * 20 ticks, aka 60 seconds) + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 8bf4d2b8c38c02d6a5b2fea37113689a252f1571..da93d38fe63035e4ff198ada84a4431f52d97c01 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -354,6 +354,13 @@ public class PaperConfig { + maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); + } + ++ public static boolean useOptimizedTickList = true; ++ private static void useOptimizedTickList() { ++ if (config.contains("settings.use-optimized-ticklist")) { // don't add default, hopefully temporary config ++ useOptimizedTickList = config.getBoolean("settings.use-optimized-ticklist"); ++ } ++ } ++ + public static boolean asyncChunks = false; + private static void asyncChunks() { + ConfigurationSection section; +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1ce556fb808a8c93106e26da7b24f9a2d579909b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +@@ -0,0 +1,628 @@ ++package com.destroystokyo.paper.server.ticklist; ++ ++import java.util.function.Function; ++import net.minecraft.CrashReport; ++import net.minecraft.CrashReportCategory; ++import net.minecraft.ReportedException; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.ServerTickList; ++import net.minecraft.world.level.TickNextTickData; ++import net.minecraft.world.level.TickPriority; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.levelgen.structure.BoundingBox; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++ ++public final class PaperTickList extends ServerTickList { // extend to avoid breaking ABI ++ ++ // in the order the state is expected to change (mostly) ++ public static final int STATE_UNSCHEDULED = 1 << 0; ++ public static final int STATE_SCHEDULED = 1 << 1; // scheduled for some tick ++ public static final int STATE_PENDING_TICK = 1 << 2; // for this tick ++ public static final int STATE_TICKING = 1 << 3; ++ public static final int STATE_TICKED = 1 << 4; // after this, it gets thrown back to unscheduled ++ public static final int STATE_CANCELLED_TICK = 1 << 5; // still gets moved to unscheduled after tick ++ ++ private static final int SHORT_SCHEDULE_TICK_THRESHOLD = 20 * 20 + 1; // 20 seconds ++ ++ private final ServerLevel world; ++ private final Predicate excludeFromScheduling; ++ private final Function getMinecraftKeyFrom; ++ //private final Function getObjectFronMinecraftKey; ++ private final Consumer> tickFunction; ++ ++ private final co.aikar.timings.Timing timingCleanup; // Paper ++ private final co.aikar.timings.Timing timingTicking; // Paper ++ private final co.aikar.timings.Timing timingFinished; ++ ++ // note: remove ops / add ops suck on fastutil, a chained hashtable implementation would work better, but Long... ++ // try to alleviate with a very small load factor ++ private final Long2ObjectOpenHashMap>> entriesByBlock = new Long2ObjectOpenHashMap<>(1024, 0.25f); ++ private final Long2ObjectOpenHashMap>> entriesByChunk = new Long2ObjectOpenHashMap<>(1024, 0.25f); ++ private final Long2ObjectOpenHashMap>> pendingChunkTickLoad = new Long2ObjectOpenHashMap<>(1024, 0.5f); ++ ++ // fastutil has O(1) first/last while TreeMap/TreeSet are log(n) ++ private final ObjectRBTreeSet> longScheduled = new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); ++ ++ private final ArrayDeque> toTickThisTick = new ArrayDeque<>(); ++ ++ private final TickListServerInterval[] shortScheduled = new TickListServerInterval[SHORT_SCHEDULE_TICK_THRESHOLD]; ++ { ++ for (int i = 0, len = this.shortScheduled.length; i < len; ++i) { ++ this.shortScheduled[i] = new TickListServerInterval<>(); ++ } ++ } ++ private int shortScheduledIndex; ++ ++ private long currentTick; ++ ++ private static final boolean WARN_ON_EXCESSIVE_DELAY = Boolean.getBoolean("paper.ticklist-warn-on-excessive-delay"); ++ private static final long EXCESSIVE_DELAY_THRESHOLD = Long.getLong("paper.ticklist-excessive-delay-threshold", 60 * 20).longValue(); // 1 min dfl ++ ++ // assume index < length ++ private static int getWrappedIndex(final int start, final int length, final int index) { ++ final int next = start + index; ++ return next < length ? next : next - length; ++ } ++ ++ private static int getNextIndex(final int curr, final int length) { ++ final int next = curr + 1; ++ return next < length ? next : 0; ++ } ++ ++ public PaperTickList(final ServerLevel world, final Predicate excludeFromScheduling, final Function getMinecraftKeyFrom, ++ final Consumer> tickFunction, final String timingsType) { ++ super(world, excludeFromScheduling, getMinecraftKeyFrom, tickFunction, timingsType); ++ this.world = world; ++ this.excludeFromScheduling = excludeFromScheduling; ++ this.getMinecraftKeyFrom = getMinecraftKeyFrom; ++ this.tickFunction = tickFunction; ++ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Cleanup"); // Paper ++ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Ticking"); // Paper ++ this.timingFinished = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Finish"); ++ this.currentTick = this.world.getGameTime(); ++ } ++ ++ private void queueEntryForTick(final TickNextTickData entry, final ServerChunkCache chunkProvider) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ if (chunkProvider.isTickingReadyMainThread(entry.getPosition())) { ++ this.toTickThisTick.add(entry); ++ entry.tickState = STATE_PENDING_TICK; ++ } else { ++ // we dump them to a map to avoid constantly re-scheduling them ++ this.addToNotTickingReady(entry); ++ } ++ } ++ } ++ ++ private void addToNotTickingReady(final TickNextTickData entry) { ++ this.pendingChunkTickLoad.computeIfAbsent(MCUtil.getCoordinateKey(entry.getPosition()), (long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(entry); ++ } ++ ++ private void addToSchedule(final TickNextTickData entry) { ++ long delay = entry.getTargetTick() - (this.currentTick + 1); ++ if (delay < SHORT_SCHEDULE_TICK_THRESHOLD) { ++ if (delay < 0) { ++ // longScheduled orders by tick time, short scheduled does not ++ this.longScheduled.add(entry); ++ } else { ++ this.shortScheduled[getWrappedIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD, (int)delay)].addEntryLast(entry); ++ } ++ } else { ++ this.longScheduled.add(entry); ++ } ++ } ++ ++ private void removeEntry(final TickNextTickData entry) { ++ entry.tickState = STATE_CANCELLED_TICK; ++ // short/long scheduled will skip the entry ++ ++ final BlockPos pos = entry.getPosition(); ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); ++ ++ if (currentEntries.size() == 1) { ++ // it should contain our entry ++ this.entriesByBlock.remove(blockKey); ++ } else { ++ // it's more likely that this entry is at the start of the list than the end ++ for (int i = 0, len = currentEntries.size(); i < len; ++i) { ++ final TickNextTickData currentEntry = currentEntries.get(i); ++ if (currentEntry == entry) { ++ currentEntries.remove(i); ++ break; ++ } ++ } ++ } ++ ++ final long chunkKey = MCUtil.getCoordinateKey(entry.getPosition()); ++ ++ ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); ++ ++ set.remove(entry); ++ ++ if (set.isEmpty()) { ++ this.entriesByChunk.remove(chunkKey); ++ } ++ ++ ArrayList> pendingTickingLoad = this.pendingChunkTickLoad.get(chunkKey); ++ ++ if (pendingTickingLoad != null) { ++ for (int i = 0, len = pendingTickingLoad.size(); i < len; ++i) { ++ if (pendingTickingLoad.get(i) == entry) { ++ pendingTickingLoad.remove(i); ++ break; ++ } ++ } ++ ++ if (pendingTickingLoad.isEmpty()) { ++ this.pendingChunkTickLoad.remove(chunkKey); ++ } ++ } ++ ++ long delay = entry.getTargetTick() - (this.currentTick + 1); ++ if (delay >= SHORT_SCHEDULE_TICK_THRESHOLD) { ++ this.longScheduled.remove(entry); ++ } ++ } ++ ++ public void onChunkSetTicking(final int chunkX, final int chunkZ) { ++ final ArrayList> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ if (pending == null) { ++ return; ++ } ++ ++ for (int i = 0, size = pending.size(); i < size; ++i) { ++ final TickNextTickData entry = pending.get(i); ++ // already in all the relevant reference maps, just need to add to longScheduled or shortScheduled ++ this.addToSchedule(entry); ++ } ++ } ++ ++ private void prepare() { ++ final long currentTick = this.currentTick; ++ ++ final ServerChunkCache chunkProvider = this.world.getChunkSource(); ++ ++ // here we setup what's going to tick ++ ++ // we don't remove items from shortScheduled (but do from longScheduled) because they're cleared at the end of ++ // this tick ++ if (this.longScheduled.isEmpty() || this.longScheduled.first().getTargetTick() > currentTick) { ++ // nothing in longScheduled to worry about ++ final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; ++ for (int i = 0, len = interval.byPriority.length; i < len; ++i) { ++ for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { ++ this.queueEntryForTick(iterator.next(), chunkProvider); ++ } ++ } ++ } else { ++ final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; ++ ++ // combine interval and longScheduled, keeping order ++ final Comparator> comparator = (Comparator)TickListServerInterval.ENTRY_COMPARATOR; ++ final Iterator> longScheduledIterator = this.longScheduled.iterator(); ++ TickNextTickData longCurrent = longScheduledIterator.next(); ++ ++ for (int i = 0, len = interval.byPriority.length; i < len; ++i) { ++ for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { ++ final TickNextTickData shortCurrent = iterator.next(); ++ if (longCurrent != null) { ++ // drain longCurrent until we can add shortCurrent ++ while (comparator.compare(longCurrent, shortCurrent) <= 0) { ++ this.queueEntryForTick(longCurrent, chunkProvider); ++ longScheduledIterator.remove(); ++ if (longScheduledIterator.hasNext()) { ++ longCurrent = longScheduledIterator.next(); ++ if (longCurrent.getTargetTick() > currentTick) { ++ longCurrent = null; ++ break; ++ } ++ } else { ++ longCurrent = null; ++ break; ++ } ++ } ++ } ++ this.queueEntryForTick(shortCurrent, chunkProvider); ++ } ++ } ++ ++ // add remaining from long scheduled ++ for (;;) { ++ if (longCurrent == null || longCurrent.getTargetTick() > currentTick) { ++ break; ++ } ++ longScheduledIterator.remove(); ++ this.queueEntryForTick(longCurrent, chunkProvider); ++ ++ if (longScheduledIterator.hasNext()) { ++ longCurrent = longScheduledIterator.next(); ++ } else { ++ break; ++ } ++ } ++ } ++ } ++ ++ private boolean warnedAboutDesync; ++ ++ @Override ++ public void nextTick() { ++ ++this.currentTick; ++ if (this.currentTick != this.world.getGameTime()) { ++ if (!this.warnedAboutDesync) { ++ this.warnedAboutDesync = true; ++ MinecraftServer.LOGGER.error("World tick desync detected! Expected " + this.currentTick + " ticks, but got " + this.world.getGameTime() + " ticks for world '" + this.world.getWorld().getName() + "'", new Throwable()); ++ MinecraftServer.LOGGER.error("Preventing redstone from breaking by refusing to accept new tick time"); ++ } ++ } ++ } ++ ++ @Override ++ public void tick() { ++ final ServerChunkCache chunkProvider = this.world.getChunkSource(); ++ ++ this.world.getProfiler().push("cleaning"); ++ this.timingCleanup.startTiming(); ++ ++ this.prepare(); ++ ++ // this must be done here in case something schedules in the tick code ++ this.shortScheduled[this.shortScheduledIndex].clear(); ++ this.shortScheduledIndex = getNextIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD); ++ ++ this.timingCleanup.stopTiming(); ++ this.world.getProfiler().popPush("ticking"); ++ this.timingTicking.startTiming(); ++ ++ for (final TickNextTickData toTick : this.toTickThisTick) { ++ if (toTick.tickState != STATE_PENDING_TICK) { ++ // onTickEnd gets called at end of tick ++ continue; ++ } ++ try { ++ if (chunkProvider.isTickingReadyMainThread(toTick.getPosition())) { ++ toTick.tickState = STATE_TICKING; ++ this.tickFunction.accept(toTick); ++ if (toTick.tickState == STATE_TICKING) { ++ toTick.tickState = STATE_TICKED; ++ } // else it's STATE_CANCELLED_TICK ++ } else { ++ // re-schedule eventually ++ toTick.tickState = STATE_SCHEDULED; ++ this.addToNotTickingReady(toTick); ++ } ++ } catch (final Throwable thr) { ++ // start copy from TickListServer // TODO check on update ++ CrashReport crashreport = CrashReport.forThrowable(thr, "Exception while ticking"); ++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being ticked"); ++ ++ CrashReportCategory.populateBlockDetails(crashreportsystemdetails, toTick.getPosition(), (BlockState) null); ++ throw new ReportedException(crashreport); ++ // end copy from TickListServer ++ } ++ } ++ ++ this.timingTicking.stopTiming(); ++ this.world.getProfiler().pop(); ++ this.timingFinished.startTiming(); ++ ++ // finished ticking, actual cleanup time ++ for (int i = 0, len = this.toTickThisTick.size(); i < len; ++i) { ++ final TickNextTickData entry = this.toTickThisTick.poll(); ++ if (entry.tickState != STATE_SCHEDULED) { ++ // some entries get re-scheduled due to their chunk not being loaded/at correct status, so do not ++ // call onTickEnd for them ++ this.onTickEnd(entry); ++ } ++ } ++ ++ this.timingFinished.stopTiming(); ++ } ++ ++ private void onTickEnd(final TickNextTickData entry) { ++ if (entry.tickState == STATE_CANCELLED_TICK) { ++ return; ++ } ++ entry.tickState = STATE_UNSCHEDULED; ++ ++ final BlockPos pos = entry.getPosition(); ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); ++ ++ if (currentEntries.size() == 1) { ++ // it should contain our entry ++ this.entriesByBlock.remove(blockKey); ++ } else { ++ // it's more likely that this entry is at the start of the list than the end ++ for (int i = 0, len = currentEntries.size(); i < len; ++i) { ++ final TickNextTickData currentEntry = currentEntries.get(i); ++ if (currentEntry == entry) { ++ currentEntries.remove(i); ++ break; ++ } ++ } ++ } ++ ++ final long chunkKey = MCUtil.getCoordinateKey(entry.getPosition()); ++ ++ ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); ++ ++ set.remove(entry); ++ ++ if (set.isEmpty()) { ++ this.entriesByChunk.remove(chunkKey); ++ } ++ ++ // already removed from longScheduled or shortScheduled ++ } ++ ++ @Override ++ public boolean isPendingTickThisTick(final BlockPos blockposition, final T data) { ++ final ArrayList> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition)); ++ ++ if (entries == null) { ++ return false; ++ } ++ ++ for (int i = 0, size = entries.size(); i < size; ++i) { ++ final TickNextTickData entry = entries.get(i); ++ if (entry.getData() == data && entry.tickState == STATE_PENDING_TICK) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean isScheduledForTick(final BlockPos blockposition, final T data) { ++ final ArrayList> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition)); ++ ++ if (entries == null) { ++ return false; ++ } ++ ++ for (int i = 0, size = entries.size(); i < size; ++i) { ++ final TickNextTickData entry = entries.get(i); ++ if (entry.getData() == data && entry.tickState == STATE_SCHEDULED) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void schedule(BlockPos blockPosition, T t, int i, TickPriority tickListPriority) { ++ this.schedule(blockPosition, t, i + this.currentTick, tickListPriority); ++ } ++ ++ public void schedule(final TickNextTickData entry) { ++ this.schedule(entry.getPosition(), entry.getData(), entry.getTargetTick(), entry.getPriority()); ++ } ++ ++ public void schedule(final BlockPos pos, final T data, final long targetTick, final TickPriority priority) { ++ final TickNextTickData entry = new TickNextTickData<>(pos, data, targetTick, priority); ++ if (this.excludeFromScheduling.test(entry.getData())) { ++ return; ++ } ++ ++ if (WARN_ON_EXCESSIVE_DELAY) { ++ final long delay = entry.getTargetTick() - this.currentTick; ++ if (delay >= EXCESSIVE_DELAY_THRESHOLD) { ++ MinecraftServer.LOGGER.warn("Entry " + entry.toString() + " has been scheduled with an excessive delay of: " + delay, new Throwable()); ++ } ++ } ++ ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.computeIfAbsent(blockKey, (long keyInMap) -> new ArrayList<>(3)); ++ ++ if (currentEntries.isEmpty()) { ++ currentEntries.add(entry); ++ } else { ++ for (int i = 0, size = currentEntries.size(); i < size; ++i) { ++ final TickNextTickData currentEntry = currentEntries.get(i); ++ ++ // entries are only blocked from scheduling if currentEntry.equals(toSchedule) && currentEntry is scheduled to tick (NOT including pending) ++ if (currentEntry.getData() == entry.getData() && currentEntry.tickState == STATE_SCHEDULED) { ++ // can't add ++ return; ++ } ++ } ++ currentEntries.add(entry); ++ } ++ ++ entry.tickState = STATE_SCHEDULED; ++ ++ this.entriesByChunk.computeIfAbsent(MCUtil.getCoordinateKey(entry.getPosition()), (final long keyInMap) -> { ++ return new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); ++ }).add(entry); ++ ++ this.addToSchedule(entry); ++ } ++ ++ public void scheduleAll(final Iterator> iterator) { ++ while (iterator.hasNext()) { ++ this.schedule(iterator.next()); ++ } ++ } ++ ++ // this is not the standard interception calculation, but it's the one vanilla uses ++ // i.e the y value is ignored? the x, z calc isn't correct? ++ // however for the copy op they use the correct intersection, after using this one of course... ++ private static boolean isBlockInSortof(final BoundingBox boundingBox, final BlockPos pos) { ++ return pos.getX() >= boundingBox.getMinX() && pos.getX() < boundingBox.getMaxX() && pos.getZ() >= boundingBox.getMinZ() && pos.getZ() < boundingBox.getMaxZ(); ++ } ++ ++ @Override ++ public List> getEntriesInBoundingBox(final BoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { ++ if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { ++ return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above ++ } ++ ++ final int lowerChunkX = structureboundingbox.getMinX() >> 4; ++ final int upperChunkX = (structureboundingbox.getMaxX() - 1) >> 4; // subtract 1 since maxX is exclusive ++ final int lowerChunkZ = structureboundingbox.getMinZ() >> 4; ++ final int upperChunkZ = (structureboundingbox.getMaxZ() - 1) >> 4; // subtract 1 since maxZ is exclusive ++ ++ final int xChunksLength = (upperChunkX - lowerChunkX + 1); ++ final int zChunksLength = (upperChunkZ - lowerChunkZ + 1); ++ ++ final ObjectRBTreeSet>[] containingChunks = new ObjectRBTreeSet[xChunksLength * zChunksLength]; ++ ++ final int offset = (xChunksLength * -lowerChunkZ - lowerChunkX); ++ int totalEntries = 0; ++ for (int currChunkX = lowerChunkX; currChunkX <= upperChunkX; ++currChunkX) { ++ for (int currChunkZ = lowerChunkZ; currChunkZ <= upperChunkZ; ++currChunkZ) { ++ // todo optimize ++ //final int index = (currChunkX - lowerChunkX) + xChunksLength * (currChunkZ - lowerChunkZ); ++ final int index = offset + currChunkX + xChunksLength * currChunkZ; ++ final ObjectRBTreeSet> set = containingChunks[index] = this.entriesByChunk.get(MCUtil.getCoordinateKey(currChunkX, currChunkZ)); ++ if (set != null) { ++ totalEntries += set.size(); ++ } ++ } ++ } ++ ++ final List> ret = new ArrayList<>(totalEntries); ++ ++ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); ++ ++ MCUtil.mergeSortedSets((TickNextTickData entry) -> { ++ if (!isBlockInSortof(structureboundingbox, entry.getPosition())) { ++ return; ++ } ++ final int tickState = entry.tickState; ++ if ((tickState & matchOne) == 0) { ++ return; ++ } ++ ++ ret.add(entry); ++ return; ++ }, TickListServerInterval.ENTRY_COMPARATOR, containingChunks); ++ ++ if (removeReturned) { ++ for (TickNextTickData entry : ret) { ++ this.removeEntry(entry); ++ } ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void copy(BoundingBox structureboundingbox, BlockPos blockposition) { ++ // start copy from TickListServer // TODO check on update ++ List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); ++ Iterator> iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ TickNextTickData nextticklistentry = iterator.next(); ++ ++ if (structureboundingbox.hasPoint( nextticklistentry.getPosition())) { ++ BlockPos blockposition1 = nextticklistentry.getPosition().add(blockposition); ++ T t0 = nextticklistentry.getData(); ++ ++ this.schedule(new TickNextTickData<>(blockposition1, t0, nextticklistentry.getTargetTick(), nextticklistentry.getPriority())); ++ } ++ } ++ // end copy from TickListServer ++ } ++ ++ @Override ++ public List> getEntriesInChunk(ChunkPos chunkPos, boolean removeReturned, boolean excludeTicked) { ++ // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks ++ // not at ticking status, and ticking status requires neighbours loaded ++ // so with this method we will reduce scheduler churning ++ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); ++ ++ final ObjectRBTreeSet> entries = this.entriesByChunk.get(MCUtil.getCoordinateKey(chunkPos)); ++ ++ if (entries == null) { ++ return Collections.emptyList(); ++ } ++ ++ final List> ret = new ArrayList<>(entries.size()); ++ ++ for (TickNextTickData entry : entries) { ++ if ((entry.tickState & matchOne) == 0) { ++ continue; ++ } ++ ret.add(entry); ++ } ++ ++ if (removeReturned) { ++ for (TickNextTickData entry : ret) { ++ this.removeEntry(entry); ++ } ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public ListTag serialize(ChunkPos chunkcoordintpair) { ++ // start copy from TickListServer // TODO check on update ++ List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); ++ ++ return ServerTickList.serialize(this.getMinecraftKeyFrom, list, this.currentTick); ++ // end copy from TickListServer ++ } ++ ++ @Override ++ public int getTotalScheduledEntries() { ++ // good thing this is only used in debug reports // TODO check on update ++ int ret = 0; ++ ++ for (TickNextTickData entry : this.longScheduled) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ ++ for (Iterator>>> iterator = this.pendingChunkTickLoad.long2ObjectEntrySet().iterator(); iterator.hasNext();) { ++ ArrayList> list = iterator.next().getValue(); ++ ++ for (TickNextTickData entry : list) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ } ++ ++ for (TickListServerInterval interval : this.shortScheduled) { ++ for (Iterable> set : interval.byPriority) { ++ for (TickNextTickData entry : set) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d40a7aff6c27883f3ae8ba878a94c97242619f2c +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.server.ticklist; ++ ++import com.destroystokyo.paper.util.set.LinkedSortedSet; ++import java.util.Comparator; ++import net.minecraft.world.level.TickNextTickData; ++import net.minecraft.world.level.TickPriority; ++ ++// represents a set of entries to tick at a specified time ++public final class TickListServerInterval { ++ ++ public static final int TOTAL_PRIORITIES = TickPriority.values().length; ++ public static final Comparator> ENTRY_COMPARATOR_BY_ID = (entry1, entry2) -> { ++ return Long.compare(entry1.getId(), entry2.getId()); ++ }; ++ public static final Comparator> ENTRY_COMPARATOR = (Comparator)TickNextTickData.comparator(); ++ ++ // we do not record the interval, this class is meant to be used on a ring buffer ++ ++ // inlined enum map for TickListPriority ++ public final LinkedSortedSet>[] byPriority = new LinkedSortedSet[TOTAL_PRIORITIES]; ++ ++ { ++ for (int i = 0, len = this.byPriority.length; i < len; ++i) { ++ this.byPriority[i] = new LinkedSortedSet<>(ENTRY_COMPARATOR_BY_ID); ++ } ++ } ++ ++ public void addEntryLast(final TickNextTickData entry) { ++ this.byPriority[entry.getPriority().ordinal()].addLast(entry); ++ } ++ ++ public void addEntryFirst(final TickNextTickData entry) { ++ this.byPriority[entry.getPriority().ordinal()].addFirst(entry); ++ } ++ ++ public void clear() { ++ for (int i = 0, len = this.byPriority.length; i < len; ++i) { ++ this.byPriority[i].clear(); // O(1) clear ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..118988c39e58f28e8a2851792b9c014f341f06fc +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java +@@ -0,0 +1,142 @@ ++package com.destroystokyo.paper.util.set; ++ ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++public final class LinkedSortedSet implements Iterable { ++ ++ public final Comparator comparator; ++ ++ protected Link head; ++ protected Link tail; ++ ++ public LinkedSortedSet() { ++ this((Comparator)Comparator.naturalOrder()); ++ } ++ ++ public LinkedSortedSet(final Comparator comparator) { ++ this.comparator = comparator; ++ } ++ ++ public void clear() { ++ this.head = this.tail = null; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ Link next = LinkedSortedSet.this.head; ++ ++ @Override ++ public boolean hasNext() { ++ return this.next != null; ++ } ++ ++ @Override ++ public E next() { ++ final Link next = this.next; ++ if (next == null) { ++ throw new NoSuchElementException(); ++ } ++ this.next = next.next; ++ return next.element; ++ } ++ }; ++ } ++ ++ public boolean addLast(final E element) { ++ final Comparator comparator = this.comparator; ++ ++ Link curr = this.tail; ++ if (curr != null) { ++ int compare; ++ ++ while ((compare = comparator.compare(element, curr.element)) < 0) { ++ Link prev = curr; ++ curr = curr.prev; ++ if (curr != null) { ++ continue; ++ } ++ this.head = prev.prev = new Link<>(element, null, prev); ++ return true; ++ } ++ ++ if (compare != 0) { ++ // insert after curr ++ final Link next = curr.next; ++ final Link insert = new Link<>(element, curr, next); ++ curr.next = insert; ++ ++ if (next == null) { ++ this.tail = insert; ++ } else { ++ next.prev = insert; ++ } ++ return true; ++ } ++ ++ return false; ++ } else { ++ this.head = this.tail = new Link<>(element); ++ return true; ++ } ++ } ++ ++ public boolean addFirst(final E element) { ++ final Comparator comparator = this.comparator; ++ ++ Link curr = this.head; ++ if (curr != null) { ++ int compare; ++ ++ while ((compare = comparator.compare(element, curr.element)) > 0) { ++ Link prev = curr; ++ curr = curr.next; ++ if (curr != null) { ++ continue; ++ } ++ this.tail = prev.next = new Link<>(element, prev, null); ++ return true; ++ } ++ ++ if (compare != 0) { ++ // insert before curr ++ final Link prev = curr.prev; ++ final Link insert = new Link<>(element, prev, curr); ++ curr.prev = insert; ++ ++ if (prev == null) { ++ this.head = insert; ++ } else { ++ prev.next = insert; ++ } ++ return true; ++ } ++ ++ return false; ++ } else { ++ this.head = this.tail = new Link<>(element); ++ return true; ++ } ++ } ++ ++ protected static final class Link { ++ public E element; ++ public Link prev; ++ public Link next; ++ ++ public Link() {} ++ ++ public Link(final E element) { ++ this.element = element; ++ } ++ ++ public Link(final E element, final Link prev, final Link next) { ++ this.element = element; ++ this.prev = prev; ++ this.next = next; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 3db77d9eda98eacb099135643aff5e94751f4c7c..595abf528a7862478100770987906af1b13439fe 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -111,6 +111,7 @@ public class BlockPos extends Vec3i { + return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.getX() + x, this.getY() + y, this.getZ() + z); + } + ++ public final BlockPos add(Vec3i baseblockposition) { return this.offset(baseblockposition); } // Paper - OBFHELPER + public BlockPos offset(Vec3i pos) { + return this.offset(pos.getX(), pos.getY(), pos.getZ()); + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 77d3969200ac6f88f3af9add05def0b627ce6db3..d12d5459c847d3f0d655c85e31d81c27b7a2face 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -495,7 +495,9 @@ public class ChunkHolder { + ChunkHolder.this.isTickingReady = true; + + +- ++ // Paper start - rewrite ticklistserver ++ ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z); ++ // Paper end - rewrite ticklistserver + + } + }); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 3aeb8426b0461ec572c1499116be80f968bb4104..e2b1541042bceac965411e3176d08c61f217c07f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -21,6 +21,7 @@ import net.minecraft.Util; + import net.minecraft.core.BlockPos; + import net.minecraft.core.SectionPos; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.progress.ChunkProgressListener; + import net.minecraft.util.Mth; + import net.minecraft.util.profiling.ProfilerFiller; +@@ -217,6 +218,13 @@ public class ServerChunkCache extends ChunkSource { + } + // Paper end + ++ // Paper start - rewrite ticklistserver ++ public final boolean isTickingReadyMainThread(BlockPos pos) { ++ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(pos)); ++ return chunk != null && chunk.isTickingReady(); ++ } ++ // Paper end - rewrite ticklistserver ++ + public ServerChunkCache(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager structureManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, boolean flag, ChunkProgressListener worldloadlistener, Supplier supplier) { + this.level = worldserver; + this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(worldserver); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5d85895456b5d65954889cadf932027ea23b400b..9da0d98bc2ed7876a00a734690ed42f01b9a9a9b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -292,6 +292,15 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + // Paper end + ++ // Paper start - rewrite ticklistserver ++ void onChunkSetTicking(int chunkX, int chunkZ) { ++ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) { ++ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.blockTicks).onChunkSetTicking(chunkX, chunkZ); ++ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.liquidTicks).onChunkSetTicking(chunkX, chunkZ); ++ } ++ } ++ // Paper end - rewrite ticklistserver ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -299,12 +308,21 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + convertable = convertable_conversionsession; + uuid = WorldUUID.getUUID(convertable_conversionsession.levelPath.toFile()); + // CraftBukkit end +- this.blockTicks = new ServerTickList<>(this, (block) -> { +- return block == null || block.defaultBlockState().isAir(); +- }, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // Paper - Timings +- this.liquidTicks = new ServerTickList<>(this, (fluidtype) -> { +- return fluidtype == null || fluidtype == Fluids.EMPTY; +- }, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // Paper - Timings ++ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) { ++ this.blockTicks = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (block) -> { ++ return block == null || block.defaultBlockState().isAir(); ++ }, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // Paper - Timings ++ this.liquidTicks = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (fluidtype) -> { ++ return fluidtype == null || fluidtype == Fluids.EMPTY; ++ }, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // Paper - Timings ++ } else { ++ this.blockTicks = new ServerTickList<>(this, (block) -> { ++ return block == null || block.defaultBlockState().isAir(); ++ }, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // Paper - Timings ++ this.liquidTicks = new ServerTickList<>(this, (fluidtype) -> { ++ return fluidtype == null || fluidtype == Fluids.EMPTY; ++ }, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // Paper - Timings ++ } + this.navigations = Sets.newHashSet(); + this.blockEvents = new ObjectLinkedOpenHashSet(); + this.tickTime = flag1; +@@ -639,7 +657,9 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + if (this.tickTime) { + long i = this.levelData.getGameTime() + 1L; + +- this.worldDataServer.setGameTime(i); ++ this.worldDataServer.setGameTime(i); // Paper - diff on change, we want the below to be ran right after this ++ this.blockTicks.nextTick(); // Paper ++ this.liquidTicks.nextTick(); // Paper + this.worldDataServer.getScheduledEvents().tick(this.server, i); + if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { + this.setDayTime(this.levelData.getDayTime() + 1L); +diff --git a/src/main/java/net/minecraft/world/level/ChunkTickList.java b/src/main/java/net/minecraft/world/level/ChunkTickList.java +index 16757eb9c03c0dab51a7a1b569daff81cf9654f3..3008e0c42efe908e45dba1a1437928d4d4378f24 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkTickList.java ++++ b/src/main/java/net/minecraft/world/level/ChunkTickList.java +@@ -9,6 +9,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; + + public class ChunkTickList implements TickList { + +@@ -61,6 +62,8 @@ public class ChunkTickList implements TickList { + return nbttaglist; + } + ++ private static final int MAX_TICK_DELAY = Integer.getInteger("paper.ticklist-max-tick-delay", -1).intValue(); // Paper - clean up broken entries ++ + public static ChunkTickList create(ListTag ticks, Function function, Function function1) { + List> list = Lists.newArrayList(); + +@@ -71,7 +74,14 @@ public class ChunkTickList implements TickList { + if (t0 != null) { + BlockPos blockposition = new BlockPos(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")); + +- list.add(new ChunkTickList.ScheduledTick<>(t0, blockposition, nbttagcompound.getInt("t"), TickPriority.byValue(nbttagcompound.getInt("p")))); ++ // Paper start - clean up broken entries ++ int delay = nbttagcompound.getInt("t"); ++ if (MAX_TICK_DELAY > 0 && delay > MAX_TICK_DELAY) { ++ MinecraftServer.LOGGER.warn("Dropping tick for pos " + blockposition + ", tick delay " + delay); ++ continue; ++ } ++ list.add(new ChunkTickList.ScheduledTick<>(t0, blockposition, delay, TickPriority.byValue(nbttagcompound.getInt("p")))); ++ // Paper end - clean up broken entries + } + } + +diff --git a/src/main/java/net/minecraft/world/level/ServerTickList.java b/src/main/java/net/minecraft/world/level/ServerTickList.java +index 10ac1ba0a3d192486f22c2127d5bc30353f0edb6..65b3a16f40a295c2916be2a9fd019b452fb65e4f 100644 +--- a/src/main/java/net/minecraft/world/level/ServerTickList.java ++++ b/src/main/java/net/minecraft/world/level/ServerTickList.java +@@ -50,7 +50,16 @@ public class ServerTickList implements TickList { + private final co.aikar.timings.Timing timingTicking; // Paper + // Paper end + ++ // Paper start ++ public void nextTick() {} ++ // Paper end ++ ++ public void tick() { ++ // Paper start - allow overriding ++ this.tick(); ++ } + public void tick() { ++ // Paper end + int i = this.tickNextTickList.size(); + + if (false) { // CraftBukkit +@@ -118,28 +127,43 @@ public class ServerTickList implements TickList { + + @Override + public boolean willTickThisTick(BlockPos pos, T object) { +- return this.currentlyTicking.contains(new TickNextTickData<>(pos, object)); ++ // Paper start - allow overriding ++ return this.isPendingTickThisTick(pos, object); ++ } ++ public boolean isPendingTickThisTick(BlockPos blockposition, T t0) { ++ // Paper end ++ return this.currentlyTicking.contains(new TickNextTickData<>(blockposition, t0)); + } + + public List> fetchTicksInChunk(ChunkPos chunkcoordintpair, boolean updateState, boolean getStaleTicks) { ++ // Paper start - allow overriding ++ return this.getEntriesInChunk(chunkcoordintpair, updateState, getStaleTicks); ++ } ++ public List> getEntriesInChunk(ChunkPos chunkcoordintpair, boolean flag, boolean flag1) { ++ // Paper end + int i = (chunkcoordintpair.x << 4) - 2; + int j = i + 16 + 2; + int k = (chunkcoordintpair.z << 4) - 2; + int l = k + 16 + 2; + +- return this.fetchTicksInArea(new BoundingBox(i, 0, k, j, 256, l), updateState, getStaleTicks); ++ return this.fetchTicksInArea(new BoundingBox(i, 0, k, j, 256, l), flag, flag1); + } + + public List> fetchTicksInArea(BoundingBox bounds, boolean updateState, boolean getStaleTicks) { +- List> list = this.fetchTicksInArea((List) null, this.tickNextTickList, bounds, updateState); ++ // Paper start - allow overriding ++ return this.getEntriesInBoundingBox(bounds, updateState, getStaleTicks); ++ } ++ public List> getEntriesInBoundingBox(BoundingBox structureboundingbox, boolean flag, boolean flag1) { ++ // Paper end ++ List> list = this.fetchTicksInArea((List) null, this.tickNextTickList, structureboundingbox, flag); + +- if (updateState && list != null) { ++ if (flag && list != null) { + this.tickNextTickSet.removeAll(list); + } + +- list = this.fetchTicksInArea(list, this.currentlyTicking, bounds, updateState); +- if (!getStaleTicks) { +- list = this.fetchTicksInArea(list, this.alreadyTicked, bounds, updateState); ++ list = this.fetchTicksInArea(list, this.currentlyTicking, structureboundingbox, flag); ++ if (!flag1) { ++ list = this.fetchTicksInArea(list, this.alreadyTicked, structureboundingbox, flag); + } + + return list == null ? Collections.emptyList() : list; +@@ -170,14 +194,19 @@ public class ServerTickList implements TickList { + } + + public void copy(BoundingBox box, BlockPos offset) { +- List> list = this.fetchTicksInArea(box, false, false); ++ // Paper start - allow overriding ++ this.copy(box, offset); ++ } ++ public void copy(BoundingBox structureboundingbox, BlockPos blockposition) { ++ // Paper end ++ List> list = this.fetchTicksInArea(structureboundingbox, false, false); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + TickNextTickData nextticklistentry = (TickNextTickData) iterator.next(); + +- if (box.isInside((Vec3i) nextticklistentry.pos)) { +- BlockPos blockposition1 = nextticklistentry.pos.offset((Vec3i) offset); ++ if (structureboundingbox.isInside((Vec3i) nextticklistentry.pos)) { ++ BlockPos blockposition1 = nextticklistentry.pos.offset((Vec3i) blockposition); + T t0 = nextticklistentry.getType(); + + this.addTickData(new TickNextTickData<>(blockposition1, t0, nextticklistentry.triggerTick, nextticklistentry.priority)); +@@ -187,11 +216,17 @@ public class ServerTickList implements TickList { + } + + public ListTag save(ChunkPos chunkcoordintpair) { ++ // Paper start - allow overriding ++ return this.serialize(chunkcoordintpair); ++ } ++ public ListTag serialize(ChunkPos chunkcoordintpair) { ++ // Paper end + List> list = this.fetchTicksInChunk(chunkcoordintpair, false, true); + + return saveTickList(this.toId, list, this.level.getGameTime()); + } + ++ public static ListTag serialize(Function function, Iterable> iterable, long i) { return ServerTickList.saveTickList(function, iterable, i); } // Paper - OBFHELPER + private static ListTag saveTickList(Function identifierProvider, Iterable> scheduledTicks, long time) { + ListTag nbttaglist = new ListTag(); + Iterator iterator = scheduledTicks.iterator(); +@@ -214,13 +249,23 @@ public class ServerTickList implements TickList { + + @Override + public boolean hasScheduledTick(BlockPos pos, T object) { +- return this.tickNextTickSet.contains(new TickNextTickData<>(pos, object)); ++ // Paper start - allow overriding ++ return this.isScheduledForTick(pos, object); ++ } ++ public boolean isScheduledForTick(BlockPos blockposition, T t0) { ++ // Paper end ++ return this.tickNextTickSet.contains(new TickNextTickData<>(blockposition, t0)); + } + + @Override + public void scheduleTick(BlockPos pos, T object, int delay, TickPriority priority) { +- if (!this.ignore.test(object)) { +- this.addTickData(new TickNextTickData<>(pos, object, (long) delay + this.level.getGameTime(), priority)); ++ // Paper start - allow overriding ++ this.schedule(pos, object, delay, priority); ++ } ++ public void schedule(BlockPos blockposition, T t0, int i, TickPriority ticklistpriority) { ++ // Paper end ++ if (!this.ignore.test(t0)) { ++ this.addTickData(new TickNextTickData<>(blockposition, t0, (long) i + this.level.getGameTime(), ticklistpriority)); + } + + } +@@ -234,6 +279,11 @@ public class ServerTickList implements TickList { + } + + public int size() { ++ // Paper start - allow overriding ++ return this.getTotalScheduledEntries(); ++ } ++ public int getTotalScheduledEntries() { ++ // Paper end + return this.tickNextTickSet.size(); + } + } +diff --git a/src/main/java/net/minecraft/world/level/TickNextTickData.java b/src/main/java/net/minecraft/world/level/TickNextTickData.java +index 90833389022d7412bdda8868a356b84f62a00e03..61cdaf45368dcc40f3311e8b7f8637a6c93a2d76 100644 +--- a/src/main/java/net/minecraft/world/level/TickNextTickData.java ++++ b/src/main/java/net/minecraft/world/level/TickNextTickData.java +@@ -6,11 +6,13 @@ import net.minecraft.core.BlockPos; + public class TickNextTickData { + + private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading +- private final T type; +- public final BlockPos pos; +- public final long triggerTick; +- public final TickPriority priority; +- private final long c; ++ private final T type; public final T getData() { return this.type; } // Paper - OBFHELPER ++ public final BlockPos pos; public final BlockPos getPosition() { return this.pos; } // Paper - OBFHELPER ++ public final long triggerTick; public final long getTargetTick() { return this.triggerTick; } // Paper - OBFHELPER ++ public final TickPriority priority; public final TickPriority getPriority() { return this.priority; } // Paper - OBFHELPER ++ private final long c; public final long getId() { return this.c; } // Paper - OBFHELPER ++ private final int hash; // Paper ++ public int tickState; // Paper + + public TickNextTickData(BlockPos pos, T t) { + this(pos, t, 0L, TickPriority.NORMAL); +@@ -22,6 +24,7 @@ public class TickNextTickData { + this.type = t; + this.triggerTick = time; + this.priority = priority; ++ this.hash = this.computeHash(); // Paper + } + + public boolean equals(Object object) { +@@ -34,19 +37,31 @@ public class TickNextTickData { + } + } + ++ // Paper start - optimize hashcode ++ @Override + public int hashCode() { ++ return this.hash; ++ } ++ public final int computeHash() { ++ // Paper end - optimize hashcode + return this.pos.hashCode(); + } + +- public static Comparator createTimeComparator() { // Paper - decompile fix +- return Comparator.comparingLong((nextticklistentry) -> { +- return ((TickNextTickData) nextticklistentry).triggerTick; // Paper - decompile fix +- }).thenComparing((nextticklistentry) -> { +- return ((TickNextTickData) nextticklistentry).priority; // Paper - decompile fix +- }).thenComparingLong((nextticklistentry) -> { +- return ((TickNextTickData) nextticklistentry).c; // Paper - decompile fix +- }); ++ // Paper start - let's not use more functional code for no reason. ++ public static Comparator comparator() { return TickNextTickData.createTimeComparator(); } // Paper - OBFHELPER ++ public static Comparator createTimeComparator() { ++ return (Comparator)(Comparator)(TickNextTickData nextticklistentry, TickNextTickData nextticklistentry1) -> { ++ int i = Long.compare(nextticklistentry.getTargetTick(), nextticklistentry1.getTargetTick()); ++ ++ if (i != 0) { ++ return i; ++ } else { ++ i = nextticklistentry.getPriority().compareTo(nextticklistentry1.getPriority()); ++ return i != 0 ? i : Long.compare(nextticklistentry.getId(), nextticklistentry1.getId()); ++ } ++ }; + } ++ // Paper end - let's not use more functional code for no reason. + + public String toString() { + return this.type + ": " + this.pos + ", " + this.triggerTick + ", " + this.priority + ", " + this.c; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/BoundingBox.java b/src/main/java/net/minecraft/world/level/levelgen/structure/BoundingBox.java +index 76bea58d35d352ee6f3d4bd0d10af3b6d615ae2c..4288fa253668196e6c32a876f18ec496fb3abad6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/BoundingBox.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/BoundingBox.java +@@ -8,12 +8,12 @@ import net.minecraft.nbt.IntArrayTag; + + public class BoundingBox { + +- public int x0; +- public int y0; +- public int z0; +- public int x1; +- public int y1; +- public int z1; ++ public int x0; public final int getMinX() { return this.x0; } // Paper - OBFHELPER ++ public int y0; public final int getMinY() { return this.y0; } // Paper - OBFHELPER ++ public int z0; public final int getMinZ() { return this.z0; } // Paper - OBFHELPER ++ public int x1; public final int getMaxX() { return this.x1; } // Paper - OBFHELPER ++ public int y1; public final int getMaxY() { return this.y1; } // Paper - OBFHELPER ++ public int z1; public final int getMaxZ() { return this.z1; } // Paper - OBFHELPER + + public BoundingBox() {} + +@@ -92,6 +92,7 @@ public class BoundingBox { + this.y1 = 512; + } + ++ public final boolean intersects(BoundingBox boundingBox) { return this.intersects(boundingBox); } // Paper - OBFHELPER + public boolean intersects(BoundingBox other) { + return this.x1 >= other.x0 && this.x0 <= other.x1 && this.z1 >= other.z0 && this.z0 <= other.z1 && this.y1 >= other.y0 && this.y0 <= other.y1; + } +@@ -126,6 +127,7 @@ public class BoundingBox { + this.move(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + } + ++ public final boolean hasPoint(Vec3i baseblockposition) { return this.isInside(baseblockposition); } // Paper - OBFHELPER + public boolean isInside(Vec3i vec) { + return vec.getX() >= this.x0 && vec.getX() <= this.x1 && vec.getZ() >= this.z0 && vec.getZ() <= this.z1 && vec.getY() >= this.y0 && vec.getY() <= this.y1; + } diff --git a/Remapped-Spigot-Server-Patches/0417-Pillager-patrol-spawn-settings-and-per-player-option.patch b/Remapped-Spigot-Server-Patches/0417-Pillager-patrol-spawn-settings-and-per-player-option.patch new file mode 100644 index 000000000..6e0269c7f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0417-Pillager-patrol-spawn-settings-and-per-player-option.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 1 Feb 2020 16:50:39 +0100 +Subject: [PATCH] Pillager patrol spawn settings and per player options + +This adds config options for defining the spawn chance, spawn delay and +spawn start day as well as toggles for handling the spawn delay and +start day per player. (Based on the time played statistic) +When not per player it will use the Vanilla mechanic of one delay per +world and the world age for the start day. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 525d702d78a609af987ebd2c32169b873e5c05ed..6c8e9d498c9a30a1aa88494ba09c3cae012a8fa1 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -582,10 +582,21 @@ public class PaperWorldConfig { + } + + public boolean disablePillagerPatrols = false; ++ public double patrolSpawnChance = 0.2; ++ public boolean patrolPerPlayerDelay = false; ++ public int patrolDelay = 12000; ++ public boolean patrolPerPlayerStart = false; ++ public int patrolStartDay = 5; + private void pillagerSettings() { + disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); ++ patrolSpawnChance = getDouble("game-mechanics.pillager-patrols.spawn-chance", patrolSpawnChance); ++ patrolPerPlayerDelay = getBoolean("game-mechanics.pillager-patrols.spawn-delay.per-player", patrolPerPlayerDelay); ++ patrolDelay = getInt("game-mechanics.pillager-patrols.spawn-delay.ticks", patrolDelay); ++ patrolPerPlayerStart = getBoolean("game-mechanics.pillager-patrols.start.per-player", patrolPerPlayerStart); ++ patrolStartDay = getInt("game-mechanics.pillager-patrols.start.day", patrolStartDay); + } + ++ + public boolean entitiesTargetWithFollowRange = false; + private void entitiesTargetWithFollowRange() { + entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 7f4e81ee3339e90b8525541dccf6dea187853cf7..a469016c43251f16913a365c4131b2448eaa4c48 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -213,6 +213,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public boolean wonGame; + private int containerUpdateDelay; // Paper + public long loginTime; // Paper ++ public int patrolSpawnDelay; // Paper - per player patrol spawns + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/stats/StatType.java b/src/main/java/net/minecraft/stats/StatType.java +index ba48795a7b7cbf4622e64273ab488e26d7a862e2..b85987910cf80b1d1a04a7b772e19200f4ce4372 100644 +--- a/src/main/java/net/minecraft/stats/StatType.java ++++ b/src/main/java/net/minecraft/stats/StatType.java +@@ -28,6 +28,7 @@ public class StatType implements Iterable> { + return this.map.values().iterator(); + } + ++ public final Stat get(T t) { return this.get(t); }; // Paper - OBFHELPER + public Stat get(T key) { + return this.get(key, StatFormatter.DEFAULT); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index 48efe133d294bb1b17e8ac8b44eea8a29f15845f..dcbe74bdb1b6e07f7b8845182576ef544493d377 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -4,11 +4,12 @@ import java.util.Random; + import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.stats.Stats; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.MobSpawnType; + import net.minecraft.world.entity.SpawnGroupData; + import net.minecraft.world.entity.monster.PatrollingMonster; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.CustomSpawner; + import net.minecraft.world.level.GameRules; +@@ -20,13 +21,13 @@ import net.minecraft.world.level.block.state.BlockState; + + public class PatrolSpawner implements CustomSpawner { + +- private int nextTick; ++ private int nextTick;private int getSpawnDelay() { return nextTick; } private void setSpawnDelay(int spawnDelay) { this.nextTick = spawnDelay; } // Paper - OBFHELPER + + public PatrolSpawner() {} + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { +- if (world.paperConfig.disablePillagerPatrols) return 0; // Paper ++ if (world.paperConfig.disablePillagerPatrols || world.paperConfig.patrolSpawnChance == 0) return 0; // Paper + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { +@@ -34,23 +35,51 @@ public class PatrolSpawner implements CustomSpawner { + } else { + Random random = world.random; + +- --this.nextTick; +- if (this.nextTick > 0) { ++ // Paper start - Patrol settings ++ // Random player selection moved up for per player spawning and configuration ++ int j = world.players().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ ServerPlayer entityhuman = world.players().get(random.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (world.paperConfig.patrolPerPlayerDelay) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.nextTick += 12000 + random.nextInt(1200); +- long i = world.getDayTime() / 24000L; ++ setSpawnDelay(getSpawnDelay() - 1); ++ patrolSpawnDelay = getSpawnDelay(); ++ } ++ ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (world.paperConfig.patrolPerPlayerStart) { ++ days = entityhuman.getStats().getValue(Stats.CUSTOM.get(Stats.PLAY_ONE_MINUTE)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = world.getDayTime() / 24000L; ++ } ++ if (world.paperConfig.patrolPerPlayerDelay) { ++ entityhuman.patrolSpawnDelay += world.paperConfig.patrolDelay + random.nextInt(1200); ++ } else { ++ setSpawnDelay(getSpawnDelay() + world.paperConfig.patrolDelay + random.nextInt(1200)); ++ } + +- if (i >= 5L && world.isDay()) { +- if (random.nextInt(5) != 0) { ++ if (days >= world.paperConfig.patrolStartDay && world.isDay()) { ++ if (random.nextDouble() >= world.paperConfig.patrolSpawnChance) { ++ // Paper end + return 0; + } else { +- int j = world.players().size(); + + if (j < 1) { + return 0; + } else { +- Player entityhuman = (Player) world.players().get(random.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; diff --git a/Remapped-Spigot-Server-Patches/0418-Ensure-Entity-is-never-double-registered.patch b/Remapped-Spigot-Server-Patches/0418-Ensure-Entity-is-never-double-registered.patch new file mode 100644 index 000000000..0c956bdb7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0418-Ensure-Entity-is-never-double-registered.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 29 Mar 2020 18:26:14 -0400 +Subject: [PATCH] Ensure Entity is never double registered + +If something calls register twice, and the world is ticking, it could be +enqueued to add twice. + +Vs behavior of non ticking of just overwriting state. + +We will now simply log a warning when this happens instead of crashing the server. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 9da0d98bc2ed7876a00a734690ed42f01b9a9a9b..9898d5c8fab63c576831bd416ccf1854ed077b0d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -643,6 +643,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + Entity entity2; + + while ((entity2 = (Entity) this.toAddAfterTick.poll()) != null) { ++ if (!entity2.isQueuedForRegister) continue; // Paper - ignore cancelled registers + this.add(entity2); + } + +@@ -1400,6 +1401,19 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + public void onEntityRemoved(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ // Paper start - fix entity registration issues ++ if (entity instanceof EnderDragonPart) { ++ // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways ++ // Dragon parts are handled special in register. they don't receive a valid = true or register by UUID etc. ++ this.entitiesById.remove(entity.getId(), entity); ++ return; ++ } ++ if (!entity.valid) { ++ // Someone called remove before we ever got added, cancel the add. ++ entity.isQueuedForRegister = false; ++ return; ++ } ++ // Paper end + // Spigot start + if ( entity instanceof Player ) + { +@@ -1466,9 +1480,21 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + private void add(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ // Paper start - don't double enqueue entity registration ++ //noinspection ObjectEquality ++ if (this.entitiesById.get(entity.getId()) == entity) { ++ LOGGER.error(entity + " was already registered!"); ++ new Throwable().printStackTrace(); ++ return; ++ } ++ // Paper end + if (this.tickingEntities) { +- this.toAddAfterTick.add(entity); ++ if (!entity.isQueuedForRegister) { // Paper ++ this.toAddAfterTick.add(entity); ++ entity.isQueuedForRegister = true; // Paper ++ } + } else { ++ entity.isQueuedForRegister = false; // Paper + this.entitiesById.put(entity.getId(), entity); + if (entity instanceof EnderDragon) { + EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity).getSubEntities(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 43f77d01fceab107d3502d282205aa579d64cc4b..7e198b94f349d4c4d61502f5ad8c60686800d88f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -147,6 +147,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + // Paper start ++ public boolean isQueuedForRegister = false; + public static Random SHARED_RANDOM = new Random() { + private boolean locked = false; + @Override diff --git a/Remapped-Spigot-Server-Patches/0419-Fix-unregistering-entities-from-unloading-chunks.patch b/Remapped-Spigot-Server-Patches/0419-Fix-unregistering-entities-from-unloading-chunks.patch new file mode 100644 index 000000000..110790b17 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0419-Fix-unregistering-entities-from-unloading-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 Mar 2020 03:01:45 -0400 +Subject: [PATCH] Fix unregistering entities from unloading chunks + +CraftBukkit caused a regression here by making unloading chunks not +have a ticket added and returning unloaded future. + +This caused entities who were killed in same tick their chunk is unloading +to not be able to be removed from the chunk. + +This then results in dead entities lingering in the Chunk. + +Combine that with a buggy detail of the previous implementation of +the Dupe UUID patch, then this was the likely source of the "Ghost entities" + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 9898d5c8fab63c576831bd416ccf1854ed077b0d..c5dc41a3cf499038bd33451a189913cd3978b230 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1559,9 +1559,9 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + private void removeFromChunk(Entity entity) { +- ChunkAccess ichunkaccess = chunkSource.getChunkUnchecked(entity.xChunk, entity.zChunk); // CraftBukkit - SPIGOT-5228: getChunkAt won't find the entity's chunk if it has already been unloaded (i.e. if it switched to state INACCESSIBLE). ++ LevelChunk ichunkaccess = entity.getCurrentChunk(); // Paper - getChunkAt(x,z,full,false) is broken by CraftBukkit as it won't return an unloading chunk. Use our current chunk reference as this points to what chunk they need to be removed from anyways + +- if (ichunkaccess instanceof LevelChunk) { ++ if (ichunkaccess != null) { // Paper + ((LevelChunk) ichunkaccess).removeEntity(entity); + } + diff --git a/Remapped-Spigot-Server-Patches/0420-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/Remapped-Spigot-Server-Patches/0420-Remote-Connections-shouldn-t-hold-up-shutdown.patch new file mode 100644 index 000000000..a83d55935 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0420-Remote-Connections-shouldn-t-hold-up-shutdown.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 Mar 2020 03:50:42 -0400 +Subject: [PATCH] Remote Connections shouldn't hold up shutdown + +Bugs in the connection logic appears to leave stale connections even, preventing shutdown + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 4862a9519d4ba5f05b634a0335837bea9812edee..f8ddc0aa98874c7879a51e76d1a629cbdaf58812 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -397,11 +397,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (this.rconThread != null) { +- this.rconThread.stop(); ++ //this.remoteControlListener.b(); // Paper - don't wait for remote connections + } + + if (this.queryThreadGs4 != null) { +- this.queryThreadGs4.stop(); ++ //this.remoteStatusListener.b(); // Paper - don't wait for remote connections + } + + System.exit(0); // CraftBukkit diff --git a/Remapped-Spigot-Server-Patches/0421-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/Remapped-Spigot-Server-Patches/0421-Do-not-allow-bees-to-load-chunks-for-beehives.patch new file mode 100644 index 000000000..044507e1b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0421-Do-not-allow-bees-to-load-chunks-for-beehives.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 17 Mar 2020 14:18:50 -0500 +Subject: [PATCH] Do not allow bees to load chunks for beehives + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index 9b68809b91910d2bbb82cafe23d1de5dfff4221c..81291a1174538d6d4073c6fa886b10e99b45d887 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -358,6 +358,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + if (this.hivePos == null) { + return false; + } else { ++ if (!this.level.isLoadedAndInBounds(hivePos)) return false; // Paper + BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); + + return tileentity instanceof BeehiveBlockEntity && ((BeehiveBlockEntity) tileentity).isFireNearby(); +@@ -390,6 +391,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + } + + private boolean doesHiveHaveSpace(BlockPos pos) { ++ if (!this.level.isLoadedAndInBounds(pos)) return false; // Paper + BlockEntity tileentity = this.level.getBlockEntity(pos); + + return tileentity instanceof BeehiveBlockEntity ? !((BeehiveBlockEntity) tileentity).isFull() : false; +@@ -632,6 +634,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + @Override + public boolean canBeeUse() { + if (Bee.this.hasHive() && Bee.this.wantsToEnterHive() && Bee.this.hivePos.closerThan((Position) Bee.this.position(), 2.0D)) { ++ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return false; // Paper + BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); + + if (tileentity instanceof BeehiveBlockEntity) { +@@ -655,6 +658,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Override + public void start() { ++ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return; // Paper + BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); + + if (tileentity instanceof BeehiveBlockEntity) { diff --git a/Remapped-Spigot-Server-Patches/0422-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/Remapped-Spigot-Server-Patches/0422-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch new file mode 100644 index 000000000..720e9dcff --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0422-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 01:42:39 -0400 +Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server + +Suspected case would be around the technique used in .stopRiding +Stack will identify any causer of this and warn instead of crashing. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 6da406c8403797a1cd9276ac06577c3c080a8a22..e6eeca5834a164d87f5b0e564fe6237902edaa6a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1501,6 +1501,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + protected void addEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server ++ if (!entity.valid || entity.level != this.level || this.entityMap.containsKey(entity.getId())) { ++ new Throwable("[ERROR] Illegal PlayerChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : "")) ++ .printStackTrace(); ++ return; ++ } ++ // Paper end + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c5dc41a3cf499038bd33451a189913cd3978b230..5127bce423a83711cea94e387b3ae7866215ded5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1525,7 +1525,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + +- this.getChunkSource().addEntity(entity); ++ // this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof Drowned) { + this.navigations.add(((Drowned) entity).waterNavigation); +@@ -1536,6 +1536,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.navigations.add(((Mob) entity).getNavigation()); + } + entity.valid = true; // CraftBukkit ++ this.getChunkSource().addEntity(entity); // Paper - from above to be below valid=true + // Paper start - Set origin location when the entity is being added to the world + if (entity.origin == null) { + entity.origin = entity.getBukkitEntity().getLocation(); diff --git a/Remapped-Spigot-Server-Patches/0423-Optimize-Collision-to-not-load-chunks.patch b/Remapped-Spigot-Server-Patches/0423-Optimize-Collision-to-not-load-chunks.patch new file mode 100644 index 000000000..58edb5460 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0423-Optimize-Collision-to-not-load-chunks.patch @@ -0,0 +1,185 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 02:37:57 -0400 +Subject: [PATCH] Optimize Collision to not load chunks + +The collision code takes an AABB and generates a cuboid of checks rather +than a cylinder, so at high velocity this can generate a lot of chunk checks. + +Treat an unloaded chunk as a collision for entities, and also for players if +the "prevent moving into unloaded chunks" setting is enabled. + +If that serting is not enabled, collisions will be ignored for players, since +movement will load only the chunk the player enters anyways and avoids loading +massive amounts of surrounding chunks due to large AABB lookups. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index dfdde9722bc0d83916779014b7718eef2c01b3db..86c5549196a4e9011c5240e7918b466c299be4a3 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -59,12 +59,23 @@ import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.PlayerAdvancements; + import net.minecraft.server.ServerScoreboard; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.server.network.ServerGamePacketListenerImpl; ++import net.minecraft.server.network.ServerLoginPacketListenerImpl; ++import net.minecraft.sounds.SoundEvents; ++import net.minecraft.sounds.SoundSource; ++import net.minecraft.stats.ServerStatsCounter; ++import net.minecraft.stats.Stats; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.Tag; + import net.minecraft.util.Mth; + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; + import net.minecraft.world.level.Level; +@@ -90,15 +101,6 @@ import io.papermc.paper.adventure.PaperAdventure; // Paper + import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; + import net.minecraft.server.dedicated.DedicatedServer; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.level.ServerPlayerGameMode; +-import net.minecraft.server.network.ServerGamePacketListenerImpl; +-import net.minecraft.server.network.ServerLoginPacketListenerImpl; +-import net.minecraft.sounds.SoundEvents; +-import net.minecraft.sounds.SoundSource; +-import net.minecraft.stats.ServerStatsCounter; +-import net.minecraft.stats.Stats; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + +@@ -805,6 +807,7 @@ public abstract class PlayerList { + entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + // CraftBukkit end + ++ worldserver1.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, new ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper + while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < 256.0D) { + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7e198b94f349d4c4d61502f5ad8c60686800d88f..b8dcc91a191f25ca578e0858abf6c1b874fee15d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -168,6 +168,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + private CraftEntity bukkitEntity; + + ChunkMap.TrackedEntity tracker; // Paper ++ public boolean collisionLoadChunks = false; // Paper + public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { +diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java +index d9e69195ee0af4dfb90bf0e8f4cc65e63f7ecf5b..1b52f2a0ce9cb847d7d57b38f4b8b6bed8de2cd9 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java ++++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java +@@ -54,7 +54,9 @@ public interface CollisionGetter extends BlockGetter { + } + + default boolean noCollision(@Nullable Entity entity, AABB axisalignedbb, Predicate predicate) { ++ try { if (entity != null) entity.collisionLoadChunks = true; // Paper + return this.getCollisions(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); ++ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper + } + + Stream getEntityCollisions(@Nullable Entity entity, AABB axisalignedbb, Predicate predicate); +diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +index 7208c61da48ce5e735810b6268490584e1d5c260..feca9ff34936686c0665ae0dbc926869087df3a7 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java ++++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +@@ -7,6 +7,9 @@ import java.util.function.Consumer; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Cursor3D; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.WorldGenRegion; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.block.Blocks; +@@ -21,13 +24,13 @@ import net.minecraft.world.phys.shapes.VoxelShape; + public class CollisionSpliterator extends AbstractSpliterator { + + @Nullable +- private final Entity source; ++ private final Entity source; final Entity getEntity() { return this.source; } // Paper - OBFHELPER + private final AABB box; + private final CollisionContext context; + private final Cursor3D cursor; +- private final BlockPos.MutableBlockPos pos; ++ private final BlockPos.MutableBlockPos pos; final BlockPos.MutableBlockPos getMutablePos() { return this.pos; } // Paper - OBFHELPER + private final VoxelShape entityShape; +- private final CollisionGetter collisionGetter; ++ private final CollisionGetter collisionGetter; final CollisionGetter getCollisionAccess() { return this.collisionGetter; } // Paper - OBFHELPER + private boolean needsBorderCheck; + private final BiPredicate predicate; + +@@ -64,23 +67,37 @@ public class CollisionSpliterator extends AbstractSpliterator { + boolean collisionCheck(Consumer consumer) { + while (true) { + if (this.cursor.advance()) { +- int i = this.cursor.nextX(); +- int j = this.cursor.nextY(); +- int k = this.cursor.nextZ(); ++ int i = this.cursor.nextX(); final int x = i; ++ int j = this.cursor.nextY(); final int y = j; ++ int k = this.cursor.nextZ(); final int z = k; + int l = this.cursor.getNextType(); + + if (l == 3) { + continue; + } + +- BlockGetter iblockaccess = this.getChunk(i, k); +- +- if (iblockaccess == null) { ++ // Paper start - ensure we don't load chunks ++ Entity entity = this.getEntity(); ++ BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getMutablePos(); ++ boolean far = entity != null && MCUtil.distanceSq(entity.getX(), y, entity.getZ(), x, y, z) > 14; ++ blockposition_mutableblockposition.setValues(x, y, z); ++ ++ boolean isRegionLimited = this.getCollisionAccess() instanceof WorldGenRegion; ++ BlockState iblockdata = isRegionLimited ? Blocks.VOID_AIR.defaultBlockState() : ((!far && entity instanceof ServerPlayer) || (entity != null && entity.collisionLoadChunks) ++ ? this.getCollisionAccess().getBlockState(blockposition_mutableblockposition) ++ : this.getCollisionAccess().getTypeIfLoaded(blockposition_mutableblockposition) ++ ); ++ ++ if (iblockdata == null) { ++ if (!(entity instanceof ServerPlayer) || entity.level.paperConfig.preventMovingIntoUnloadedChunks) { ++ VoxelShape voxelshape3 = Shapes.of(far ? entity.getBoundingBox() : new AABB(new BlockPos(x, y, z))); ++ consumer.accept(voxelshape3); ++ return true; ++ } + continue; + } +- +- this.pos.set(i, j, k); +- BlockState iblockdata = iblockaccess.getBlockState(this.pos); ++ // Paper - moved up ++ // Paper end + + if (!this.predicate.test(iblockdata, this.pos) || l == 1 && !iblockdata.hasLargeCollisionShape() || l == 2 && !iblockdata.is(Blocks.MOVING_PISTON)) { + continue; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index fa2942d0b0424390daee2121f8959034c5352e0b..c14d5ebe16a693834ed218af8f737714065b2e17 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -249,7 +249,8 @@ public final class Shapes { + + if (k2 < 3) { + blockposition_mutableblockposition.set(enumaxiscycle1, i2, j2, l1); +- BlockState iblockdata = world.getBlockState(blockposition_mutableblockposition); ++ BlockState iblockdata = world.getTypeIfLoaded(blockposition_mutableblockposition); // Paper ++ if (iblockdata == null) return 0.0D; // Paper + + if ((k2 != 1 || iblockdata.hasLargeCollisionShape()) && (k2 != 2 || iblockdata.is(Blocks.MOVING_PISTON))) { + initial = iblockdata.getCollisionShape((BlockGetter) world, blockposition_mutableblockposition, context).collide(enumdirection_enumaxis2, box.move((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), initial); diff --git a/Remapped-Spigot-Server-Patches/0424-Don-t-tick-dead-players.patch b/Remapped-Spigot-Server-Patches/0424-Don-t-tick-dead-players.patch new file mode 100644 index 000000000..b748ace1a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0424-Don-t-tick-dead-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 17:16:48 -0400 +Subject: [PATCH] Don't tick dead players + +Causes sync chunk loads and who knows what all else. +This is safe because Spectators are skipped in unloaded chunks too in vanilla. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index a469016c43251f16913a365c4131b2448eaa4c48..286b75a27103a084a9f9d79a90716ebcad65d813 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -606,7 +606,7 @@ public class ServerPlayer extends Player implements ContainerListener { + + public void doTick() { + try { +- if (!this.isSpectator() || this.level.hasChunkAt(this.blockPosition())) { ++ if (valid && !this.isSpectator() || this.level.hasChunkAt(this.blockPosition())) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + diff --git a/Remapped-Spigot-Server-Patches/0425-Dead-Player-s-shouldn-t-be-able-to-move.patch b/Remapped-Spigot-Server-Patches/0425-Dead-Player-s-shouldn-t-be-able-to-move.patch new file mode 100644 index 000000000..3e2b8e535 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0425-Dead-Player-s-shouldn-t-be-able-to-move.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 19:31:16 -0400 +Subject: [PATCH] Dead Player's shouldn't be able to move + +This fixes a lot of game state issues where packets were delayed for processing +due to 1.15's new queue but processed while dead. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 0685920073a6a2b2c6a80018d0c9009b2ef860c4..32f1b180e82f41f3ce1b49ea7d67b7d55d2b9ca7 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1046,7 +1046,7 @@ public abstract class Player extends LivingEntity { + + @Override + protected boolean isImmobile() { +- return super.isImmobile() || this.isSleeping(); ++ return super.isImmobile() || this.isSleeping() || removed || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0426-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/Remapped-Spigot-Server-Patches/0426-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch new file mode 100644 index 000000000..a211d40a2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0426-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch @@ -0,0 +1,294 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 8 Apr 2020 03:06:30 -0400 +Subject: [PATCH] Optimize PlayerChunkMap memory use for visibleChunks + +No longer clones visible chunks which is causing massive memory +allocation issues, likely the source of Humongous Objects on large servers. + +Instead we just synchronize, clear and rebuild, reusing the same object buffers +as before with only 2 small objects created (FastIterator/MapEntry) + +This should result in siginificant memory use reduction and improved GC behavior. + +diff --git a/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6ff4d8132a95895680f5bc81f8f873e78f0bbdb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java +@@ -0,0 +1,39 @@ ++package com.destroystokyo.paper.util.map; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++ ++public class Long2ObjectLinkedOpenHashMapFastCopy extends Long2ObjectLinkedOpenHashMap { ++ ++ public void copyFrom(Long2ObjectLinkedOpenHashMapFastCopy map) { ++ if (key.length != map.key.length) { ++ key = null; ++ key = new long[map.key.length]; ++ } ++ if (value.length != map.value.length) { ++ value = null; ++ //noinspection unchecked ++ value = (V[]) new Object[map.value.length]; ++ } ++ if (link.length != map.link.length) { ++ link = null; ++ link = new long[map.link.length]; ++ } ++ System.arraycopy(map.key, 0, this.key, 0, map.key.length); ++ System.arraycopy(map.value, 0, this.value, 0, map.value.length); ++ System.arraycopy(map.link, 0, this.link, 0, map.link.length); ++ this.size = map.size; ++ this.mask = map.mask; ++ this.first = map.first; ++ this.last = map.last; ++ this.n = map.n; ++ this.maxFill = map.maxFill; ++ this.containsNullKey = map.containsNullKey; ++ } ++ ++ @Override ++ public Long2ObjectLinkedOpenHashMapFastCopy clone() { ++ Long2ObjectLinkedOpenHashMapFastCopy clone = (Long2ObjectLinkedOpenHashMapFastCopy) super.clone(); ++ clone.copyFrom(this); ++ return clone; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 99c3337eec552ba47d3b8b2d8feaaa80acf2a86f..9abef8550a89df5e15ac28de1a5549d064f29122 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -616,7 +616,7 @@ public final class MCUtil { + + ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); + ChunkMap chunkMap = world.getChunkSource().chunkMap; +- Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunkMap; ++ Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.getVisibleChunks(); + DistanceManager chunkMapDistance = chunkMap.distanceManager; + List allChunks = new ArrayList<>(visibleChunks.values()); + List players = world.players; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index e6eeca5834a164d87f5b0e564fe6237902edaa6a..99eee56d6e66b79dd48ecbd1eeebc08174003f4a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -104,8 +104,33 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + private static final Logger LOGGER = LogManager.getLogger(); + public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance(); +- public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); +- public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; ++ // Paper start - faster copying ++ public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - faster copying ++ public final Long2ObjectLinkedOpenHashMap visibleChunkMap = new ProtectedVisibleChunksMap(); // Paper - faster copying ++ ++ private class ProtectedVisibleChunksMap extends com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy { ++ @Override ++ public ChunkHolder put(long k, ChunkHolder playerChunk) { ++ throw new UnsupportedOperationException("Updating visible Chunks"); ++ } ++ ++ @Override ++ public ChunkHolder remove(long k) { ++ throw new UnsupportedOperationException("Removing visible Chunks"); ++ } ++ ++ @Override ++ public ChunkHolder get(long k) { ++ return ChunkMap.this.getVisibleChunkIfPresent(k); ++ } ++ ++ public ChunkHolder safeGet(long k) { ++ return super.get(k); ++ } ++ } ++ // Paper end ++ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy(); // Paper - this is used if the visible chunks is updated while iterating only ++ public transient com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy visibleChunksClone; // Paper - used for async access of visible chunks, clone and cache only when needed + private final Long2ObjectLinkedOpenHashMap pendingUnloads; + public final LongSet entitiesInLevel; // Paper - private -> public + public final ServerLevel level; +@@ -178,7 +203,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + public ChunkMap(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager definedstructuremanager, Executor workerExecutor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.getDimensionPath(worldserver.dimension()), "region"), dataFixer, flag); +- this.visibleChunkMap = this.updatingChunkMap.clone(); ++ //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning + this.pendingUnloads = new Long2ObjectLinkedOpenHashMap(); + this.entitiesInLevel = new LongOpenHashSet(); + this.toDrop = new LongOpenHashSet(); +@@ -270,9 +295,52 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return (ChunkHolder) this.updatingChunkMap.get(pos); + } + ++ // Paper start - remove cloning of visible chunks unless accessed as a collection async ++ private static final boolean DEBUG_ASYNC_VISIBLE_CHUNKS = Boolean.getBoolean("paper.debug-async-visible-chunks"); ++ private boolean isIterating = false; ++ private boolean hasPendingVisibleUpdate = false; ++ public void forEachVisibleChunk(java.util.function.Consumer consumer) { ++ org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk"); ++ boolean prev = isIterating; ++ isIterating = true; ++ try { ++ for (ChunkHolder value : this.visibleChunkMap.values()) { ++ consumer.accept(value); ++ } ++ } finally { ++ this.isIterating = prev; ++ if (!this.isIterating && this.hasPendingVisibleUpdate) { ++ ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom(this.pendingVisibleChunks); ++ this.pendingVisibleChunks.clear(); ++ this.hasPendingVisibleUpdate = false; ++ } ++ } ++ } ++ public Long2ObjectLinkedOpenHashMap getVisibleChunks() { ++ if (Thread.currentThread() == this.level.thread) { ++ return this.visibleChunkMap; ++ } else { ++ synchronized (this.visibleChunkMap) { ++ if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace(); ++ if (this.visibleChunksClone == null) { ++ this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : ((ProtectedVisibleChunksMap)this.visibleChunkMap).clone(); ++ } ++ return this.visibleChunksClone; ++ } ++ } ++ } ++ // Paper end ++ + @Nullable + public ChunkHolder getVisibleChunkIfPresent(long pos) { // Paper - protected -> public +- return (ChunkHolder) this.visibleChunkMap.get(pos); ++ // Paper start - mt safe get ++ if (Thread.currentThread() != this.level.thread) { ++ synchronized (this.visibleChunkMap) { ++ return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos)); ++ } ++ } ++ return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos)); ++ // Paper end + } + + protected IntSupplier getChunkQueueLevel(long pos) { +@@ -460,8 +528,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end + + protected void saveAllChunks(boolean flush) { ++ Long2ObjectLinkedOpenHashMap visibleChunks = this.getVisibleChunks(); // Paper remove clone of visible Chunks unless saving off main thread (watchdog kill) + if (flush) { +- List list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); ++ List list = (List) visibleChunks.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper - remove cloning of visible chunks + MutableBoolean mutableboolean = new MutableBoolean(); + + do { +@@ -489,7 +558,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // this.i(); // Paper - nuke IOWorker + ChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.storageFolder.getName()); + } else { +- this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> { ++ visibleChunks.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> { + ChunkAccess ichunkaccess = (ChunkAccess) playerchunk.getChunkToSave().getNow(null); // CraftBukkit - decompile error + + if (ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk) { +@@ -660,7 +729,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (!this.modified) { + return false; + } else { +- this.visibleChunkMap = this.updatingChunkMap.clone(); ++ // Paper start - stop cloning visibleChunks ++ synchronized (this.visibleChunkMap) { ++ if (isIterating) { ++ hasPendingVisibleUpdate = true; ++ this.pendingVisibleChunks.copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunkMap); ++ } else { ++ hasPendingVisibleUpdate = false; ++ this.pendingVisibleChunks.clear(); ++ ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunkMap); ++ this.visibleChunksClone = null; ++ } ++ } ++ // Paper end ++ + this.modified = false; + return true; + } +@@ -1139,12 +1221,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + protected Iterable getChunks() { +- return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); ++ return Iterables.unmodifiableIterable(this.getVisibleChunks().values()); // Paper + } + + void dumpChunks(Writer writer) throws IOException { + CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("entity_count").addColumn("block_entity_count").build(writer); +- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator(); ++ ObjectBidirectionalIterator objectbidirectionaliterator = this.getVisibleChunks().long2ObjectEntrySet().iterator(); // Paper + + while (objectbidirectionaliterator.hasNext()) { + Entry entry = (Entry) objectbidirectionaliterator.next(); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index e2b1541042bceac965411e3176d08c61f217c07f..f5de878020be9465739fba07fd7dea46b0a3ae34 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -782,7 +782,7 @@ public class ServerChunkCache extends ChunkSource { + }; + // Paper end + this.level.timings.chunkTicks.startTiming(); // Paper +- this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no... ++ this.chunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + + if (optional.isPresent()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index fb74bdcf4c2935b56e92717cc5a1504fbc853d0a..1a839242e359fa32f32d0e571c6e918ac39642e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -276,6 +276,7 @@ public class CraftWorld implements World { + return ret; + } + public int getTileEntityCount() { ++ return net.minecraft.server.MCUtil.ensureMain(() -> { + // We don't use the full world tile entity list, so we must iterate chunks + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + int size = 0; +@@ -287,11 +288,13 @@ public class CraftWorld implements World { + size += chunk.blockEntities.size(); + } + return size; ++ }); + } + public int getTickableTileEntityCount() { + return world.tickableBlockEntities.size(); + } + public int getChunkCount() { ++ return net.minecraft.server.MCUtil.ensureMain(() -> { + int ret = 0; + + for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) { +@@ -300,7 +303,7 @@ public class CraftWorld implements World { + } + } + +- return ret; ++ return ret; }); + } + public int getPlayerCount() { + return world.players.size(); +@@ -425,6 +428,14 @@ public class CraftWorld implements World { + + @Override + public Chunk[] getLoadedChunks() { ++ // Paper start ++ if (Thread.currentThread() != world.getLevel().thread) { ++ synchronized (world.getChunkSource().chunkMap.visibleChunkMap) { ++ Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; ++ return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new); ++ } ++ } ++ // Paper end + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new); + } diff --git a/Remapped-Spigot-Server-Patches/0427-Increase-Light-Queue-Size.patch b/Remapped-Spigot-Server-Patches/0427-Increase-Light-Queue-Size.patch new file mode 100644 index 000000000..f5ba65950 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0427-Increase-Light-Queue-Size.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 8 Apr 2020 21:24:05 -0400 +Subject: [PATCH] Increase Light Queue Size + +Wiz mentioned that large WorldEdit operations cause light to run on +main thread. The queue was small, set to 5.. this bumps it to 20 +but makes it configurable per-world. + +The main risk of increasing this higher is during shutdown, some +queued light updates may be lost because mojang did not flush the +light engine on shutdown... + +The queue size only puts a cap on max loss, doesn't solve that problem. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6c8e9d498c9a30a1aa88494ba09c3cae012a8fa1..cd248eb6be663e8be33f2c3c6b06b77b6d5753a4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -620,4 +620,9 @@ public class PaperWorldConfig { + private void zombieVillagerInfectionChance() { + zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); + } ++ ++ public int lightQueueSize = 20; ++ private void lightQueueSize() { ++ lightQueueSize = getInt("light-queue-size", lightQueueSize); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e33189dc8375a3034910087654607fb531061636..11c6e8ce10c53dcb639145fbda32c5426eb6b3d9 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -775,7 +775,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 9 Apr 2020 00:09:26 -0400 +Subject: [PATCH] Mid Tick Chunk Tasks - Speed up processing of chunk loads and + generation + +Credit to Spotted for the idea + +A lot of the new chunk system requires constant back and forth the main thread +to handle priority scheduling and ensuring conflicting tasks do not run at the +same time. + +The issue is, these queues are only checked at either: + +A) Sync Chunk Loads +B) End of Tick while sleeping + +This results in generating chunks sitting waiting for a full tick to +complete before it will even start the next unit of work to do. + +Additionally, this also delays loading of chunks until this same timing. + +We will now periodically poll the chunk task queues throughout the tick, +looking for work to do. +We do this in a fair method that considers all worlds, not just the one being +ticked, so that each world can get 1 task procesed each before the next pass. + +In a view distance of 15, chunk loading performance was visually faster on the client. + +Flying at high speed in spectator mode was able to keep up with chunk loading (as long as they are already generated) + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index be3a62f543a5fec4739c14821fe5a443c1fa3f5b..6bff5317939635b925bb41eb7a67d1fd95715078 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -17,6 +17,7 @@ import java.util.Map; + public final class MinecraftTimings { + + public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep"); ++ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); + public static final Timing playerListTimer = Timings.ofSafe("Player List"); + public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions"); + public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index da93d38fe63035e4ff198ada84a4431f52d97c01..ddbc8cb712c50038922eded75dd6ca85fe851078 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -410,4 +410,9 @@ public class PaperConfig { + log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag."); + } + } ++ ++ public static int midTickChunkTasks = 1000; ++ private static void midTickChunkTasks() { ++ midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 11c6e8ce10c53dcb639145fbda32c5426eb6b3d9..087f31ac0cc7816b1cbeffc45be6927b174dee62 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1055,6 +1055,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick + return !this.canOversleep(); + }); + isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); +@@ -1318,13 +1337,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Paper - safe iterator incase chunk loads, also no wrapping ++ final int[] chunksTicked = {0}; this.chunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + + if (optional.isPresent()) { +@@ -806,6 +809,7 @@ public class ServerChunkCache extends ChunkSource { + //this.world.timings.chunkTicks.startTiming(); // Spigot // Paper + this.level.tickChunk(chunk, k); + //this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper ++ if (chunksTicked[0]++ % 10 == 0) this.level.getServer().midTickLoadChunks(); // Paper + } + } + } +@@ -963,6 +967,41 @@ public class ServerChunkCache extends ChunkSource { + super.doRunTask(task); + } + ++ // Paper start ++ private long lastMidTickChunkTask = 0; ++ public boolean pollChunkLoadTasks() { ++ if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask()) { ++ try { ++ ServerChunkCache.this.runDistanceManagerUpdates(); ++ } finally { ++ // from below: process pending Chunk loadCallback() and unloadCallback() after each run task ++ chunkMap.callbackExecutor.run(); ++ } ++ return true; ++ } ++ return false; ++ } ++ public void midTickLoadChunks() { ++ MinecraftServer server = ServerChunkCache.this.level.getServer(); ++ // always try to load chunks, restrain generation/other updates only. don't count these towards tick count ++ //noinspection StatementWithEmptyBody ++ while (pollChunkLoadTasks()) {} ++ ++ if (System.nanoTime() - lastMidTickChunkTask < 200000) { ++ return; ++ } ++ ++ for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.haveTime();) { ++ if (this.pollTask()) { ++ server.midTickChunksTasksRan++; ++ lastMidTickChunkTask = System.nanoTime(); ++ } else { ++ break; ++ } ++ } ++ } ++ // Paper end ++ + @Override + protected boolean pollTask() { + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5127bce423a83711cea94e387b3ae7866215ded5..4e75cc5e52a5295e32ccadb371702a405bb518bb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -565,6 +565,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + timings.scheduledBlocks.stopTiming(); // Paper + ++ this.getServer().midTickLoadChunks(); // Paper + gameprofilerfiller.popPush("raid"); + this.timings.raids.startTiming(); // Paper - timings + this.raids.tick(); +@@ -573,6 +574,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + timings.doSounds.startTiming(); // Spigot + this.runBlockEvents(); + timings.doSounds.stopTiming(); // Spigot ++ this.getServer().midTickLoadChunks(); // Paper + this.handlingTick = false; + gameprofilerfiller.popPush("entities"); + boolean flag3 = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players +@@ -639,6 +641,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + timings.entityTick.stopTiming(); // Spigot + + this.tickingEntities = false; ++ this.getServer().midTickLoadChunks(); // Paper + + Entity entity2; + +@@ -648,6 +651,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + + timings.tickEntities.stopTiming(); // Spigot ++ this.getServer().midTickLoadChunks(); // Paper + this.tickBlockEntities(); + } + diff --git a/Remapped-Spigot-Server-Patches/0429-Don-t-move-existing-players-to-world-spawn.patch b/Remapped-Spigot-Server-Patches/0429-Don-t-move-existing-players-to-world-spawn.patch new file mode 100644 index 000000000..02f8b575b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0429-Don-t-move-existing-players-to-world-spawn.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 9 Apr 2020 21:20:33 -0400 +Subject: [PATCH] Don't move existing players to world spawn + +This can cause a nasty server lag the spawn chunks are not kept loaded +or they aren't finished loading yet, or if the world spawn radius is +larger than the keep loaded range. + +By skipping this, we avoid potential for a large spike on server start. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 286b75a27103a084a9f9d79a90716ebcad65d813..162b1a8c6ab57aafa4f6deefc842755a8e14208e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -251,7 +251,7 @@ public class ServerPlayer extends Player implements ContainerListener { + this.stats = server.getPlayerList().getStatisticManager(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); + this.maxUpStep = 1.0F; +- this.fudgeSpawnLocation(world); ++ //this.c(worldserver); // Paper - don't move to spawn on login, only first join + this.textFilter = server.createTextFilterForPlayer(this); + + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper +@@ -303,6 +303,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + // CraftBukkit end + ++ public final void moveToSpawn(ServerLevel worldserver) { fudgeSpawnLocation(worldserver); } // Paper - OBFHELPER + private void fudgeSpawnLocation(ServerLevel world) { + BlockPos blockposition = world.getSpawn(); + +@@ -480,7 +481,7 @@ public class ServerPlayer extends Player implements ContainerListener { + position = Vec3.atCenterOf(((ServerLevel) world).getSpawn()); + } + this.level = world; +- this.setPos(position.x(), position.y(), position.z()); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet + } + this.gameMode.setLevel((ServerLevel) world); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 86c5549196a4e9011c5240e7918b466c299be4a3..30666fca36b683158ff60302684b5093f5536e24 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -204,6 +204,8 @@ public abstract class PlayerList { + worldserver1 = worldserver; + } + ++ if (nbttagcompound == null) player.moveToSpawn(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... ++ + player.setLevel(worldserver1); + player.gameMode.setLevel((ServerLevel) player.level); + String s1 = "local"; diff --git a/Remapped-Spigot-Server-Patches/0430-Add-tick-times-API-and-mspt-command.patch b/Remapped-Spigot-Server-Patches/0430-Add-tick-times-API-and-mspt-command.patch new file mode 100644 index 000000000..0f2cf4005 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0430-Add-tick-times-API-and-mspt-command.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Apr 2020 22:23:14 -0500 +Subject: [PATCH] Add tick times API and /mspt command + + +diff --git a/src/main/java/com/destroystokyo/paper/MSPTCommand.java b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d0211d4f39f9d6af1d751ac66342b42cc6d7ba6d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +@@ -0,0 +1,64 @@ ++package com.destroystokyo.paper; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.ChatColor; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import java.text.DecimalFormat; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++ ++public class MSPTCommand extends Command { ++ private static final DecimalFormat DF = new DecimalFormat("########0.0"); ++ ++ public MSPTCommand(String name) { ++ super(name); ++ this.description = "View server tick times"; ++ this.usageMessage = "/mspt"; ++ this.setPermission("bukkit.command.mspt"); ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ List times = new ArrayList<>(); ++ times.addAll(eval(server.tickTimes5s.getTimes())); ++ times.addAll(eval(server.tickTimes10s.getTimes())); ++ times.addAll(eval(server.tickTimes60s.getTimes())); ++ ++ sender.sendMessage("§6Server tick times §e(§7avg§e/§7min§e/§7max§e)§6 from last 5s§7,§6 10s§7,§6 1m§e:"); ++ sender.sendMessage(String.format("§6◴ %s§7/%s§7/%s§e, %s§7/%s§7/%s§e, %s§7/%s§7/%s", times.toArray())); ++ return true; ++ } ++ ++ private static List eval(long[] times) { ++ long min = Integer.MAX_VALUE; ++ long max = 0L; ++ long total = 0L; ++ for (long value : times) { ++ if (value > 0L && value < min) min = value; ++ if (value > max) max = value; ++ total += value; ++ } ++ double avgD = ((double) total / (double) times.length) * 1.0E-6D; ++ double minD = ((double) min) * 1.0E-6D; ++ double maxD = ((double) max) * 1.0E-6D; ++ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); ++ } ++ ++ private static String getColor(double avg) { ++ return ChatColor.COLOR_CHAR + (avg >= 50 ? "c" : avg >= 40 ? "e" : "a") + DF.format(avg); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index ddbc8cb712c50038922eded75dd6ca85fe851078..78271b400c79578d043b20a5389a37b1bef9a70d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -69,6 +69,7 @@ public class PaperConfig { + + commands = new HashMap(); + commands.put("paper", new PaperCommand("paper")); ++ commands.put("mspt", new MSPTCommand("mspt")); + + version = getInt("config-version", 20); + set("config-version", 20); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 087f31ac0cc7816b1cbeffc45be6927b174dee62..99ee9de92264381a064066bc22bb66b4b2852a2e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -217,6 +217,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 10 Apr 2020 21:24:12 -0400 +Subject: [PATCH] Expose MinecraftServer#isRunning + +This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4fa49b6bb26456d485f7f9193af560cb379e36f0..3f35e93b42efd03ff1002f09962fe3da51fb4c3f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2378,5 +2378,10 @@ public final class CraftServer implements Server { + public int getCurrentTick() { + return net.minecraft.server.MinecraftServer.currentTick; + } ++ ++ @Override ++ public boolean isStopping() { ++ return net.minecraft.server.MinecraftServer.getServer().hasStopped(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0432-Add-Raw-Byte-ItemStack-Serialization.patch b/Remapped-Spigot-Server-Patches/0432-Add-Raw-Byte-ItemStack-Serialization.patch new file mode 100644 index 000000000..4f867cd88 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0432-Add-Raw-Byte-ItemStack-Serialization.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Thu, 30 Apr 2020 16:56:54 +0200 +Subject: [PATCH] Add Raw Byte ItemStack Serialization + +Serializes using NBT which is safer for server data migrations than bukkits format. + +diff --git a/src/main/java/net/minecraft/nbt/NbtIo.java b/src/main/java/net/minecraft/nbt/NbtIo.java +index b3838e709c1581c25da7738c9a03a827761845b1..05ace1d046e32a261e67bff5afc18c2d32e1a8aa 100644 +--- a/src/main/java/net/minecraft/nbt/NbtIo.java ++++ b/src/main/java/net/minecraft/nbt/NbtIo.java +@@ -51,6 +51,7 @@ public class NbtIo { + return nbttagcompound; + } + ++ public static CompoundTag readNBT(InputStream inputstream) throws IOException { return readCompressed(inputstream); } // Paper - OBFHELPER + public static CompoundTag readCompressed(InputStream stream) throws IOException { + DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(stream))); + Throwable throwable = null; +@@ -106,6 +107,7 @@ public class NbtIo { + + } + ++ public static void writeNBT(CompoundTag nbttagcompound, OutputStream outputstream) throws IOException { writeCompressed(nbttagcompound, outputstream); } // Paper - OBFHELPER + public static void writeCompressed(CompoundTag tag, OutputStream stream) throws IOException { + DataOutputStream dataoutputstream = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(stream))); + Throwable throwable = null; +diff --git a/src/main/java/net/minecraft/util/datafix/DataFixers.java b/src/main/java/net/minecraft/util/datafix/DataFixers.java +index 950a4b67f9091af551ec1036ebeb943e3b335e91..dc4e2fc26e1bc2c545f955d30c052bb86e3ef614 100644 +--- a/src/main/java/net/minecraft/util/datafix/DataFixers.java ++++ b/src/main/java/net/minecraft/util/datafix/DataFixers.java +@@ -78,6 +78,7 @@ public class DataFixers { + return datafixerbuilder.build(Util.bootstrapExecutor()); + } + ++ public static DataFixer getDataFixer() { return getDataFixer(); } // Paper - OBFHELPER + public static DataFixer getDataFixer() { + return DataFixers.DATA_FIXER; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index ac996d581925c8f92832009945c766962e5b51c5..458cdfbeac9d757c9721acd4557a548affa0ede1 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -196,6 +196,7 @@ public final class ItemStack { + this.updateEmptyCacheFlag(); + } + ++ public static ItemStack fromCompound(CompoundTag nbttagcompound) { return of(nbttagcompound); } // Paper - OBFHELPER + public static ItemStack of(CompoundTag tag) { + try { + return new ItemStack(tag); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 86b319337fc41a09dd45df466df60cadaed1343f..a5a5038a84434e69fda8f6b41d2f00b4989e25ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -378,6 +378,46 @@ public final class CraftMagicNumbers implements UnsafeValues { + public boolean isSupportedApiVersion(String apiVersion) { + return apiVersion != null && SUPPORTED_API.contains(apiVersion); + } ++ ++ @Override ++ public byte[] serializeItem(ItemStack item) { ++ Preconditions.checkNotNull(item, "null cannot be serialized"); ++ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); ++ ++ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); ++ CompoundTag compound = (item instanceof CraftItemStack ? ((CraftItemStack) item).getHandle() : CraftItemStack.asNMSCopy(item)).save(new CompoundTag()); ++ compound.putInt("DataVersion", getDataVersion()); ++ try { ++ net.minecraft.nbt.NbtIo.writeNBT( ++ compound, ++ outputStream ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return outputStream.toByteArray(); ++ } ++ ++ @Override ++ public ItemStack deserializeItem(byte[] data) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ try { ++ CompoundTag compound = net.minecraft.nbt.NbtIo.readNBT( ++ new java.io.ByteArrayInputStream(data) ++ ); ++ int dataVersion = compound.getInt("DataVersion"); ++ ++ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); ++ Dynamic converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.fromCompound((CompoundTag) converted.getValue())); ++ } catch (IOException ex) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ex); ++ throw new RuntimeException(); ++ } ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0433-Remove-streams-from-Mob-AI-System.patch b/Remapped-Spigot-Server-Patches/0433-Remove-streams-from-Mob-AI-System.patch new file mode 100644 index 000000000..019eef698 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0433-Remove-streams-from-Mob-AI-System.patch @@ -0,0 +1,253 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:53:29 -0700 +Subject: [PATCH] Remove streams from Mob AI System + +The streams hurt performance and allocate tons of garbage, so +replace them with the standard iterator. + +Also optimise the stream.anyMatch statement to move to a bitset +where we can replace the call with a single bitwise operation. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index 558dd72c47930f6993952467f83b5a54ead95d92..acc6306d659cd65a043d12cd42dcbaf55aaf5250 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -1,10 +1,12 @@ + package net.minecraft.world.entity.ai.goal; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import java.util.EnumSet; + + public abstract class Goal { + +- private final EnumSet flags = EnumSet.noneOf(Goal.Flag.class); ++ private final EnumSet flags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + + public Goal() {} + +@@ -28,16 +30,20 @@ public abstract class Goal { + public void tick() {} + + public void setFlags(EnumSet controls) { +- this.flags.clear(); +- this.flags.addAll(controls); ++ // Paper start - remove streams from pathfindergoalselector ++ this.goalTypes.clear(); ++ this.goalTypes.addAllUnchecked(controls); ++ // Paper end - remove streams from pathfindergoalselector + } + + public String toString() { + return this.getClass().getSimpleName(); + } + +- public EnumSet getFlags() { +- return this.flags; ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.goalTypes; ++ // Paper end - remove streams from pathfindergoalselector + } + + public static enum Flag { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 9bd2ee05a0de6678ad8933a8ffbe0ae66bd073b4..5da2d780c17522e07c733a5e23b17ec760c7b342 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -1,8 +1,10 @@ + package net.minecraft.world.entity.ai.goal; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import com.google.common.collect.Sets; + import java.util.EnumMap; + import java.util.EnumSet; ++import java.util.Iterator; // Paper - remove streams from pathfindergoalselector + import java.util.Map; + import java.util.Set; + import java.util.function.Supplier; +@@ -28,7 +30,8 @@ public class GoalSelector { + private final Map lockedFlags = new EnumMap(Goal.Flag.class); + private final Set availableGoals = Sets.newLinkedHashSet(); private Set getTasks() { return availableGoals; }// Paper - OBFHELPER + private final Supplier profiler; +- private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); ++ private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + private int newGoalRate = 3;private int getTickRate() { return newGoalRate; } // Paper - OBFHELPER + private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + +@@ -56,35 +59,38 @@ public class GoalSelector { + // Paper end + + public void removeGoal(Goal goal) { +- this.availableGoals.stream().filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.getGoal() == goal; +- }).filter(WrappedGoal::isRunning).forEach(WrappedGoal::stop); +- this.availableGoals.removeIf((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.getGoal() == goal; +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { ++ WrappedGoal goalWrapped = iterator.next(); ++ if (goalWrapped.getGoal() != goal) { ++ continue; ++ } ++ if (goalWrapped.isRunning()) { ++ goalWrapped.stop(); ++ } ++ iterator.remove(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + } + ++ private static final Goal.Flag[] PATHFINDER_GOAL_TYPES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector ++ + public void tick() { + ProfilerFiller gameprofilerfiller = (ProfilerFiller) this.profiler.get(); + + gameprofilerfiller.push("goalCleanup"); +- this.getRunningGoals().filter((pathfindergoalwrapped) -> { +- boolean flag; +- +- if (pathfindergoalwrapped.isRunning()) { +- Stream stream = pathfindergoalwrapped.getFlags().stream(); +- EnumSet enumset = this.disabledFlags; +- +- this.disabledFlags.getClass(); +- if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.canContinueToUse()) { +- flag = false; +- return flag; +- } ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { ++ WrappedGoal wrappedGoal = iterator.next(); ++ if (!wrappedGoal.isRunning()) { ++ continue; + } +- +- flag = true; +- return flag; +- }).forEach(Goal::stop); ++ if (!this.goalTypes.hasCommonElements(wrappedGoal.getGoalTypes()) && wrappedGoal.canContinueToUse()) { ++ continue; ++ } ++ wrappedGoal.stop(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + this.lockedFlags.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { + if (!pathfindergoalwrapped.isRunning()) { + this.lockedFlags.remove(pathfindergoal_type); +@@ -93,30 +99,58 @@ public class GoalSelector { + }); + gameprofilerfiller.pop(); + gameprofilerfiller.push("goalUpdate"); +- this.availableGoals.stream().filter((pathfindergoalwrapped) -> { +- return !pathfindergoalwrapped.isRunning(); +- }).filter((pathfindergoalwrapped) -> { +- Stream stream = pathfindergoalwrapped.getFlags().stream(); +- EnumSet enumset = this.disabledFlags; +- +- this.disabledFlags.getClass(); +- return stream.noneMatch(enumset::contains); +- }).filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.getFlags().stream().allMatch((pathfindergoal_type) -> { +- return ((WrappedGoal) this.lockedFlags.getOrDefault(pathfindergoal_type, GoalSelector.NO_GOAL)).canBeReplacedBy(pathfindergoalwrapped); +- }); +- }).filter(WrappedGoal::canUse).forEach((pathfindergoalwrapped) -> { +- pathfindergoalwrapped.getFlags().forEach((pathfindergoal_type) -> { +- WrappedGoal pathfindergoalwrapped1 = (WrappedGoal) this.lockedFlags.getOrDefault(pathfindergoal_type, GoalSelector.NO_GOAL); +- +- pathfindergoalwrapped1.stop(); +- this.lockedFlags.put(pathfindergoal_type, pathfindergoalwrapped); +- }); +- pathfindergoalwrapped.start(); +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ goal_update_loop: for (Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { ++ WrappedGoal wrappedGoal = iterator.next(); ++ if (wrappedGoal.isRunning()) { ++ continue; ++ } ++ ++ OptimizedSmallEnumSet wrappedGoalSet = wrappedGoal.getGoalTypes(); ++ ++ if (this.goalTypes.hasCommonElements(wrappedGoalSet)) { ++ continue; ++ } ++ ++ long iterator1 = wrappedGoalSet.getBackingSet(); ++ int wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL); ++ if (!wrapped.canBeReplacedBy(wrappedGoal)) { ++ continue goal_update_loop; ++ } ++ } ++ ++ if (!wrappedGoal.canUse()) { ++ continue; ++ } ++ ++ iterator1 = wrappedGoalSet.getBackingSet(); ++ wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL); ++ ++ wrapped.stop(); ++ this.lockedFlags.put(type, wrappedGoal); ++ } ++ ++ wrappedGoal.start(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + gameprofilerfiller.pop(); + gameprofilerfiller.push("goalTick"); +- this.getRunningGoals().forEach(WrappedGoal::tick); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { ++ WrappedGoal wrappedGoal = iterator.next(); ++ if (wrappedGoal.isRunning()) { ++ wrappedGoal.tick(); ++ } ++ } ++ // Paper end - remove streams from pathfindergoalselector + gameprofilerfiller.pop(); + } + +@@ -125,11 +159,11 @@ public class GoalSelector { + } + + public void disableControlFlag(Goal.Flag control) { +- this.disabledFlags.add(control); ++ this.goalTypes.addUnchecked(control); // Paper - remove streams from pathfindergoalselector + } + + public void enableControlFlag(Goal.Flag control) { +- this.disabledFlags.remove(control); ++ this.goalTypes.removeUnchecked(control); // Paper - remove streams from pathfindergoalselector + } + + public void setControlFlag(Goal.Flag control, boolean enabled) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +index 81b4618a7979ee8dd25e1749c084de9262318ef4..984146b2b6eb3e498433b1c4971397848166d9c9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +@@ -59,9 +59,10 @@ public class WrappedGoal extends Goal { + this.goal.setFlags(controls); + } + +- @Override +- public EnumSet getFlags() { +- return this.goal.getFlags(); ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.goal.getGoalTypes(); ++ // Paper end - remove streams from pathfindergoalselector + } + + public boolean isRunning() { return this.isRunning(); } // Paper - OBFHELPER diff --git a/Remapped-Spigot-Server-Patches/0434-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch b/Remapped-Spigot-Server-Patches/0434-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch new file mode 100644 index 000000000..9fb51d1e3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0434-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Apr 2020 21:23:42 -0400 +Subject: [PATCH] Delay unsafe actions until after entity ticking is done + +This will help prevent many cases of unregistering entities during entity ticking + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 4e75cc5e52a5295e32ccadb371702a405bb518bb..b9978d296b83e73d3395b8254c0e8ccd9b36d0fa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -172,6 +172,16 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public final List players = Lists.newArrayList(); // Paper - private -> public + public final ServerChunkCache chunkSource; // Paper - public + boolean tickingEntities; ++ // Paper start ++ List afterEntityTickingTasks = Lists.newArrayList(); ++ public void doIfNotEntityTicking(java.lang.Runnable run) { ++ if (tickingEntities) { ++ afterEntityTickingTasks.add(run); ++ } else { ++ run.run(); ++ } ++ } ++ // Paper end + private final MinecraftServer server; + public final PrimaryLevelData worldDataServer; // CraftBukkit - type + public boolean noSave; +@@ -641,6 +651,16 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + timings.entityTick.stopTiming(); // Spigot + + this.tickingEntities = false; ++ // Paper start ++ for (java.lang.Runnable run : this.afterEntityTickingTasks) { ++ try { ++ run.run(); ++ } catch (Exception e) { ++ LOGGER.error("Error in After Entity Ticking Task", e); ++ } ++ } ++ this.afterEntityTickingTasks.clear(); ++ // Paper end + this.getServer().midTickLoadChunks(); // Paper + + Entity entity2; diff --git a/Remapped-Spigot-Server-Patches/0435-Async-command-map-building.patch b/Remapped-Spigot-Server-Patches/0435-Async-command-map-building.patch new file mode 100644 index 000000000..87f32eb78 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0435-Async-command-map-building.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Callahan +Date: Wed, 8 Apr 2020 02:42:14 -0500 +Subject: [PATCH] Async command map building + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 44f2e8a3741afc0e3c3bca3b0864e37ecf83e5d4..8154d9327c5411bbfea3bfa4d99d57feab764664 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -29,6 +29,7 @@ import net.minecraft.network.chat.MutableComponent; + import net.minecraft.network.chat.TextComponent; + import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.game.ClientboundCommandsPacket; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.commands.AdvancementCommands; + import net.minecraft.server.commands.AttributeCommand; + import net.minecraft.server.commands.BanIpCommands; +@@ -328,25 +329,40 @@ public class Commands { + if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot + // CraftBukkit start + // Register Vanilla commands into builtRoot as before ++ // Paper start - Async command map building ++ java.util.concurrent.ForkJoinPool.commonPool().execute(() -> { ++ sendAsync(player); ++ }); ++ } ++ ++ private void sendAsync(ServerPlayer entityplayer) { ++ // Paper end - Async command map building + Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues + RootCommandNode vanillaRoot = new RootCommandNode(); + +- RootCommandNode vanilla = player.server.vanillaCommandDispatcher.getDispatcher().getRoot(); ++ RootCommandNode vanilla = entityplayer.server.vanillaCommandDispatcher.getDispatcher().getRoot(); + map.put(vanilla, vanillaRoot); +- this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map); ++ this.fillUsableCommands(vanilla, vanillaRoot, entityplayer.createCommandSourceStack(), (Map) map); + + // Now build the global commands in a second pass + RootCommandNode rootcommandnode = new RootCommandNode(); + + map.put(this.dispatcher.getRoot(), rootcommandnode); +- this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, player.createCommandSourceStack(), (Map) map); ++ this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, entityplayer.createCommandSourceStack(), (Map) map); + + Collection bukkit = new LinkedHashSet<>(); + for (CommandNode node : rootcommandnode.getChildren()) { + bukkit.add(node.getName()); + } ++ // Paper start - Async command map building ++ MinecraftServer.getServer().execute(() -> { ++ runSync(entityplayer, bukkit, rootcommandnode); ++ }); ++ } + +- PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); ++ private void runSync(ServerPlayer entityplayer, Collection bukkit, RootCommandNode rootcommandnode) { ++ // Paper end - Async command map building ++ PlayerCommandSendEvent event = new PlayerCommandSendEvent(entityplayer.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + + // Remove labels that were removed during the event +@@ -356,7 +372,7 @@ public class Commands { + } + } + // CraftBukkit end +- player.connection.send(new ClientboundCommandsPacket(rootcommandnode)); ++ entityplayer.connection.send(new ClientboundCommandsPacket(rootcommandnode)); + } + + private void fillUsableCommands(CommandNode tree, CommandNode result, CommandSourceStack source, Map, CommandNode> resultNodes) { diff --git a/Remapped-Spigot-Server-Patches/0436-Improved-Watchdog-Support.patch b/Remapped-Spigot-Server-Patches/0436-Improved-Watchdog-Support.patch new file mode 100644 index 000000000..928b62d52 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0436-Improved-Watchdog-Support.patch @@ -0,0 +1,608 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 12 Apr 2020 15:50:48 -0400 +Subject: [PATCH] Improved Watchdog Support + +Forced Watchdog Crash support and Improve Async Shutdown + +If the request to shut down the server is received while we are in +a watchdog hang, immediately treat it as a crash and begin the shutdown +process. Shutdown process is now improved to also shutdown cleanly when +not using restart scripts either. + +If a server is deadlocked, a server owner can send SIGUP (or any other signal +the JVM understands to shut down as it currently does) and the watchdog +will no longer need to wait until the full timeout, allowing you to trigger +a close process and try to shut the server down gracefully, saving player and +world data. + +Previously there was no way to trigger this outside of waiting for a full watchdog +timeout, which may be set to a really long time... + +Additionally, fix everything to do with shutting the server down asynchronously. + +Previously, nearly everything about the process was fragile and unsafe. Main might +not have actually been frozen, and might still be manipulating state. + +Or, some reuest might ask main to do something in the shutdown but main is dead. + +Or worse, other things might start closing down items such as the Console or Thread Pool +before we are fully shutdown. + +This change tries to resolve all of these issues by moving everything into the stop +method and guaranteeing only one thread is stopping the server. + +We then issue Thread Death to the main thread of another thread initiates the stop process. +We have to ensure Thread Death propagates correctly though to stop main completely. + +This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save. + +This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they +are properly accounted for and wont trip watchdog on init. + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index 0b9e689d57705965721b5c55bc45d36657f360e4..dee00aac05f1acf050f05d4db557a08dd0f301c8 100644 +--- a/src/main/java/com/destroystokyo/paper/Metrics.java ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -92,7 +92,12 @@ public class Metrics { + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { +- final Runnable submitTask = this::submitData; ++ final Runnable submitTask = () -> { ++ if (MinecraftServer.getServer().hasStopped()) { ++ return; ++ } ++ submitData(); ++ }; + + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. +diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java +index 4008fbe506e74f2c463dc7b12f5dd0f3b6fc342d..766ab2fe536a2acccaec28e922ccf8993b0790dc 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -257,6 +257,7 @@ public class CrashReport { + } + + public static CrashReport forThrowable(Throwable cause, String title) { ++ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper + while (cause instanceof CompletionException && cause.getCause() != null) { + cause = cause.getCause(); + } +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index c2f747226f10479c826849af898538610a2dd659..83f9f97586f8c0e9d228923e4fec6f121a6702e2 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -129,6 +129,7 @@ public class Util { + return Util.IO_POOL; + } + ++ public static void shutdownServerThreadPool() { shutdownExecutors(); } // Paper - OBFHELPER + public static void shutdownExecutors() { + shutdownExecutor(Util.BACKGROUND_EXECUTOR); + shutdownExecutor(Util.IO_POOL); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 99ee9de92264381a064066bc22bb66b4b2852a2e..e5ad635a480d32e7a10ee92c65cfc18a98beafad 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -269,7 +269,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new Thread(() -> { +@@ -851,6 +854,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ world.tickingEntities = false; ++ }); ++ } ++ // Paper end + // CraftBukkit end + MinecraftServer.LOGGER.info("Stopping server"); + MinecraftTimings.stopServer(); // Paper +@@ -930,7 +951,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; ++ } ++ // Paper end + return new TickTask(this.tickCount, runnable); + } + +@@ -1421,6 +1478,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + CompletableFuture completablefuture; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index b9978d296b83e73d3395b8254c0e8ccd9b36d0fa..bf4e50cd1d561456c033cda2d5c5487c5e3fe1eb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -171,7 +171,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + private final Queue toAddAfterTick = Queues.newArrayDeque(); + public final List players = Lists.newArrayList(); // Paper - private -> public + public final ServerChunkCache chunkSource; // Paper - public +- boolean tickingEntities; ++ public boolean tickingEntities; // Paper - expose for watchdog + // Paper start + List afterEntityTickingTasks = Lists.newArrayList(); + public void doIfNotEntityTicking(java.lang.Runnable run) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 30666fca36b683158ff60302684b5093f5536e24..984ac19dcab446531c816e365c7c149e2c49d567 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -503,7 +503,7 @@ public abstract class PlayerList { + cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +- entityplayer.doTick(); // SPIGOT-924 ++ if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) + // CraftBukkit end + + // Paper start - Remove from collideRule team if needed +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index a5ce61be7d6e85ac289730d9671e66a7190529f9..add18ba4833686ff51fbb280b0a5759f142b3f91 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -135,6 +135,7 @@ public abstract class BlockableEventLoop implements Processo + try { + task.run(); + } catch (Exception exception) { ++ if (exception.getCause() instanceof ThreadDeath) throw exception; // Paper + BlockableEventLoop.LOGGER.fatal("Error executing task on {}", this.name(), exception); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 632f32405053fbcff2fd26fa99f98c6add9f9dc7..5860e7866724abd35bde2a5710d9c92799e5de67 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -858,6 +858,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + gameprofilerfiller.pop(); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + String msg = "TileEntity threw exception at " + tileentity.getLevel().getWorld().getName() + ":" + tileentity.getBlockPos().getX() + "," + tileentity.getBlockPos().getY() + "," + tileentity.getBlockPos().getZ(); + System.err.println(msg); +@@ -932,6 +933,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + try { + tickConsumer.accept(entity); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + String msg = "Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ(); + System.err.println(msg); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 3f35e93b42efd03ff1002f09962fe3da51fb4c3f..43c37e660a8a7f9d326ad38e66f9aa7c53c7b87c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1839,7 +1839,7 @@ public final class CraftServer implements Server { + + @Override + public boolean isPrimaryThread() { +- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly ++ return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index c519ceca6f7788ca7c5d74ad1001dbc09f62681c..c288b89bf5a22269823ba1d18af217032d7c6a36 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -12,6 +12,8 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import joptsimple.OptionParser; + import joptsimple.OptionSet; ++import net.minecraft.util.ExceptionCollector; ++import net.minecraft.world.level.lighting.LayerLightEventListener; + import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper + + public class Main { +@@ -156,6 +158,36 @@ public class Main { + + OptionSet options = null; + ++ // Paper start - preload logger classes to avoid plugins mixing versions ++ tryPreloadClass("com.destroystokyo.paper.log.LogFullPolicy"); ++ tryPreloadClass("org.apache.logging.log4j.core.Core"); ++ tryPreloadClass("org.apache.logging.log4j.core.Appender"); ++ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector"); ++ tryPreloadClass("org.apache.logging.log4j.core.Filter"); ++ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.Logger"); ++ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener"); ++ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable"); ++ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.Message"); ++ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory"); ++ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement"); ++ // Paper end + try { + options = parser.parse(args); + } catch (joptsimple.OptionException ex) { +@@ -251,8 +283,64 @@ public class Main { + } catch (Throwable t) { + t.printStackTrace(); + } ++ // Paper start ++ // load some required classes to avoid errors during shutdown if jar is replaced ++ // also to guarantee our version loads over plugins ++ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow"); ++ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry"); ++ tryPreloadClass("com.google.common.collect.Iterables"); ++ for (int i = 1; i <= 15; i++) { ++ tryPreloadClass("com.google.common.collect.Iterables$" + i, false); ++ } ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean"); ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1"); ++ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking"); ++ tryPreloadClass("co.aikar.timings.TimingHistory"); ++ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12"); ++ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1"); ++ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil"); ++ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4"); ++ tryPreloadClass("org.slf4j.helpers.MessageFormatter"); ++ tryPreloadClass("org.slf4j.helpers.FormattingTuple"); ++ tryPreloadClass("org.slf4j.helpers.BasicMarker"); ++ tryPreloadClass("org.slf4j.helpers.Util"); ++ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent"); ++ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent"); ++ // Minecraft, seen during saving ++ tryPreloadClass(LayerLightEventListener.DummyLightLayerEventListener.class.getName()); ++ tryPreloadClass(LayerLightEventListener.class.getName()); ++ tryPreloadClass(ExceptionCollector.class.getName()); ++ // Paper end ++ } ++ } ++ ++ // Paper start ++ private static void tryPreloadClass(String className) { ++ tryPreloadClass(className, true); ++ } ++ private static void tryPreloadClass(String className, boolean printError) { ++ try { ++ Class.forName(className); ++ } catch (ClassNotFoundException e) { ++ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage()); + } + } ++ // Paper end + + private static List asList(String... params) { + return Arrays.asList(params); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index 449e99d1b673870ed6892f6ab2c715a2db35c35d..c7ed6e0f8a989cec97700df2b15198c9c481c549 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -12,12 +12,27 @@ public class ServerShutdownThread extends Thread { + @Override + public void run() { + try { ++ // Paper start - try to shutdown on main ++ server.safeShutdown(false, false); ++ for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) { ++ Thread.sleep(100); ++ } ++ if (server.hasStopped()) { ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ return; ++ } ++ // Looks stalled, close async + org.spigotmc.AsyncCatcher.enabled = false; // Spigot + org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper ++ server.forceTicks = true; + server.close(); ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ // Paper end + } finally { + try { +- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender ++ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop + } catch (Exception e) { + } + } +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 6dab105cd7cc4340c031c395c0346d4731355d79..6498dc4c6630bfef1a52edf74d8574e5e4876720 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -139,7 +139,7 @@ public class RestartCommand extends Command + // Paper end + + // Paper start - copied from above and modified to return if the hook registered +- private static boolean addShutdownHook(String restartScript) ++ public static boolean addShutdownHook(String restartScript) + { + String[] split = restartScript.split( " " ); + if ( split.length > 0 && new File( split[0] ).isFile() ) +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 33a66322d253c7562ae5acbdbc6cc87f7d72a9af..26c9adf7af4328ce2d8e08568019c5b438e28b05 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -13,6 +13,7 @@ import org.bukkit.Bukkit; + public class WatchdogThread extends Thread + { + ++ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper + private static WatchdogThread instance; + private long timeoutTime; + private boolean restart; +@@ -41,6 +42,7 @@ public class WatchdogThread extends Thread + { + if ( instance == null ) + { ++ if (timeoutTime <= 0) timeoutTime = 300; // Paper + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } else +@@ -71,12 +73,13 @@ public class WatchdogThread extends Thread + // Paper start + Logger log = Bukkit.getServer().getLogger(); + long currentTime = monotonicMillis(); +- if ( lastTick != 0 && timeoutTime > 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") ) ++ MinecraftServer server = MinecraftServer.getServer(); ++ if (lastTick != 0 && timeoutTime > 0 && hasStarted && (!server.isRunning() || (currentTime > lastTick + earlyWarningEvery && !DISABLE_WATCHDOG) )) + { +- boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000); + // Don't spam early warning dumps + if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; +- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... + lastEarlyWarning = currentTime; + if (isLongTimeout) { + // Paper end +@@ -118,7 +121,7 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper +- dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); ++ dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + // Paper start - Only print full dump on long timeouts +@@ -139,9 +142,25 @@ public class WatchdogThread extends Thread + + if ( isLongTimeout ) + { +- if ( restart && !MinecraftServer.getServer().hasStopped() ) ++ if ( !server.hasStopped() ) + { +- RestartCommand.restart(); ++ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us ++ AsyncCatcher.shuttingDown = true; ++ server.forceTicks = true; ++ if (restart) { ++ RestartCommand.addShutdownHook( SpigotConfig.restartScript ); ++ } ++ // try one last chance to safe shutdown on main incase it 'comes back' ++ server.abnormalExit = true; ++ server.safeShutdown(false, restart); ++ try { ++ Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ } ++ if (!server.hasStopped()) { ++ server.close(); ++ } + } + break; + } // Paper end +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 476f4a5cbe664ddd05474cb88553018bd334a5b8..8af159abd3d0cc94cf155fec5b384c42f69551bf 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,5 +1,5 @@ + +- ++ + + + diff --git a/Remapped-Spigot-Server-Patches/0437-Optimize-Pathfinding.patch b/Remapped-Spigot-Server-Patches/0437-Optimize-Pathfinding.patch new file mode 100644 index 000000000..9f2777798 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0437-Optimize-Pathfinding.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 02:02:07 -0600 +Subject: [PATCH] Optimize Pathfinding + +Prevents pathfinding from spamming failures for things such as +arrow attacks. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index a362506f38e8d30543b6cd6d215db561290dac76..c501e42b6fef4af065807182dc5b4c444e74e310 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -11,6 +11,7 @@ import net.minecraft.core.Position; + import net.minecraft.core.Vec3i; + import net.minecraft.network.protocol.game.DebugPackets; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.Mob; +@@ -32,7 +33,7 @@ public abstract class PathNavigation { + protected final Mob mob; public Entity getEntity() { return mob; } // Paper - OBFHELPER + protected final Level level; + @Nullable +- protected Path path; ++ protected Path path; protected final Path getCurrentPath() { return this.path; } // Paper - OBFHELPER + protected double speedModifier; + protected int tick; + protected int lastStuckCheck; +@@ -184,10 +185,30 @@ public abstract class PathNavigation { + return this.moveTo(this.createPath(x, y, z, 1), speed); + } + ++ // Paper start - optimise pathfinding ++ private int lastFailure = 0; ++ private int pathfindFailures = 0; ++ // Paper end ++ + public boolean moveTo(Entity entity, double speed) { ++ // Paper start - Pathfinding optimizations ++ if (this.pathfindFailures > 10 && this.getCurrentPath() == null && MinecraftServer.currentTick < this.lastFailure + 40) { ++ return false; ++ } ++ // Paper end + Path pathentity = this.createPath(entity, 1); + +- return pathentity != null && this.moveTo(pathentity, speed); ++ // Paper start - Pathfinding optimizations ++ if (pathentity != null && this.moveTo(pathentity, speed)) { ++ this.lastFailure = 0; ++ this.pathfindFailures = 0; ++ return true; ++ } else { ++ this.pathfindFailures++; ++ this.lastFailure = MinecraftServer.currentTick; ++ return false; ++ } ++ // Paper end + } + + public boolean setDestination(@Nullable Path pathentity, double speed) { return moveTo(pathentity, speed); } // Paper - OBFHELPER diff --git a/Remapped-Spigot-Server-Patches/0438-Reduce-Either-Optional-allocation.patch b/Remapped-Spigot-Server-Patches/0438-Reduce-Either-Optional-allocation.patch new file mode 100644 index 000000000..991b0f903 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0438-Reduce-Either-Optional-allocation.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 18:35:09 -0700 +Subject: [PATCH] Reduce Either Optional allocation + +In order to get chunk values, we shouldn't need to create +an optional each time. + +diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java +index fc8dbdf43833d76d8dc5f4e92575ca2965afa93a..ab71cdb3a8c2bec036ece630a0e0f088653e928f 100644 +--- a/src/main/java/com/mojang/datafixers/util/Either.java ++++ b/src/main/java/com/mojang/datafixers/util/Either.java +@@ -22,7 +22,7 @@ public abstract class Either implements App, L> { + } + + private static final class Left extends Either { +- private final L value; ++ private final L value; private Optional valueOptional; // Paper - reduce the optional allocation... + + public Left(final L value) { + this.value = value; +@@ -51,7 +51,7 @@ public abstract class Either implements App, L> { + + @Override + public Optional left() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation... + } + + @Override +@@ -83,7 +83,7 @@ public abstract class Either implements App, L> { + } + + private static final class Right extends Either { +- private final R value; ++ private final R value; private Optional valueOptional; // Paper - reduce the optional allocation... + + public Right(final R value) { + this.value = value; +@@ -117,7 +117,7 @@ public abstract class Either implements App, L> { + + @Override + public Optional right() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation... + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0439-Remove-streams-from-PairedQueue.patch b/Remapped-Spigot-Server-Patches/0439-Remove-streams-from-PairedQueue.patch new file mode 100644 index 000000000..935abedc0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0439-Remove-streams-from-PairedQueue.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 18:10:43 -0700 +Subject: [PATCH] Remove streams from PairedQueue + +We shouldn't be doing stream calls just to see if the queue is +empty. This creates loads of garbage thanks to how often it's called. + +diff --git a/src/main/java/net/minecraft/util/thread/StrictQueue.java b/src/main/java/net/minecraft/util/thread/StrictQueue.java +index cdc572b0261034248960fa13b8412e874fd20db5..07938519b699a31a280f3f419b34fb7cf6cf6883 100644 +--- a/src/main/java/net/minecraft/util/thread/StrictQueue.java ++++ b/src/main/java/net/minecraft/util/thread/StrictQueue.java +@@ -20,32 +20,30 @@ public interface StrictQueue { + + public static final class FixedPriorityQueue implements StrictQueue { + +- private final List> queueList; ++ private final List> queueList; private final List> getQueues() { return this.queueList; } // Paper - OBFHELPER + + public FixedPriorityQueue(int priorityCount) { +- this.queueList = (List) IntStream.range(0, priorityCount).mapToObj((j) -> { +- return Queues.newConcurrentLinkedQueue(); +- }).collect(Collectors.toList()); ++ // Paper start - remove streams ++ this.queueList = new java.util.ArrayList<>(priorityCount); // queues ++ for (int j = 0; j < priorityCount; ++j) { ++ this.getQueues().add(Queues.newConcurrentLinkedQueue()); ++ } ++ // Paper end - remove streams + } + + @Nullable + @Override + public Runnable pop() { +- Iterator iterator = this.queueList.iterator(); +- +- Runnable runnable; +- +- do { +- if (!iterator.hasNext()) { +- return null; ++ // Paper start - remove iterator creation ++ for (int i = 0, len = this.getQueues().size(); i < len; ++i) { ++ Queue queue = this.getQueues().get(i); ++ Runnable ret = queue.poll(); ++ if (ret != null) { ++ return ret; + } +- +- Queue queue = (Queue) iterator.next(); +- +- runnable = (Runnable) queue.poll(); +- } while (runnable == null); +- +- return runnable; ++ } ++ return null; ++ // Paper end - remove iterator creation + } + + public boolean push(StrictQueue.IntRunnable message) { +@@ -57,7 +55,16 @@ public interface StrictQueue { + + @Override + public boolean isEmpty() { +- return this.queueList.stream().allMatch(Collection::isEmpty); ++ // Paper start - remove streams ++ // why are we doing streams every time we might want to execute a task? ++ for (int i = 0, len = this.getQueues().size(); i < len; ++i) { ++ Queue queue = this.getQueues().get(i); ++ if (!queue.isEmpty()) { ++ return false; ++ } ++ } ++ return true; ++ // Paper end - remove streams + } + } + diff --git a/Remapped-Spigot-Server-Patches/0440-Reduce-memory-footprint-of-NBTTagCompound.patch b/Remapped-Spigot-Server-Patches/0440-Reduce-memory-footprint-of-NBTTagCompound.patch new file mode 100644 index 000000000..dcba632a3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0440-Reduce-memory-footprint-of-NBTTagCompound.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:39:25 -0700 +Subject: [PATCH] Reduce memory footprint of NBTTagCompound + +Fastutil maps are going to have a lower memory footprint - which +is important because we clone chunk data after reading it for safety. +So, reduce the impact of the clone on GC. + +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index a91bf94ed9f2f353a685194fc91c4b101ccc1232..c856ca720a9329a94bb07eaa3060c034f95718b3 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -26,6 +26,7 @@ import net.minecraft.ReportedException; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.MutableComponent; + import net.minecraft.network.chat.TextComponent; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; // Paper + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +@@ -47,7 +48,7 @@ public class CompoundTag implements Tag { + if (i > 512) { + throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); + } else { +- HashMap hashmap = Maps.newHashMap(); ++ Object2ObjectOpenHashMap hashmap = new Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - reduce memory footprint of NBTTagCompound + + byte b0; + +@@ -83,7 +84,7 @@ public class CompoundTag implements Tag { + } + + public CompoundTag() { +- this(Maps.newHashMap()); ++ this(new Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - reduce memory footprint of NBTTagCompound + } + + @Override +@@ -417,9 +418,17 @@ public class CompoundTag implements Tag { + + @Override + public CompoundTag copy() { +- Map map = Maps.newHashMap(Maps.transformValues(this.tags, Tag::copy)); ++ // Paper start - reduce memory footprint of NBTTagCompound ++ Object2ObjectOpenHashMap ret = new Object2ObjectOpenHashMap<>(this.tags.size(), 0.8f); + +- return new CompoundTag(map); ++ Iterator> iterator = (this.tags instanceof Object2ObjectOpenHashMap) ? ((Object2ObjectOpenHashMap)this.tags).object2ObjectEntrySet().fastIterator() : this.tags.entrySet().iterator(); ++ while (iterator.hasNext()) { ++ Map.Entry entry = iterator.next(); ++ ret.put(entry.getKey(), entry.getValue().copy()); ++ } ++ ++ return new CompoundTag(ret); ++ // Paper end - reduce memory footprint of NBTTagCompound + } + + public boolean equals(Object object) { diff --git a/Remapped-Spigot-Server-Patches/0441-Prevent-opening-inventories-when-frozen.patch b/Remapped-Spigot-Server-Patches/0441-Prevent-opening-inventories-when-frozen.patch new file mode 100644 index 000000000..7b2a22a56 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0441-Prevent-opening-inventories-when-frozen.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 13 Apr 2020 07:31:44 +0100 +Subject: [PATCH] Prevent opening inventories when frozen + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 162b1a8c6ab57aafa4f6deefc842755a8e14208e..efacfcaab444270b985f3a7fe0ef97e33c18a9de 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -557,7 +557,7 @@ public class ServerPlayer extends Player implements ContainerListener { + containerUpdateDelay = level.paperConfig.containerUpdateTickRate; + } + // Paper end +- if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) { ++ if (!this.level.isClientSide && this.containerMenu != this.inventoryMenu && (isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.containerMenu = this.inventoryMenu; + } +@@ -1404,7 +1404,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } else { + // CraftBukkit start + this.containerMenu = container; +- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); ++ if (!isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper + // CraftBukkit end + container.addSlotListener(this); + return OptionalInt.of(this.containerCounter); +@@ -2206,7 +2206,7 @@ public class ServerPlayer extends Player implements ContainerListener { + } + + @Override +- protected boolean isImmobile() { ++ public boolean isImmobile() { // Paper - protected > public + return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index a73c6ddd6bf66cc21ae5b25daacdece8cbfeeeac..ae6faa331fcbefd99ee1cd92c88926d767fc50ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -323,7 +323,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(container.getBukkitView().getTitle()); // Paper + + //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + getHandle().containerMenu = container; + getHandle().containerMenu.addSlotListener(player); + } +@@ -397,7 +397,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(inventory.getTitle()); // Paper + //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.containerMenu = container; + player.containerMenu.addSlotListener(player); + } diff --git a/Remapped-Spigot-Server-Patches/0442-Optimise-ArraySetSorted-removeIf.patch b/Remapped-Spigot-Server-Patches/0442-Optimise-ArraySetSorted-removeIf.patch new file mode 100644 index 000000000..dad03aa17 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0442-Optimise-ArraySetSorted-removeIf.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 18:23:28 -0700 +Subject: [PATCH] Optimise ArraySetSorted#removeIf + +Remove iterator allocation and ensure the call is always O(n) + +diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java +index 93813a508be1e1e600a8211f9822f2087328de70..8f03847eeb95ccdb69ad181b38b8724c9c72a76b 100644 +--- a/src/main/java/net/minecraft/util/SortedArraySet.java ++++ b/src/main/java/net/minecraft/util/SortedArraySet.java +@@ -10,8 +10,8 @@ import java.util.NoSuchElementException; + public class SortedArraySet extends AbstractSet { + + private final Comparator comparator; +- private T[] contents; +- private int size; ++ private T[] contents; private final T[] getBackingArray() { return this.contents; } // Paper - OBFHELPER ++ private int size; private final int getSize() { return this.size; } private final void setSize(int value) { this.size = value; } // Paper - OBFHELPER + + private SortedArraySet(int initialCapacity, Comparator comparator) { + this.comparator = comparator; +@@ -22,6 +22,42 @@ public class SortedArraySet extends AbstractSet { + } + } + ++ // Paper start - optimise removeIf ++ @Override ++ public boolean removeIf(java.util.function.Predicate filter) { ++ // prev. impl used an iterator, which could be n^2 and creates garbage ++ int i = 0, len = this.getSize(); ++ T[] backingArray = this.getBackingArray(); ++ ++ for (;;) { ++ if (i >= len) { ++ return false; ++ } ++ if (!filter.test(backingArray[i])) { ++ ++i; ++ continue; ++ } ++ break; ++ } ++ ++ // we only want to write back to backingArray if we really need to ++ ++ int lastIndex = i; // this is where new elements are shifted to ++ ++ for (; i < len; ++i) { ++ T curr = backingArray[i]; ++ if (!filter.test(curr)) { // if test throws we're screwed ++ backingArray[lastIndex++] = curr; ++ } ++ } ++ ++ // cleanup end ++ Arrays.fill(backingArray, lastIndex, len, null); ++ this.setSize(lastIndex); ++ return true; ++ } ++ // Paper end - optimise removeIf ++ + public static > SortedArraySet create(int initialCapacity) { + return new SortedArraySet<>(initialCapacity, (Comparator)Comparator.naturalOrder()); // Paper - decompile fix + } diff --git a/Remapped-Spigot-Server-Patches/0443-Don-t-run-entity-collision-code-if-not-needed.patch b/Remapped-Spigot-Server-Patches/0443-Don-t-run-entity-collision-code-if-not-needed.patch new file mode 100644 index 000000000..474688a9b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0443-Don-t-run-entity-collision-code-if-not-needed.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 17:56:07 -0700 +Subject: [PATCH] Don't run entity collision code if not needed + +Will not run if max entity craming is disabled and +the max collisions per entity is less than or equal to 0 + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 43fbe7d220f61802ae0cb0620ad078c5df7b69bc..46b962183e2e27ed93054ad9fb6d8ecbf70bc5f9 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2941,10 +2941,16 @@ public abstract class LivingEntity extends Entity { + protected void serverAiStep() {} + + protected void pushEntities() { ++ // Paper - start don't run getEntities if we're not going to use its result ++ int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ if (i <= 0 && level.paperConfig.maxCollisionsPerEntity <= 0) { ++ return; ++ } ++ // Paper - end don't run getEntities if we're not going to use its result + List list = this.level.getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this)); + + if (!list.isEmpty()) { +- int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ // Paper - move up + int j; + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { diff --git a/Remapped-Spigot-Server-Patches/0444-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch b/Remapped-Spigot-Server-Patches/0444-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch new file mode 100644 index 000000000..e6f625565 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0444-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 16 Apr 2020 16:13:59 -0700 +Subject: [PATCH] Optimize ChunkProviderServer's chunk level checking helper + methods + +These can be hot functions (i.e entity ticking and block ticking), +so inline where possible, and avoid the abstraction of the +Either class. + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 3744cce8611ac01b1b6c76cd3c4890795c1f06a2..531fe1259a1d60ff69321c3fefbf97f7141e6475 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -24,7 +24,6 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.ChunkProgressListener; +-import net.minecraft.util.Mth; + import net.minecraft.util.profiling.ProfilerFiller; + import net.minecraft.util.thread.BlockableEventLoop; + import net.minecraft.world.entity.Entity; +@@ -644,21 +643,29 @@ public class ServerChunkCache extends ChunkSource { + + public final boolean isInEntityTickingChunk(Entity entity) { return this.isEntityTickingChunk(entity); } // Paper - OBFHELPER + @Override public boolean isEntityTickingChunk(Entity entity) { +- long i = ChunkPos.asLong(Mth.floor(entity.getX()) >> 4, Mth.floor(entity.getZ()) >> 4); +- +- return this.checkChunkFuture(i, (Function>>) ChunkHolder::getEntityTickingChunkFuture); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // entity ticking ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(MCUtil.getCoordinateKey(entity)); ++ return playerChunk != null && playerChunk.isEntityTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + public final boolean isEntityTickingChunk(ChunkPos chunkcoordintpair) { return this.isEntityTickingChunk(chunkcoordintpair); } // Paper - OBFHELPER + @Override public boolean isEntityTickingChunk(ChunkPos pos) { +- return this.checkChunkFuture(pos.toLong(), (Function>>) ChunkHolder::getEntityTickingChunkFuture); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // is entity ticking ready ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(MCUtil.getCoordinateKey(pos)); ++ return playerChunk != null && playerChunk.isEntityTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + @Override + public boolean isTickingChunk(BlockPos pos) { +- long i = ChunkPos.asLong(pos.getX() >> 4, pos.getZ() >> 4); +- +- return this.checkChunkFuture(i, (Function>>) ChunkHolder::getTickingChunkFuture); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // is ticking ready ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(MCUtil.getCoordinateKey(pos)); ++ return playerChunk != null && playerChunk.isTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + private boolean checkChunkFuture(long pos, Function>> futureFunction) { diff --git a/Remapped-Spigot-Server-Patches/0445-Restrict-vanilla-teleport-command-to-valid-locations.patch b/Remapped-Spigot-Server-Patches/0445-Restrict-vanilla-teleport-command-to-valid-locations.patch new file mode 100644 index 000000000..bc23f7141 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0445-Restrict-vanilla-teleport-command-to-valid-locations.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 16 Apr 2020 20:07:29 -0500 +Subject: [PATCH] Restrict vanilla teleport command to valid locations + +Fixes GH-3165, GH-3575 + +diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +index 774180d9e450199309fee65b1d10e1592f84548a..d04ce9a323b079b4556b8c341fb11186e8d3e05d 100644 +--- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java ++++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +@@ -141,6 +141,12 @@ public class TeleportCommand { + + private static void performTeleport(CommandSourceStack source, Entity target, ServerLevel world, double x, double y, double z, Set movementFlags, float yaw, float pitch, @Nullable TeleportCommand.LookAt facingLocation) throws CommandSyntaxException { + BlockPos blockposition = new BlockPos(x, y, z); ++ // Paper start - Don't allow teleport command to invalid locations ++ if (x <= -30000000 || z <= -30000000 || x > 30000000 || z > 30000000 || y > 30000000 || y <= -30000000) { // Copy/pasta from BaseBlockPosition#isValidLocation ++ org.bukkit.Bukkit.getLogger().warning("Refused to teleport " + target.getScoreboardName() + " to " + x + ", " + y + ", " + z); ++ return; ++ } ++ // Paper end + + if (!Level.isInSpawnableBounds(blockposition)) { + throw TeleportCommand.INVALID_POSITION.create(); diff --git a/Remapped-Spigot-Server-Patches/0446-Implement-Player-Client-Options-API.patch b/Remapped-Spigot-Server-Patches/0446-Implement-Player-Client-Options-API.patch new file mode 100644 index 000000000..dcdbea549 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0446-Implement-Player-Client-Options-API.patch @@ -0,0 +1,188 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Mon, 20 Jan 2020 21:38:15 +0100 +Subject: [PATCH] Implement Player Client Options API + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +@@ -0,0 +1,74 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Objects; ++ ++import java.util.StringJoiner; ++ ++public class PaperSkinParts implements SkinParts { ++ ++ private final int raw; ++ ++ public PaperSkinParts(int raw) { ++ this.raw = raw; ++ } ++ ++ public boolean hasCapeEnabled() { ++ return (raw & 1) == 1; ++ } ++ ++ public boolean hasJacketEnabled() { ++ return (raw >> 1 & 1) == 1; ++ } ++ ++ public boolean hasLeftSleeveEnabled() { ++ return (raw >> 2 & 1) == 1; ++ } ++ ++ public boolean hasRightSleeveEnabled() { ++ return (raw >> 3 & 1) == 1; ++ } ++ ++ public boolean hasLeftPantsEnabled() { ++ return (raw >> 4 & 1) == 1; ++ } ++ ++ public boolean hasRightPantsEnabled() { ++ return (raw >> 5 & 1) == 1; ++ } ++ ++ public boolean hasHatsEnabled() { ++ return (raw >> 6 & 1) == 1; ++ } ++ ++ @Override ++ public int getRaw() { ++ return raw; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ PaperSkinParts that = (PaperSkinParts) o; ++ return raw == that.raw; ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hashCode(raw); ++ } ++ ++ @Override ++ public String toString() { ++ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") ++ .add("raw=" + raw) ++ .add("cape=" + hasCapeEnabled()) ++ .add("jacket=" + hasJacketEnabled()) ++ .add("leftSleeve=" + hasLeftSleeveEnabled()) ++ .add("rightSleeve=" + hasRightSleeveEnabled()) ++ .add("leftPants=" + hasLeftPantsEnabled()) ++ .add("rightPants=" + hasRightPantsEnabled()) ++ .add("hats=" + hasHatsEnabled()) ++ .toString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundClientInformationPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundClientInformationPacket.java +index 1f486cfd77b49568540398b1b3fa6127b17ba6aa..4b43740f9ff4feab4f1cd2f8e91d55be3cf8eb50 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundClientInformationPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundClientInformationPacket.java +@@ -41,14 +41,17 @@ public class ServerboundClientInformationPacket implements Packet POSES = ImmutableMap.builder().put(Pose.STANDING, Player.STANDING_DIMENSIONS).put(Pose.SLEEPING, Player.SLEEPING_DIMENSIONS).put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F)).put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F)).put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F)).put(Pose.CROUCHING, EntityDimensions.scalable(0.6F, 1.5F)).put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F)).build(); + private static final EntityDataAccessor DATA_PLAYER_ABSORPTION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.FLOAT); + private static final EntityDataAccessor DATA_SCORE_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.INT); +- protected static final EntityDataAccessor DATA_PLAYER_MODE_CUSTOMISATION = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE); ++ protected static final EntityDataAccessor DATA_PLAYER_MODE_CUSTOMISATION = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE); public static EntityDataAccessor getSkinPartsWatcher() { return DATA_PLAYER_MODE_CUSTOMISATION; } // Paper - OBFHELPER + protected static final EntityDataAccessor DATA_PLAYER_MAIN_HAND = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE); + protected static final EntityDataAccessor DATA_SHOULDER_LEFT = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG); + protected static final EntityDataAccessor DATA_SHOULDER_RIGHT = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 20de8e358789d05bb5ac15e4cdd7dda85b61b7f8..eb366396820c9b6731469df4198e0884a431a77c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1,5 +1,8 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.ClientOption.ChatVisibility; ++import com.destroystokyo.paper.PaperSkinParts; ++import com.destroystokyo.paper.ClientOption; + import com.destroystokyo.paper.Title; + import com.google.common.base.Preconditions; + import com.google.common.collect.ImmutableSet; +@@ -2250,6 +2253,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void setViewDistance(int viewDistance) { + throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO + } ++ ++ @Override ++ public T getClientOption(ClientOption type) { ++ if(ClientOption.SKIN_PARTS.equals(type)) { ++ return type.getType().cast(new PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.getSkinPartsWatcher()))); ++ } else if(ClientOption.CHAT_COLORS_ENABLED.equals(type)) { ++ return type.getType().cast(getHandle().hasChatColorsEnabled()); ++ } else if(ClientOption.CHAT_VISIBILITY.equals(type)) { ++ return type.getType().cast(getHandle().getChatVisibility() == null ? ChatVisibility.UNKNOWN : ChatVisibility.valueOf(getHandle().getChatVisibility().name())); ++ } else if(ClientOption.LOCALE.equals(type)) { ++ return type.getType().cast(getLocale()); ++ } else if(ClientOption.MAIN_HAND.equals(type)) { ++ return type.getType().cast(getMainHand()); ++ } else if(ClientOption.VIEW_DISTANCE.equals(type)) { ++ return type.getType().cast(getClientViewDistance()); ++ } ++ throw new RuntimeException("Unknown settings type"); ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0447-Fix-Chunk-Post-Processing-deadlock-risk.patch b/Remapped-Spigot-Server-Patches/0447-Fix-Chunk-Post-Processing-deadlock-risk.patch new file mode 100644 index 000000000..1d32694a1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0447-Fix-Chunk-Post-Processing-deadlock-risk.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 04:36:11 -0400 +Subject: [PATCH] Fix Chunk Post Processing deadlock risk + +See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6 + +as part of post processing a chunk, we can call ChunkConverter. + +ChunkConverter then kicks off major physics updates, and when blocks +that have connections across chunk boundries occur, a recursive risk +can occur where A updates a block that triggers a physics request. + +That physics request may trigger a chunk request, that then enqueues +a task into the Mailbox ChunkTaskQueueSorter. + +If anything requests that same chunk that is in the middle of conversion, +it's mailbox queue is going to be held up, so the subsequent chunk request +will be unable to proceed. + +We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into +the executor so that the mailbox ChunkQueue is now considered empty. + +This successfully fixed a reoccurring and highly reproduceable crash +for heightmaps. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 49728aab7512ea8486d277d34e80d3c6a4727aac..dbe60f5d24fb39be52c3cb8f933371b1626951df 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -181,6 +181,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }; + // CraftBukkit end + ++ final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper ++ + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + +@@ -1054,7 +1056,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return Either.left(chunk); + }); + }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); ++ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request. + }); + + completablefuture1.thenAcceptAsync((either) -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 531fe1259a1d60ff69321c3fefbf97f7141e6475..b45fe750c8ca838e1beebff4077e5819eec2836c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -1021,6 +1021,7 @@ public class ServerChunkCache extends ChunkSource { + return super.pollTask() || execChunkTask; // Paper + } + } finally { ++ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter + chunkMap.callbackExecutor.run(); + } + // CraftBukkit end diff --git a/Remapped-Spigot-Server-Patches/0448-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/Remapped-Spigot-Server-Patches/0448-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch new file mode 100644 index 000000000..1469edc10 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0448-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 15:59:41 -0400 +Subject: [PATCH] Don't crash if player is attempted to be removed from + untracked chunk. + +I suspect it deals with teleporting as it uses players current x/y/z + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 71a51cc99e26579e765f88340588e23956888929..90429d3f5c5b725098cfb001d54c70608f3df7bb 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -245,8 +245,8 @@ public abstract class DistanceManager { + ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); + if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(player); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/Remapped-Spigot-Server-Patches/0449-Broadcast-join-message-to-console.patch b/Remapped-Spigot-Server-Patches/0449-Broadcast-join-message-to-console.patch new file mode 100644 index 000000000..de365413d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0449-Broadcast-join-message-to-console.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AvrooVulcan +Date: Fri, 17 Apr 2020 00:15:23 +0100 +Subject: [PATCH] Broadcast join message to console + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 984ac19dcab446531c816e365c7c149e2c49d567..e043722436492140162940770c22be47690fb47f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -284,7 +284,9 @@ public abstract class PlayerList { + + if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure + joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure +- server.getPlayerList().broadcastAll(new ClientboundChatPacket(joinMessage, ChatType.SYSTEM, Util.NIL_UUID)); // Paper - Adventure ++ // Paper start - Removed sendAll for loop and broadcasted to console also ++ server.getPlayerList().sendMessage(joinMessage); // Paper - Adventure ++ // Paper end + } + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0450-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/Remapped-Spigot-Server-Patches/0450-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch new file mode 100644 index 000000000..e4ac02eb9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0450-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch @@ -0,0 +1,149 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 00:05:46 -0400 +Subject: [PATCH] Fix Longstanding Broken behavior of PlayerJoinEvent + +For years, plugin developers have had to delay many things they do +inside of the PlayerJoinEvent by 1 tick to make it actually work. + +This all boiled down to 1 reason why: The event fired before the +player was fully ready and joined to the world! + +Additionally, if that player logged out on a vehicle, the event +fired before the vehicle was even loaded, so that plugins had no +access to the vehicle during this event either. + +This change finally fixes this issue, fully preparing the player +into the world as a fully ready entity, vehicle included. + +There should be no plugins that break because of this change, but might +improve consistency with other plugins instead. + +For example, if 2 plugins listens to this event, and the first one +teleported the player in the event, then the 2nd plugin actually +would be getting a valid player! + +This was very non deterministic. This change will ensure every plugin +receives a deterministic result, and should no longer require 1 tick +delays anymore. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index dbe60f5d24fb39be52c3cb8f933371b1626951df..b3ca4300b280a24f3ed2acaffdd6ae2cdffd140d 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1584,7 +1584,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + +- protected void addEntity(Entity entity) { ++ public void addEntity(Entity entity) { // Paper - protected -> public + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level != this.level || this.entityMap.containsKey(entity.getId())) { +@@ -1593,6 +1593,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + .printStackTrace(); + return; + } ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets + // Paper end + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index aa979d17c264840ebd528708df3d6118e69fec68..75a095e0c2177dc1b46b080597ff8f12f1480acc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -237,6 +237,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public double maxHealthCache; + public boolean joining = true; + public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper + public Integer clientViewDistance; + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e043722436492140162940770c22be47690fb47f..454d60566743e02e7e55868c7bb45e30583dfa8f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -272,6 +272,12 @@ public abstract class PlayerList { + this.playersByUUID.put(player.getUUID(), player); + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below + ++ // Paper start - correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ mountSavedVehicle(player, worldserver1, nbttagcompound); ++ // Paper end + // CraftBukkit start + PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(player), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + cserver.getPluginManager().callEvent(playerJoinEvent); +@@ -307,6 +313,8 @@ public abstract class PlayerList { + player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[] { entityplayer1})); + } + player.sentListPacket = true; ++ player.supressTrackerForLogin = false; // Paper ++ ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now + // CraftBukkit end + + player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn +@@ -332,6 +340,11 @@ public abstract class PlayerList { + playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); + } + ++ // Paper start - move vehicle into method so it can be called above - short circuit around that code ++ onPlayerJoinFinish(player, worldserver1, s1); ++ } ++ private void mountSavedVehicle(ServerPlayer entityplayer, ServerLevel worldserver1, CompoundTag nbttagcompound) { ++ // Paper end + if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) { + CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); + // CraftBukkit start +@@ -354,20 +367,20 @@ public abstract class PlayerList { + Entity entity1; + + if (entity.getUUID().equals(uuid)) { +- player.startRiding(entity, true); ++ entityplayer.startRiding(entity, true); + } else { + iterator1 = entity.getIndirectPassengers().iterator(); + + while (iterator1.hasNext()) { + entity1 = (Entity) iterator1.next(); + if (entity1.getUUID().equals(uuid)) { +- player.startRiding(entity1, true); ++ entityplayer.startRiding(entity1, true); + break; + } + } + } + +- if (!player.isPassenger()) { ++ if (!entityplayer.isPassenger()) { + PlayerList.LOGGER.warn("Couldn't reattach entity to player"); + worldserver1.despawn(entity); + iterator1 = entity.getIndirectPassengers().iterator(); +@@ -380,16 +393,20 @@ public abstract class PlayerList { + } + } + +- player.initMenu(); ++ // Paper start ++ } ++ public void onPlayerJoinFinish(ServerPlayer entityplayer, ServerLevel worldserver1, String s1) { ++ // Paper end ++ entityplayer.initMenu(); + // Paper start - Add to collideRule team if needed + final Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); + final PlayerTeam collideRuleTeam = scoreboard.getTeam(collideRuleTeamName); +- if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { +- scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); ++ if (this.collideRuleTeamName != null && collideRuleTeam != null && entityplayer.getTeam() == null) { ++ scoreboard.addPlayerToTeam(entityplayer.getScoreboardName(), collideRuleTeam); + } + // Paper end + // CraftBukkit - Moved from above, added world +- PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.worldDataServer.getLevelName(), player.getX(), player.getY(), player.getZ()); ++ PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", entityplayer.getName().getString(), s1, entityplayer.getId(), worldserver1.worldDataServer.getLevelName(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()); + } + + public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) { diff --git a/Remapped-Spigot-Server-Patches/0451-Load-Chunks-for-Login-Asynchronously.patch b/Remapped-Spigot-Server-Patches/0451-Load-Chunks-for-Login-Asynchronously.patch new file mode 100644 index 000000000..f02494b2a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0451-Load-Chunks-for-Login-Asynchronously.patch @@ -0,0 +1,416 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 04:28:29 -0400 +Subject: [PATCH] Load Chunks for Login Asynchronously + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index b3ca4300b280a24f3ed2acaffdd6ae2cdffd140d..97a582614ad28f9fa864ae9be4860658e5979214 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -145,7 +145,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final ProcessorHandle> worldgenMailbox; + private final ProcessorHandle> mainThreadMailbox; + public final ChunkProgressListener progressListener; +- public final ChunkMap.ChunkDistanceManager distanceManager; ++ public final ChunkMap.ChunkDistanceManager distanceManager; public final DistanceManager getChunkDistanceManager() { return this.distanceManager; } // Paper - OBFHELPER + private final AtomicInteger tickingGenerated; + public final StructureManager structureManager; // Paper - private -> public + private final File storageFolder; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index b45fe750c8ca838e1beebff4077e5819eec2836c..79fb63c40dd0543a6f629e78f390f23f34992ba1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -629,7 +629,7 @@ public class ServerChunkCache extends ChunkSource { + return this.mainThreadProcessor.pollTask(); + } + +- private boolean runDistanceManagerUpdates() { ++ public boolean runDistanceManagerUpdates() { // Paper - private -> public + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = this.chunkMap.promoteChunkMap(); + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 75a095e0c2177dc1b46b080597ff8f12f1480acc..24c508ade61a6ad90b0ef73cdc995f531ef18263 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -32,6 +32,7 @@ import net.minecraft.core.Vec3i; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtOps; + import net.minecraft.nbt.Tag; ++import net.minecraft.network.Connection; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.HoverEvent; +@@ -172,6 +173,7 @@ public class ServerPlayer extends Player implements ContainerListener { + + private static final Logger LOGGER = LogManager.getLogger(); + public ServerGamePacketListenerImpl connection; ++ public Connection networkManager; // Paper + public final MinecraftServer server; + public final ServerPlayerGameMode gameMode; + public final Deque removeQueue = new ArrayDeque<>(); // Paper +@@ -238,6 +240,7 @@ public class ServerPlayer extends Player implements ContainerListener { + public boolean joining = true; + public boolean sentListPacket = false; + public boolean supressTrackerForLogin = false; // Paper ++ public boolean didPlayerJoinEvent = false; // Paper + public Integer clientViewDistance; + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index d09e4857b6c40410d134fa81b48e95919a7373bd..583587457790df826a8a3239a4bd1d0f1dcab1da 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -21,6 +21,7 @@ public class TicketType { + public static final TicketType FORCED = create("forced", Comparator.comparingLong(ChunkPos::toLong)); + public static final TicketType LIGHT = create("light", Comparator.comparingLong(ChunkPos::toLong)); + public static final TicketType PORTAL = create("portal", Vec3i::compareTo, 300); ++ public static final TicketType LOGIN = create("login", Long::compareTo, 100); // Paper + public static final TicketType POST_TELEPORT = create("post_teleport", Integer::compareTo, 5); + public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public static final TicketType PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 09a663cc53cdf8ae45352b280200c8170dbbcdfc..1bed6e69bf3cc1ab9b0c1259de4f643bf58371aa 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -220,6 +220,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private static final Logger LOGGER = LogManager.getLogger(); + public final Connection connection; + private final MinecraftServer server; ++ public Runnable playerJoinReady; // Paper + public ServerPlayer player; + private int tickCount; + private long keepAliveTime = Util.getMillis(); private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER +@@ -298,6 +299,15 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit end + + public void tick() { ++ // Paper start - login async ++ Runnable playerJoinReady = this.playerJoinReady; ++ if (playerJoinReady != null) { ++ this.playerJoinReady = null; ++ playerJoinReady.run(); ++ } ++ // Don't tick if not valid (dead), otherwise we load chunks below ++ if (this.player.valid) { ++ // Paper end + this.resetPosition(); + this.player.xo = this.player.getX(); + this.player.yo = this.player.getY(); +@@ -339,7 +349,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.lastVehicle = null; + this.clientVehicleIsFloating = false; + this.aboveGroundVehicleTickCount = 0; +- } ++ }} // Paper - end if (valid) + + this.server.getProfiler().push("keepAlive"); + // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 9631fa93b821c7f6bc6dc707c2c82cce2ae8291e..e229c7735ba88be3d8721440104958408a2a075e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -86,7 +86,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + } + // Paper end + } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { +- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); ++ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper + + if (entityplayer == null) { + this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; +@@ -186,7 +186,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + } + + this.connection.send(new ClientboundGameProfilePacket(this.gameProfile)); +- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); ++ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper + + if (entityplayer != null) { + this.state = ServerLoginPacketListenerImpl.State.DELAY_ACCEPT; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 454d60566743e02e7e55868c7bb45e30583dfa8f..ffc8c9ee8b1768dd809189858ee45658fb9bf1c5 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -36,6 +36,7 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; + import net.minecraft.network.protocol.game.ClientboundChatPacket; + import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; ++import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; + import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; + import net.minecraft.network.protocol.game.ClientboundGameEventPacket; + import net.minecraft.network.protocol.game.ClientboundLoginPacket; +@@ -59,6 +60,8 @@ import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.PlayerAdvancements; + import net.minecraft.server.ServerScoreboard; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.ServerPlayerGameMode; +@@ -124,11 +127,12 @@ public abstract class PlayerList { + private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); + private final MinecraftServer server; + public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety +- private final Map playersByUUID = Maps.newHashMap(); ++ private final Map playersByUUID = Maps.newHashMap();Map getUUIDMap() { return playersByUUID; } // Paper - OBFHELPER + private final UserBanList bans; + private final IpBanList ipBans; + private final ServerOpList ops; + private final UserWhiteList whitelist; ++ private final Map pendingPlayers = Maps.newHashMap(); // Paper + // CraftBukkit start + // private final Map o; + // private final Map p; +@@ -167,6 +171,11 @@ public abstract class PlayerList { + } + + public void placeNewPlayer(Connection connection, ServerPlayer player) { ++ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper ++ if (prev != null) { ++ disconnectPendingPlayer(prev); ++ } ++ player.networkManager = connection; // Paper + player.loginTime = System.currentTimeMillis(); // Paper + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); +@@ -180,7 +189,7 @@ public abstract class PlayerList { + if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { + CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); + s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; +- } ++ }String lastKnownName = s; // Paper + // CraftBukkit end + + if (nbttagcompound != null) { +@@ -255,34 +264,79 @@ public abstract class PlayerList { + player.getRecipeBook().sendInitialRecipeBook(player); + this.updateEntireScoreboard(worldserver1.getScoreboard(), player); + this.server.invalidateStatus(); ++ // Paper start - async load spawn in chunk ++ ServerLevel finalWorldserver = worldserver1; ++ int chunkX = loc.getBlockX() >> 4; ++ int chunkZ = loc.getBlockZ() >> 4; ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; ++ playerChunkMap.getChunkDistanceManager().addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.toLong()); ++ worldserver1.getChunkSource().runDistanceManagerUpdates(); ++ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { ++ ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()); ++ if (updatingChunk != null) { ++ return updatingChunk.getEntityTickingFuture(); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(chunk); ++ } ++ }).thenAccept(chunk -> { ++ playerconnection.playerJoinReady = () -> { ++ postChunkLoadJoin( ++ player, finalWorldserver, connection, playerconnection, ++ nbttagcompound, connection.getRemoteAddress().toString(), lastKnownName ++ ); ++ }; ++ }); ++ } ++ ++ public ServerPlayer getActivePlayer(UUID uuid) { ++ ServerPlayer player = this.getUUIDMap().get(uuid); ++ return player != null ? player : pendingPlayers.get(uuid); ++ } ++ ++ void disconnectPendingPlayer(ServerPlayer entityplayer) { ++ TranslatableComponent msg = new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]); ++ entityplayer.networkManager.send(new ClientboundDisconnectPacket(msg), (future) -> { ++ entityplayer.networkManager.disconnect(msg); ++ entityplayer.networkManager = null; ++ }); ++ } ++ ++ private void postChunkLoadJoin(ServerPlayer entityplayer, ServerLevel worldserver1, Connection networkmanager, ServerGamePacketListenerImpl playerconnection, CompoundTag nbttagcompound, String s1, String s) { ++ pendingPlayers.remove(entityplayer.getUUID(), entityplayer); ++ if (!networkmanager.isConnected()) { ++ return; ++ } ++ entityplayer.didPlayerJoinEvent = true; ++ // Paper end + TranslatableComponent chatmessage; + +- if (player.getGameProfile().getName().equalsIgnoreCase(s)) { +- chatmessage = new TranslatableComponent("multiplayer.player.joined", new Object[]{player.getDisplayName()}); ++ if (entityplayer.getGameProfile().getName().equalsIgnoreCase(s)) { ++ chatmessage = new TranslatableComponent("multiplayer.player.joined", new Object[]{entityplayer.getDisplayName()}); + } else { +- chatmessage = new TranslatableComponent("multiplayer.player.joined.renamed", new Object[]{player.getDisplayName(), s}); ++ chatmessage = new TranslatableComponent("multiplayer.player.joined.renamed", new Object[]{entityplayer.getDisplayName(), s}); + } + // CraftBukkit start + chatmessage.withStyle(ChatFormatting.YELLOW); + Component joinMessage = chatmessage; // Paper - Adventure + +- playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.yRot, player.xRot); +- this.players.add(player); +- this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot +- this.playersByUUID.put(player.getUUID(), player); ++ playerconnection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.yRot, entityplayer.xRot); ++ this.players.add(entityplayer); ++ this.playersByName.put(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT), entityplayer); // Spigot ++ this.playersByUUID.put(entityplayer.getUUID(), entityplayer); + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below + + // Paper start - correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks +- player.supressTrackerForLogin = true; +- worldserver1.addNewPlayer(player); +- this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); +- mountSavedVehicle(player, worldserver1, nbttagcompound); ++ entityplayer.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(entityplayer); ++ this.server.getCustomBossEvents().onPlayerConnect(entityplayer); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ mountSavedVehicle(entityplayer, worldserver1, nbttagcompound); + // Paper end + // CraftBukkit start +- PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(player), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure ++ PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(entityplayer), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + cserver.getPluginManager().callEvent(playerJoinEvent); + +- if (!player.connection.connection.isConnected()) { ++ if (!entityplayer.connection.connection.isConnected()) { + return; + } + +@@ -297,51 +351,51 @@ public abstract class PlayerList { + // CraftBukkit end + + // CraftBukkit start - sendAll above replaced with this loop +- ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, player); ++ ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, entityplayer); + + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + +- if (entityplayer1.getBukkitEntity().canSee(player.getBukkitEntity())) { ++ if (entityplayer1.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { + entityplayer1.connection.send(packet); + } + +- if (!player.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { ++ if (!entityplayer.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { + continue; + } + +- player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[] { entityplayer1})); ++ entityplayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[] { entityplayer1})); + } +- player.sentListPacket = true; +- player.supressTrackerForLogin = false; // Paper +- ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now ++ entityplayer.sentListPacket = true; ++ entityplayer.supressTrackerForLogin = false; // Paper ++ ((ServerLevel)entityplayer.level).getChunkSource().chunkMap.addEntity(entityplayer); // Paper - track entity now + // CraftBukkit end + +- player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn ++ entityplayer.connection.send(new ClientboundSetEntityDataPacket(entityplayer.getId(), entityplayer.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn + + // CraftBukkit start - Only add if the player wasn't moved in the event +- if (player.level == worldserver1 && !worldserver1.players().contains(player)) { +- worldserver1.addNewPlayer(player); +- this.server.getCustomBossEvents().onPlayerConnect(player); ++ if (entityplayer.level == worldserver1 && !worldserver1.players().contains(entityplayer)) { ++ worldserver1.addNewPlayer(entityplayer); ++ this.server.getCustomBossEvents().onPlayerConnect(entityplayer); + } + +- worldserver1 = player.getLevel(); // CraftBukkit - Update in case join event changed it ++ worldserver1 = entityplayer.getLevel(); // CraftBukkit - Update in case join event changed it + // CraftBukkit end +- this.sendLevelInfo(player, worldserver1); ++ this.sendLevelInfo(entityplayer, worldserver1); + if (!this.server.getResourcePack().isEmpty()) { +- player.sendTexturePack(this.server.getResourcePack(), this.server.getResourcePackHash()); ++ entityplayer.sendTexturePack(this.server.getResourcePack(), this.server.getResourcePackHash()); + } + +- Iterator iterator = player.getActiveEffects().iterator(); ++ Iterator iterator = entityplayer.getActiveEffects().iterator(); + + while (iterator.hasNext()) { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + +- playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); ++ playerconnection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobeffect)); + } + + // Paper start - move vehicle into method so it can be called above - short circuit around that code +- onPlayerJoinFinish(player, worldserver1, s1); ++ onPlayerJoinFinish(entityplayer, worldserver1, s1); + } + private void mountSavedVehicle(ServerPlayer entityplayer, ServerLevel worldserver1, CompoundTag nbttagcompound) { + // Paper end +@@ -492,6 +546,7 @@ public abstract class PlayerList { + + protected void save(ServerPlayer player) { + if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit ++ if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug) + this.playerIo.save(player); + ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + +@@ -519,7 +574,7 @@ public abstract class PlayerList { + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); +- cserver.getPluginManager().callEvent(playerQuitEvent); ++ if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + + if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) +@@ -572,6 +627,13 @@ public abstract class PlayerList { + // this.p.remove(uuid); + // CraftBukkit end + } ++ // Paper start ++ entityplayer1 = pendingPlayers.get(uuid); ++ if (entityplayer1 == entityplayer) { ++ pendingPlayers.remove(uuid); ++ } ++ entityplayer.networkManager = null; ++ // Paper end + + // CraftBukkit start + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer})); +@@ -589,7 +651,7 @@ public abstract class PlayerList { + cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); + // CraftBukkit end + +- return playerQuitEvent.quitMessage(); // Paper - Adventure ++ return entityplayer.didPlayerJoinEvent ? playerQuitEvent.quitMessage() : null; // CraftBukkit // Paper - Adventure // Paper - don't print quit if we never printed join + } + + // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer +@@ -608,6 +670,13 @@ public abstract class PlayerList { + list.add(entityplayer); + } + } ++ // Paper start - check pending players too ++ entityplayer = pendingPlayers.get(uuid); ++ if (entityplayer != null) { ++ this.pendingPlayers.remove(uuid); ++ disconnectPendingPlayer(entityplayer); ++ } ++ // Paper end + + Iterator iterator = list.iterator(); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index b8dcc91a191f25ca578e0858abf6c1b874fee15d..9f0371282f5829d26dc9618c3d466bccaa4cd3af 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1371,7 +1371,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + this.yo = y; + this.zo = d4; + this.setPos(d3, y, d4); +- level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit ++ if (valid) level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit // Paper + } + + public void moveTo(Vec3 vec3d) { diff --git a/Remapped-Spigot-Server-Patches/0452-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/Remapped-Spigot-Server-Patches/0452-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch new file mode 100644 index 000000000..eeaa3a49f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0452-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2277 <38501234+2277@users.noreply.github.com> +Date: Tue, 31 Mar 2020 10:33:55 +0100 +Subject: [PATCH] Move player to spawn point if spawn in unloaded world + +The code following this has better support for null worlds to move +them back to the world spawn. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9f0371282f5829d26dc9618c3d466bccaa4cd3af..34226102c50a4353c42e68917d41c44d251e602f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1808,9 +1808,11 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + bworld = server.getWorld(worldName); + } + +- if (bworld == null) { +- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld(); +- } ++ // Paper start - Move player to spawn point if spawn in unloaded world ++// if (bworld == null) { ++// bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(World.OVERWORLD).getWorld(); ++// } ++ // Paper end - Move player to spawn point if spawn in unloaded world + + setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); + } diff --git a/Remapped-Spigot-Server-Patches/0453-Add-PlayerAttackEntityCooldownResetEvent.patch b/Remapped-Spigot-Server-Patches/0453-Add-PlayerAttackEntityCooldownResetEvent.patch new file mode 100644 index 000000000..f1eab97e3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0453-Add-PlayerAttackEntityCooldownResetEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nossr50 +Date: Thu, 26 Mar 2020 19:44:50 -0700 +Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 46b962183e2e27ed93054ad9fb6d8ecbf70bc5f9..cec1e6105b8c2ac3d1482c00482d53d6be0d38d1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1928,7 +1928,16 @@ public abstract class LivingEntity extends Entity { + + EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption); + if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { +- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ // Paper start - PlayerAttackEntityCooldownResetEvent ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { ++ player.resetAttackStrengthTicker(); ++ } ++ } else { ++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); ++ } ++ // Paper end + } + if (event.isCancelled()) { + return false; diff --git a/Remapped-Spigot-Server-Patches/0454-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch b/Remapped-Spigot-Server-Patches/0454-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch new file mode 100644 index 000000000..025b5a9d8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0454-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 21 Apr 2020 03:51:53 -0400 +Subject: [PATCH] Allow multiple callbacks to schedule for Callback Executor + +ChunkMapDistance polls multiple entries for pendingChunkUpdates + +Each of these have the potential to move a chunk in and out of +"Loaded" state, which will result in multiple callbacks being +needed within a single tick of ChunkMapDistance + +Use an ArrayDeque to store this Queue + +We make sure to also implement a pattern that is recursion safe too. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 97a582614ad28f9fa864ae9be4860658e5979214..6c7af93cead523830d32b007cc69b313e59abef1 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -160,24 +160,32 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); + public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { + +- private Runnable queued; ++ // Paper start - replace impl with recursive safe multi entry queue ++ // it's possible to schedule multiple tasks currently, so it's vital we change this impl ++ // If we recurse into the executor again, we will append to another queue, ensuring task order consistency ++ private java.util.ArrayDeque queued = new java.util.ArrayDeque<>(); + + @Override + public void execute(Runnable runnable) { +- if (queued != null) { +- throw new IllegalStateException("Already queued"); ++ if (queued == null) { ++ queued = new java.util.ArrayDeque<>(); + } +- queued = runnable; ++ queued.add(runnable); + } + + @Override + public void run() { +- Runnable task = queued; ++ if (queued == null) { ++ return; ++ } ++ java.util.ArrayDeque queue = queued; + queued = null; +- if (task != null) { ++ Runnable task; ++ while ((task = queue.pollFirst()) != null) { + task.run(); + } + } ++ // Paper end + }; + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0455-Don-t-fire-BlockFade-on-worldgen-threads.patch b/Remapped-Spigot-Server-Patches/0455-Don-t-fire-BlockFade-on-worldgen-threads.patch new file mode 100644 index 000000000..8997a0897 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0455-Don-t-fire-BlockFade-on-worldgen-threads.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 23 Apr 2020 01:36:39 -0400 +Subject: [PATCH] Don't fire BlockFade on worldgen threads + +Caused a deadlock + +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 31b6c1333c7d0af28385e804e94348cef398748b..ac63c5bef5b35b158e57835d765bbdd15fc60664 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -93,6 +93,7 @@ public class FireBlock extends BaseFireBlock { + @Override + public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor world, BlockPos pos, BlockPos posFrom) { + // CraftBukkit start ++ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation + if (!this.canSurvive(state, world, pos)) { + // Suppress during worldgen + if (!(world instanceof Level)) { +@@ -108,7 +109,7 @@ public class FireBlock extends BaseFireBlock { + return blockState.getHandle(); + } + } +- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); ++ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - diff on change, see "don't fire events in world generation" + // CraftBukkit end + } + diff --git a/Remapped-Spigot-Server-Patches/0456-Add-phantom-creative-and-insomniac-controls.patch b/Remapped-Spigot-Server-Patches/0456-Add-phantom-creative-and-insomniac-controls.patch new file mode 100644 index 000000000..9a052de40 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0456-Add-phantom-creative-and-insomniac-controls.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 25 Apr 2020 15:13:41 -0500 +Subject: [PATCH] Add phantom creative and insomniac controls + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cd248eb6be663e8be33f2c3c6b06b77b6d5753a4..46ac6d91422423f1e03b86d3efa3241f2599000d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -625,4 +625,11 @@ public class PaperWorldConfig { + private void lightQueueSize() { + lightQueueSize = getInt("light-queue-size", lightQueueSize); + } ++ ++ public boolean phantomIgnoreCreative = true; ++ public boolean phantomOnlyAttackInsomniacs = true; ++ private void phantomSettings() { ++ phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative); ++ phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index e7facd849e3511c64b4ae44b34382f4a4985f2a4..8ce62148ebaeac9988e7c9d4b2f7ee57f58d883e 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -3,6 +3,9 @@ package net.minecraft.world.entity; + import com.google.common.base.Predicates; + import java.util.function.Predicate; + import javax.annotation.Nullable; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.stats.Stats; ++import net.minecraft.util.Mth; + import net.minecraft.world.Container; + import net.minecraft.world.Difficulty; + import net.minecraft.world.entity.player.Player; +@@ -31,10 +34,11 @@ public final class EntitySelector { + public static final Predicate NO_SPECTATORS = (entity) -> { + return !entity.isSpectator(); + }; ++ public static Predicate isInsomniac = (player) -> Mth.clamp(((ServerPlayer) player).getStats().getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper + + // Paper start + public static final Predicate affectsSpawning = (entity) -> { +- return !entity.isSpectator() && entity.isAlive() && (entity instanceof EntityPlayer) && ((EntityPlayer) entity).affectsSpawning; ++ return !entity.isSpectator() && entity.isAlive() && (entity instanceof ServerPlayer) && ((ServerPlayer) entity).affectsSpawning; + }; + // Paper end + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index e37137a2890330b92e05d6f76c46ffc99a527803..a40c23e824652cff59633b7c314e27ec9a515c07 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -262,6 +262,7 @@ public class Phantom extends FlyingMob implements Enemy { + Player entityhuman = (Player) iterator.next(); + + if (Phantom.this.canAttack((LivingEntity) entityhuman, TargetingConditions.DEFAULT)) { ++ if (!level.paperConfig.phantomOnlyAttackInsomniacs || EntitySelector.isInsomniac.test(entityhuman)) // Paper + Phantom.this.setGoalTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason + return true; + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index 42effcbd3ca7c38a4e8b1aa835543ad243112a33..79504dc3448402e73b09c4232b1fd0488872cf68 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -53,7 +53,7 @@ public class PhantomSpawner implements CustomSpawner { + while (iterator.hasNext()) { + Player entityhuman = (Player) iterator.next(); + +- if (!entityhuman.isSpectator()) { ++ if (!entityhuman.isSpectator() && (!world.paperConfig.phantomIgnoreCreative || !entityhuman.isCreative())) { // Paper + BlockPos blockposition = entityhuman.blockPosition(); + + if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { diff --git a/Remapped-Spigot-Server-Patches/0457-Fix-numerous-item-duplication-issues-and-teleport-is.patch b/Remapped-Spigot-Server-Patches/0457-Fix-numerous-item-duplication-issues-and-teleport-is.patch new file mode 100644 index 000000000..9e378d1d3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0457-Fix-numerous-item-duplication-issues-and-teleport-is.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 25 Apr 2020 06:46:35 -0400 +Subject: [PATCH] Fix numerous item duplication issues and teleport issues + +This notably fixes the newest "Donkey Dupe", but also fixes a lot +of dupe bugs in general around nether portals and entity world transfer + +We also fix item duplication generically by anytime we clone an item +to drop it on the ground, destroy the source item. + +This avoid an itemstack ever existing twice in the world state pre +clean up stage. + +So even if something NEW comes up, it would be impossible to drop the +same item twice because the source was destroyed. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 34226102c50a4353c42e68917d41c44d251e602f..2b48c4a2b512c42bed2c767db90a28898c74286a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1973,11 +1973,12 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later + return null; + } + // CraftBukkit end +- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - clone so we can destroy original ++ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); + // CraftBukkit start +@@ -2625,6 +2626,12 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + @Nullable + public Entity teleportTo(ServerLevel worldserver, BlockPos location) { + // CraftBukkit end ++ // Paper start - fix bad state entities causing dupes ++ if (!isAlive() || !valid) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + worldserver + ":" + location, new Throwable()); ++ return null; ++ } ++ // Paper end + if (this.level instanceof ServerLevel && !this.removed) { + this.level.getProfiler().push("changeDimension"); + // CraftBukkit start +@@ -2645,6 +2652,11 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + // CraftBukkit end + + this.level.getProfiler().popPush("reloading"); ++ // Paper start - Change lead drop timing to prevent dupe ++ if (this instanceof Mob) { ++ ((Mob) this).dropLeash(true, true); // Paper drop lead ++ } ++ // Paper end + Entity entity = this.getType().create((Level) worldserver); + + if (entity != null) { +@@ -2658,10 +2670,6 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + // CraftBukkit start - Forward the CraftEntity to the new entity + this.getBukkitEntity().setHandle(entity); + entity.bukkitEntity = this.getBukkitEntity(); +- +- if (this instanceof Mob) { +- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads. +- } + // CraftBukkit end + } + +@@ -2786,7 +2794,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean canChangeDimensions() { +- return true; ++ return isAlive() && valid; // Paper + } + + public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 33d51852ed6fe3f5adcdecf8f405a23689f4265a..5714aa450ac09788bcf1c2790d4f1581c9a7c28b 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -598,7 +598,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -606,7 +606,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7d9a3b65b2d6b294d3a11414289e64fac88665f0..87fe7f4f5ed70bf1b3dc1e2a392ba42a1f8f568b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -815,7 +815,8 @@ public class CraftEventFactory { + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); ++ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; diff --git a/Remapped-Spigot-Server-Patches/0458-Implement-Brigadier-Mojang-API.patch b/Remapped-Spigot-Server-Patches/0458-Implement-Brigadier-Mojang-API.patch new file mode 100644 index 000000000..5504aee90 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0458-Implement-Brigadier-Mojang-API.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 18:15:29 -0400 +Subject: [PATCH] Implement Brigadier Mojang API + +Adds AsyncPlayerSendCommandsEvent + - Allows modifying on a per command basis what command data they see. + +Adds CommandRegisteredEvent + - Allows manipulating the CommandNode to add more children/metadata for the client + +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index f74765f31bc7272724ee7fac0cc5a8c852550006..e1f4ffaa36bfffb7741c74b7a094e26a03a9a1e6 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -37,7 +37,7 @@ import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import com.mojang.brigadier.tree.CommandNode; // CraftBukkit + +-public class CommandSourceStack implements SharedSuggestionProvider { ++public class CommandSourceStack implements SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper + + public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(new TranslatableComponent("permissions.requires.player")); + public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(new TranslatableComponent("permissions.requires.entity")); +@@ -149,6 +149,25 @@ public class CommandSourceStack implements SharedSuggestionProvider { + return this.textName; + } + ++ // Paper start ++ @Override ++ public org.bukkit.entity.Entity getBukkitEntity() { ++ return getEntity() != null ? getEntity().getBukkitEntity() : null; ++ } ++ ++ @Override ++ public org.bukkit.World getBukkitWorld() { ++ return getLevel() != null ? getLevel().getWorld() : null; ++ } ++ ++ @Override ++ public org.bukkit.Location getBukkitLocation() { ++ Vec3 pos = getPosition(); ++ org.bukkit.World world = getBukkitWorld(); ++ return world != null && pos != null ? new org.bukkit.Location(world, pos.x, pos.y, pos.z) : null; ++ } ++ // Paper end ++ + @Override + public boolean hasPermission(int level) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 8154d9327c5411bbfea3bfa4d99d57feab764664..c63033e3eb50423a7c32acfc0e705623cc4bec68 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -355,6 +355,7 @@ public class Commands { + bukkit.add(node.getName()); + } + // Paper start - Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(entityplayer.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + MinecraftServer.getServer().execute(() -> { + runSync(entityplayer, bukkit, rootcommandnode); + }); +@@ -362,6 +363,7 @@ public class Commands { + + private void runSync(ServerPlayer entityplayer, Collection bukkit, RootCommandNode rootcommandnode) { + // Paper end - Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(entityplayer.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + PlayerCommandSendEvent event = new PlayerCommandSendEvent(entityplayer.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1bed6e69bf3cc1ab9b0c1259de4f643bf58371aa..5f12987b93f1578624626c4e911d1757dee3d45f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -768,8 +768,12 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer +- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); ++ // Paper start ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (!suggestEvent.callEvent()) return; ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), (com.mojang.brigadier.suggestion.Suggestions) suggestEvent.getSuggestions())); // CraftBukkit - decompile error // Paper ++ // Paper end + }); + }); + } +@@ -778,7 +782,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); + completions.forEach(builder::suggest); +- player.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (!suggestEvent.callEvent()) return; ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); + } + // Paper end - async tab completion + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index f9a245503c8982d1756503a6179f3715d919d910..b17002abdb43e74da4eb61e65e45c5e0e1dc0f95 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -17,7 +17,7 @@ import net.minecraft.commands.CommandSourceStack; + import org.bukkit.command.Command; + import org.bukkit.craftbukkit.CraftServer; + +-public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider { ++public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand { // Paper + + private final CraftServer server; + private final Command command; +@@ -28,10 +28,19 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command register(CommandDispatcher dispatcher, String label) { +- return dispatcher.register( +- LiteralArgumentBuilder.literal(label).requires(this).executes(this) +- .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this)) +- ); ++ // Paper start - Expose Brigadier to Paper-MojangAPI ++ com.mojang.brigadier.tree.RootCommandNode root = dispatcher.getRoot(); ++ LiteralCommandNode literal = LiteralArgumentBuilder.literal(label).requires(this).executes(this).build(); ++ com.mojang.brigadier.tree.ArgumentCommandNode defaultArgs = RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this).build(); ++ literal.addChild(defaultArgs); ++ com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent event = new com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent<>(label, this, this.command, root, literal, defaultArgs); ++ if (!event.callEvent()) { ++ return null; ++ } ++ literal = event.getLiteral(); ++ root.addChild(literal); ++ return literal; ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0459-Villager-Restocks-API.patch b/Remapped-Spigot-Server-Patches/0459-Villager-Restocks-API.patch new file mode 100644 index 000000000..49b6ff50f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0459-Villager-Restocks-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: zbk +Date: Sun, 26 Apr 2020 23:49:01 -0400 +Subject: [PATCH] Villager Restocks API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 4aa34320ef7d6c62ccb17734bfa61d406190b919..a83a7d37f3d769535161fda46fca6f71dcc4d515 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -112,7 +112,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + private long lastGossipDecayTime; + private int villagerXp; + private long lastRestockGameTime; +- private int numberOfRestocksToday; ++ private int numberOfRestocksToday; public int getRestocksToday(){ return this.numberOfRestocksToday; } public void setRestocksToday(int restocksToday){ this.numberOfRestocksToday = restocksToday; } // Paper OBFHELPER + private long lastRestockCheckDayTime; + private boolean assignProfessionWhenSpawned; + private static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.HOME, MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, MemoryModuleType.MEETING_POINT, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.VISIBLE_VILLAGER_BABIES, MemoryModuleType.NEAREST_PLAYERS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.WALK_TARGET, new MemoryModuleType[]{MemoryModuleType.LOOK_TARGET, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.BREED_TARGET, MemoryModuleType.PATH, MemoryModuleType.DOORS_TO_CLOSE, MemoryModuleType.NEAREST_BED, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.NEAREST_HOSTILE, MemoryModuleType.SECONDARY_JOB_SITE, MemoryModuleType.HIDING_PLACE, MemoryModuleType.HEARD_BELL_TIME, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.LAST_SLEPT, MemoryModuleType.LAST_WOKEN, MemoryModuleType.LAST_WORKED_AT_POI, MemoryModuleType.GOLEM_DETECTED_RECENTLY}); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 5a21e9447c3e0225b07144eec83c277dd101bfd5..d0b933cfd02b237bfe85011831dab6e8e966496e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -83,6 +83,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + getHandle().setVillagerXp(experience); + } + ++ // Paper start ++ @Override ++ public int getRestocksToday() { ++ return getHandle().getRestocksToday(); ++ } ++ ++ @Override ++ public void setRestocksToday(int restocksToday) { ++ getHandle().setRestocksToday(restocksToday); ++ } ++ // Paper end ++ + @Override + public boolean sleep(Location location) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/Remapped-Spigot-Server-Patches/0460-Validate-PickItem-Packet-and-kick-for-invalid.patch b/Remapped-Spigot-Server-Patches/0460-Validate-PickItem-Packet-and-kick-for-invalid.patch new file mode 100644 index 000000000..34909e54c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0460-Validate-PickItem-Packet-and-kick-for-invalid.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 May 2020 03:09:46 -0400 +Subject: [PATCH] Validate PickItem Packet and kick for invalid + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5f12987b93f1578624626c4e911d1757dee3d45f..3f416479e23c60ec5b4b779cce9ab62c74865ac8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -881,7 +881,14 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + @Override + public void handlePickItem(ServerboundPickItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +- this.player.inventory.pickSlot(packet.getSlot()); ++ // Paper start - validate pick item position ++ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.inventory.items.size())) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); ++ return; ++ } ++ this.player.inventory.pickSlot(packet.getSlot()); // Paper - Diff above if changed ++ // Paper end + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, this.player.inventory.selected, this.player.inventory.getItem(this.player.inventory.selected))); + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, packet.getSlot(), this.player.inventory.getItem(packet.getSlot()))); + this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.inventory.selected)); diff --git a/Remapped-Spigot-Server-Patches/0461-Expose-game-version.patch b/Remapped-Spigot-Server-Patches/0461-Expose-game-version.patch new file mode 100644 index 000000000..9e0cf63bd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0461-Expose-game-version.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Fri, 1 May 2020 17:39:26 +0300 +Subject: [PATCH] Expose game version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 43c37e660a8a7f9d326ad38e66f9aa7c53c7b87c..1bfe96443877e460d22513d59ebc3b5988e8eb43 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -514,6 +514,13 @@ public final class CraftServer implements Server { + return bukkitVersion; + } + ++ // Paper start - expose game version ++ @Override ++ public String getMinecraftVersion() { ++ return console.getServerVersion(); ++ } ++ // Paper end ++ + @Override + public List getOnlinePlayers() { + return this.playerView; diff --git a/Remapped-Spigot-Server-Patches/0462-Optimize-Voxel-Shape-Merging.patch b/Remapped-Spigot-Server-Patches/0462-Optimize-Voxel-Shape-Merging.patch new file mode 100644 index 000000000..935b12df1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0462-Optimize-Voxel-Shape-Merging.patch @@ -0,0 +1,175 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 3 May 2020 22:35:09 -0400 +Subject: [PATCH] Optimize Voxel Shape Merging + +This method shows up as super hot in profiler, and also a high "self" time. + +Upon analyzing, it appears most usages of this method fall down to the final +else statement of the nasty ternary. + +Upon even further analyzation, it appears then the majority of those have a +consistent list 1.... One with Infinity head and Tails. + +First optimization is to detect these infinite states and immediately return that +VoxelShapeMergerList so we can avoid testing the rest for most cases. + +Break the method into 2 to help the JVM promote inlining of this fast path. + +Then it was also noticed that VoxelShapeMergerList constructor is also a hotspot +with a high self time... + +Well, knowing that in most cases our list 1 is actualy the same value, it allows +us to know that with an infinite list1, the result on the merger is essentially +list2 as the final values. + +This let us analyze the 2 potential states (Infinite with 2 sources or 4 sources) +and compute a deterministic result for the MergerList values. + +Additionally, this lets us avoid even allocating new objects for this too, further +reducing memory usage. + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +index f0e74daa5bb9e88c028225e7c71deb04c481a7ac..abbe05b07831423eccf8779e854251dec5fbc2ae 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +@@ -6,10 +6,16 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; + + public final class IndirectMerger implements IndexMerger { + +- private final DoubleArrayList result; ++ private final DoubleList a; // Paper + private final IntArrayList firstIndices; + private final IntArrayList secondIndices; + ++ // Paper start ++ private static final IntArrayList INFINITE_B_1 = new IntArrayList(new int[]{1, 1}); ++ private static final IntArrayList INFINITE_B_0 = new IntArrayList(new int[]{0, 0}); ++ private static final IntArrayList INFINITE_C = new IntArrayList(new int[]{0, 1}); ++ // Paper end ++ + protected IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) { + int i = 0; + int j = 0; +@@ -18,7 +24,23 @@ public final class IndirectMerger implements IndexMerger { + int l = second.size(); + int i1 = k + l; + +- this.result = new DoubleArrayList(i1); ++ // Paper start - optimize common path of infinity doublelist ++ int size = first.size(); ++ double tail = first.getDouble(size - 1); ++ double head = first.getDouble(0); ++ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) { ++ this.a = second; ++ if (size == 2) { ++ this.firstIndices = INFINITE_B_0; ++ } else { ++ this.firstIndices = INFINITE_B_1; ++ } ++ this.secondIndices = INFINITE_C; ++ return; ++ } ++ // Paper end ++ ++ this.a = new DoubleArrayList(i1); + this.firstIndices = new IntArrayList(i1); + this.secondIndices = new IntArrayList(i1); + +@@ -27,8 +49,8 @@ public final class IndirectMerger implements IndexMerger { + boolean flag3 = j < l; + + if (!flag2 && !flag3) { +- if (this.result.isEmpty()) { +- this.result.add(Math.min(first.getDouble(k - 1), second.getDouble(l - 1))); ++ if (this.a.isEmpty()) { ++ this.a.add(Math.min(first.getDouble(k - 1), second.getDouble(l - 1))); + } + + return; +@@ -41,9 +63,9 @@ public final class IndirectMerger implements IndexMerger { + if (!(d0 >= d1 - 1.0E-7D)) { // Paper - decompile error - welcome to hell + this.firstIndices.add(i - 1); + this.secondIndices.add(j - 1); +- this.result.add(d1); ++ this.a.add(d1); + d0 = d1; +- } else if (!this.result.isEmpty()) { ++ } else if (!this.a.isEmpty()) { + this.firstIndices.set(this.firstIndices.size() - 1, i - 1); + this.secondIndices.set(this.secondIndices.size() - 1, j - 1); + } +@@ -53,7 +75,7 @@ public final class IndirectMerger implements IndexMerger { + + @Override + public boolean forMergedIndexes(IndexMerger.IndexConsumer predicate) { +- for (int i = 0; i < this.result.size() - 1; ++i) { ++ for (int i = 0; i < this.a.size() - 1; ++i) { + if (!predicate.merge(this.firstIndices.getInt(i), this.secondIndices.getInt(i), i)) { + return false; + } +@@ -64,6 +86,6 @@ public final class IndirectMerger implements IndexMerger { + + @Override + public DoubleList getList() { +- return this.result; ++ return this.a; + } + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index c14d5ebe16a693834ed218af8f737714065b2e17..1603eb3f7d90a4b3a028b20776566db77d09c123 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -329,19 +329,46 @@ public final class Shapes { + } + + @VisibleForTesting +- protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { +- int j = first.size() - 1; +- int k = second.size() - 1; ++ private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // Paper - private ++ // Paper start - fast track the most common scenario ++ // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause ++ // This is actually the most common path, so jump to it straight away ++ if (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) { ++ return new IndirectMerger(first, second, includeFirst, includeSecond); ++ } ++ // Split out rest to hopefully inline the above ++ return lessCommonMerge(size, first, second, includeFirst, includeSecond); ++ } ++ ++ private static IndexMerger lessCommonMerge(int i, DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { ++ int j = doublelist.size() - 1; ++ int k = doublelist1.size() - 1; ++ // Paper note - Rewrite below as optimized order if instead of nasty ternary + +- if (first instanceof CubePointRange && second instanceof CubePointRange) { ++ if (doublelist instanceof CubePointRange && doublelist1 instanceof CubePointRange) { + long l = lcm(j, k); + +- if ((long) size * l <= 256L) { ++ if ((long) i * l <= 256L) { + return new DiscreteCubeMerger(j, k); + } + } + +- return (IndexMerger) (first.getDouble(j) < second.getDouble(0) - 1.0E-7D ? new NonOverlappingMerger(first, second, false) : (second.getDouble(k) < first.getDouble(0) - 1.0E-7D ? new NonOverlappingMerger(second, first, true) : (j == k && Objects.equals(first, second) ? (first instanceof IdenticalMerger ? (IndexMerger) first : (second instanceof IdenticalMerger ? (IndexMerger) second : new IdenticalMerger(first))) : new IndirectMerger(first, second, includeFirst, includeSecond)))); ++ // Identical happens more often than Disjoint ++ if (j == k && Objects.equals(doublelist, doublelist1)) { ++ if (doublelist instanceof IdenticalMerger) { ++ return (IndexMerger) doublelist; ++ } else if (doublelist1 instanceof IdenticalMerger) { ++ return (IndexMerger) doublelist1; ++ } ++ return new IdenticalMerger(doublelist); ++ } else if (doublelist.getDouble(j) < doublelist1.getDouble(0) - 1.0E-07) { ++ return new NonOverlappingMerger(doublelist, doublelist1, false); ++ } else if (doublelist1.getDouble(k) < doublelist.getDouble(0) - 1.0E-07) { ++ return new NonOverlappingMerger(doublelist1, doublelist, true); ++ } else { ++ return new IndirectMerger(doublelist, doublelist1, flag, flag1); ++ } ++ // Paper end + } + + public interface DoubleLineConsumer { diff --git a/Remapped-Spigot-Server-Patches/0463-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/Remapped-Spigot-Server-Patches/0463-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch new file mode 100644 index 000000000..d35e66356 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0463-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 May 2020 01:08:56 -0400 +Subject: [PATCH] Set cap on JDK per-thread native byte buffer cache + +See: https://www.evanjones.ca/java-bytebuffer-leak.html + +This is potentially a source of lots of native memory usage. + +We are clearly seeing native usage upwards to 1-4GB which doesn't make sense. + +Region File usage fixed in previous patch should of tecnically only been somewhat +temporary until GC finally gets it some time later, but between all the various +plugins doing IO on various threads, this hidden detail of the JDK could be +keeping long lived large direct buffers in cache. + +Set system properly at server startup if not set already to help protect from this. + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index c288b89bf5a22269823ba1d18af217032d7c6a36..bd10345cb90f98b8af1519afd603a5244f3a5ca2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -28,6 +28,7 @@ public class Main { + } + // Paper end + // Todo: Installation script ++ if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size + OptionParser parser = new OptionParser() { + { + acceptsAll(asList("?", "help"), "Show the help"); diff --git a/Remapped-Spigot-Server-Patches/0464-Implement-Mob-Goal-API.patch b/Remapped-Spigot-Server-Patches/0464-Implement-Mob-Goal-API.patch new file mode 100644 index 000000000..0aaed21f2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0464-Implement-Mob-Goal-API.patch @@ -0,0 +1,1104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Fri, 3 Jan 2020 16:26:19 +0100 +Subject: [PATCH] Implement Mob Goal API + + +diff --git a/pom.xml b/pom.xml +index 4c8a057e790c96b0ab5123549d0566371acacb46..1a9204c869dd36e80932b1366352db15ebd70723 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -176,6 +176,13 @@ + 1.3 + test + ++ ++ ++ io.github.classgraph ++ classgraph ++ 4.8.47 ++ test ++ + + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ddb198f86ccf3bc2471752d5fb2f59d9a7ab4df +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,462 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import GoalKey; ++import GoalType; ++import com.destroystokyo.paper.entity.RangedEntity; ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import java.lang.reflect.Constructor; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.AgableMob; ++import net.minecraft.world.entity.FlyingMob; ++import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.TamableAnimal; ++import net.minecraft.world.entity.ai.goal.Goal; ++import net.minecraft.world.entity.ambient.AmbientCreature; ++import net.minecraft.world.entity.animal.AbstractFish; ++import net.minecraft.world.entity.animal.AbstractGolem; ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Pufferfish; ++import net.minecraft.world.entity.animal.ShoulderRidingEntity; ++import net.minecraft.world.entity.animal.SnowGolem; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; ++import net.minecraft.world.entity.boss.wither.WitherBoss; ++import net.minecraft.world.entity.monster.AbstractIllager; ++import net.minecraft.world.entity.monster.AbstractSkeleton; ++import net.minecraft.world.entity.monster.EnderMan; ++import net.minecraft.world.entity.monster.PatrollingMonster; ++import net.minecraft.world.entity.monster.RangedAttackMob; ++import net.minecraft.world.entity.monster.SpellcasterIllager; ++import net.minecraft.world.entity.monster.ZombifiedPiglin; ++import net.minecraft.world.entity.monster.piglin.AbstractPiglin; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.Ambient; ++import org.bukkit.entity.Animals; ++import org.bukkit.entity.Bat; ++import org.bukkit.entity.Bee; ++import org.bukkit.entity.Blaze; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.CaveSpider; ++import org.bukkit.entity.ChestedHorse; ++import org.bukkit.entity.Chicken; ++import org.bukkit.entity.Cod; ++import org.bukkit.entity.Cow; ++import org.bukkit.entity.Creature; ++import org.bukkit.entity.Creeper; ++import org.bukkit.entity.Dolphin; ++import org.bukkit.entity.Donkey; ++import org.bukkit.entity.Drowned; ++import org.bukkit.entity.ElderGuardian; ++import org.bukkit.entity.EnderDragon; ++import org.bukkit.entity.Enderman; ++import org.bukkit.entity.Endermite; ++import org.bukkit.entity.Evoker; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Flying; ++import org.bukkit.entity.Fox; ++import org.bukkit.entity.Ghast; ++import org.bukkit.entity.Giant; ++import org.bukkit.entity.Golem; ++import org.bukkit.entity.Guardian; ++import org.bukkit.entity.Hoglin; ++import org.bukkit.entity.Horse; ++import org.bukkit.entity.Husk; ++import org.bukkit.entity.Illager; ++import org.bukkit.entity.Illusioner; ++import org.bukkit.entity.IronGolem; ++import org.bukkit.entity.Llama; ++import org.bukkit.entity.MagmaCube; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Monster; ++import org.bukkit.entity.Mule; ++import org.bukkit.entity.MushroomCow; ++import org.bukkit.entity.Ocelot; ++import org.bukkit.entity.Panda; ++import org.bukkit.entity.Parrot; ++import org.bukkit.entity.Phantom; ++import org.bukkit.entity.Pig; ++import org.bukkit.entity.PigZombie; ++import org.bukkit.entity.Piglin; ++import org.bukkit.entity.PiglinAbstract; ++import org.bukkit.entity.PiglinBrute; ++import org.bukkit.entity.Pillager; ++import org.bukkit.entity.PolarBear; ++import org.bukkit.entity.PufferFish; ++import org.bukkit.entity.Rabbit; ++import org.bukkit.entity.Raider; ++import org.bukkit.entity.Ravager; ++import org.bukkit.entity.Salmon; ++import org.bukkit.entity.Sheep; ++import org.bukkit.entity.Shulker; ++import org.bukkit.entity.Silverfish; ++import org.bukkit.entity.Skeleton; ++import org.bukkit.entity.SkeletonHorse; ++import org.bukkit.entity.Slime; ++import org.bukkit.entity.Snowman; ++import org.bukkit.entity.Spellcaster; ++import org.bukkit.entity.Spider; ++import org.bukkit.entity.Squid; ++import org.bukkit.entity.Stray; ++import org.bukkit.entity.Strider; ++import org.bukkit.entity.Tameable; ++import org.bukkit.entity.TraderLlama; ++import org.bukkit.entity.TropicalFish; ++import org.bukkit.entity.Turtle; ++import org.bukkit.entity.Vex; ++import org.bukkit.entity.Villager; ++import org.bukkit.entity.Vindicator; ++import org.bukkit.entity.WanderingTrader; ++import org.bukkit.entity.WaterMob; ++import org.bukkit.entity.Witch; ++import org.bukkit.entity.Wither; ++import org.bukkit.entity.WitherSkeleton; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.Zoglin; ++import org.bukkit.entity.Zombie; ++import org.bukkit.entity.ZombieHorse; ++import org.bukkit.entity.ZombieVillager; ++ ++public class MobGoalHelper { ++ ++ private static final BiMap deobfuscationMap = HashBiMap.create(); ++ private static final Map, Class> entityClassCache = new HashMap<>(); ++ private static final Map, Class> bukkitMap = new HashMap<>(); ++ ++ static final Set ignored = new HashSet<>(); ++ ++ static { ++ // TODO these kinda should be checked on each release, in case obfuscation changes ++ deobfuscationMap.put("bee_b", "bee_attack"); ++ deobfuscationMap.put("bee_c", "bee_become_angry"); ++ deobfuscationMap.put("bee_d", "bee_enter_hive"); ++ deobfuscationMap.put("bee_e", "bee_go_to_hive"); ++ deobfuscationMap.put("bee_f", "bee_go_to_known_flower"); ++ deobfuscationMap.put("bee_g", "bee_grow_crop"); ++ deobfuscationMap.put("bee_h", "bee_hurt_by_other"); ++ deobfuscationMap.put("bee_i", "bee_locate_hive"); ++ deobfuscationMap.put("bee_k", "bee_pollinate"); ++ deobfuscationMap.put("bee_l", "bee_wander"); ++ deobfuscationMap.put("cat_a", "cat_avoid_entity"); ++ deobfuscationMap.put("cat_b", "cat_relax_on_owner"); ++ deobfuscationMap.put("dolphin_b", "dolphin_swim_to_treasure"); ++ deobfuscationMap.put("dolphin_c", "dolphin_swim_with_player"); ++ deobfuscationMap.put("dolphin_d", "dolphin_play_with_items"); ++ deobfuscationMap.put("drowned_a", "drowned_attack"); ++ deobfuscationMap.put("drowned_b", "drowned_goto_beach"); ++ deobfuscationMap.put("drowned_c", "drowned_goto_water"); ++ deobfuscationMap.put("drowned_e", "drowned_swim_up"); ++ deobfuscationMap.put("drowned_f", "drowned_trident_attack"); ++ deobfuscationMap.put("enderman_a", "enderman_freeze_when_looked_at"); ++ deobfuscationMap.put("evoker_a", "evoker_attack_spell"); ++ deobfuscationMap.put("evoker_b", "evoker_cast_spell"); ++ deobfuscationMap.put("evoker_c", "evoker_summon_spell"); ++ deobfuscationMap.put("evoker_d", "evoker_wololo_spell"); ++ deobfuscationMap.put("fish_b", "fish_swim"); ++ deobfuscationMap.put("fox_a", "fox_defend_trusted"); ++ deobfuscationMap.put("fox_b", "fox_faceplant"); ++ deobfuscationMap.put("fox_e", "fox_breed"); ++ deobfuscationMap.put("fox_f", "fox_eat_berries"); ++ deobfuscationMap.put("fox_g", "fox_float"); ++ deobfuscationMap.put("fox_h", "fox_follow_parent"); ++ deobfuscationMap.put("fox_j", "fox_look_at_player"); ++ deobfuscationMap.put("fox_l", "fox_melee_attack"); ++ deobfuscationMap.put("fox_n", "fox_panic"); ++ deobfuscationMap.put("fox_o", "fox_pounce"); ++ deobfuscationMap.put("fox_p", "fox_search_for_items"); ++ deobfuscationMap.put("fox_q", "fox_stroll_through_village"); ++ deobfuscationMap.put("fox_r", "fox_perch_and_search"); ++ deobfuscationMap.put("fox_s", "fox_seek_shelter"); ++ deobfuscationMap.put("fox_t", "fox_sleep"); ++ deobfuscationMap.put("fox_u", "fox_stalk_prey"); ++ deobfuscationMap.put("illager_abstract_b", "raider_open_door"); ++ deobfuscationMap.put("illager_illusioner_a", "illusioner_blindness_spell"); ++ deobfuscationMap.put("illager_illusioner_b", "illusioner_mirror_spell"); ++ deobfuscationMap.put("illager_wizard_b", "spellcaster_cast_spell"); ++ deobfuscationMap.put("llama_a", "llama_attack_wolf"); ++ deobfuscationMap.put("llama_c", "llama_hurt_by"); ++ deobfuscationMap.put("llama_trader_a", "llamatrader_defended_wandering_trader"); ++ deobfuscationMap.put("monster_patrolling_a", "long_distance_patrol"); ++ deobfuscationMap.put("ocelot_a", "ocelot_avoid_entity"); ++ deobfuscationMap.put("ocelot_b", "ocelot_tempt"); ++ deobfuscationMap.put("panda_b", "panda_attack"); ++ deobfuscationMap.put("panda_c", "panda_avoid"); ++ deobfuscationMap.put("panda_d", "panda_breed"); ++ deobfuscationMap.put("panda_e", "panda_hurt_by_target"); ++ deobfuscationMap.put("panda_f", "panda_lie_on_back"); ++ deobfuscationMap.put("panda_g", "panda_look_at_player"); ++ deobfuscationMap.put("panda_i", "panda_panic"); ++ deobfuscationMap.put("panda_j", "panda_roll"); ++ deobfuscationMap.put("panda_k", "panda_sit"); ++ deobfuscationMap.put("panda_l", "panda_sneeze"); ++ deobfuscationMap.put("phantom_b", "phantom_attack_player"); ++ deobfuscationMap.put("phantom_c", "phantom_attack_strategy"); ++ deobfuscationMap.put("phantom_e", "phantom_circle_around_anchor"); ++ deobfuscationMap.put("phantom_i", "phantom_sweep_attack"); ++ deobfuscationMap.put("polar_bear_a", "polarbear_attack_players"); ++ deobfuscationMap.put("polar_bear_b", "polarbear_hurt_by"); ++ deobfuscationMap.put("polar_bear_c", "polarbear_melee"); ++ deobfuscationMap.put("polar_bear_d", "polarbear_panic"); ++ deobfuscationMap.put("puffer_fish_a", "pufferfish_puff"); ++ deobfuscationMap.put("raider_a", "raider_hold_ground"); ++ deobfuscationMap.put("raider_b", "raider_obtain_banner"); ++ deobfuscationMap.put("raider_c", "raider_celebration"); ++ deobfuscationMap.put("raider_d", "raider_move_through_village"); ++ deobfuscationMap.put("ravager_a", "ravager_melee_attack"); ++ deobfuscationMap.put("shulker_a", "shulker_attack"); ++ deobfuscationMap.put("shulker_c", "shulker_defense"); ++ deobfuscationMap.put("shulker_d", "shulker_nearest"); ++ deobfuscationMap.put("shulker_e", "shulker_peek"); ++ deobfuscationMap.put("squid_a", "squid_flee"); ++ deobfuscationMap.put("skeleton_abstract_1", "skeleton_melee"); ++ deobfuscationMap.put("strider_a", "strider_go_to_lava"); ++ deobfuscationMap.put("turtle_a", "turtle_breed"); ++ deobfuscationMap.put("turtle_b", "turtle_go_home"); ++ deobfuscationMap.put("turtle_c", "turtle_goto_water"); ++ deobfuscationMap.put("turtle_d", "turtle_lay_egg"); ++ deobfuscationMap.put("turtle_f", "turtle_panic"); ++ deobfuscationMap.put("turtle_h", "turtle_random_stroll"); ++ deobfuscationMap.put("turtle_i", "turtle_tempt"); ++ deobfuscationMap.put("turtle_j", "turtle_travel"); ++ deobfuscationMap.put("vex_a", "vex_charge_attack"); ++ deobfuscationMap.put("vex_b", "vex_copy_target_of_owner"); ++ deobfuscationMap.put("vex_d", "vex_random_move"); ++ deobfuscationMap.put("villager_trader_a", "villagertrader_wander_to_position"); ++ deobfuscationMap.put("vindicator_a", "vindicator_break_door"); ++ deobfuscationMap.put("vindicator_b", "vindicator_johnny_attack"); ++ deobfuscationMap.put("vindicator_c", "vindicator_melee_attack"); ++ deobfuscationMap.put("wither_a", "wither_do_nothing"); ++ deobfuscationMap.put("wolf_a", "wolf_avoid_entity"); ++ deobfuscationMap.put("zombie_a", "zombie_attack_turtle_egg"); ++ ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); ++ bukkitMap.put(AgableMob.class, Ageable.class); ++ bukkitMap.put(AmbientCreature.class, Ambient.class); ++ bukkitMap.put(Animal.class, Animals.class); ++ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); ++ bukkitMap.put(PathfinderMob.class, Creature.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); ++ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EnderMan.class, Enderman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); ++ bukkitMap.put(AbstractFish.class, Fish.class); ++ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(FlyingMob.class, Flying.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); ++ bukkitMap.put(AbstractGolem.class, Golem.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); ++ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); ++ bukkitMap.put(AbstractIllager.class, Illager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); ++ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); ++ bukkitMap.put(PatrollingMonster.class, Monster.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); ++ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); ++ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); ++ bukkitMap.put(Pufferfish.class, PufferFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); ++ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); ++ bukkitMap.put(AbstractSkeleton.class, Skeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); ++ bukkitMap.put(SnowGolem.class, Snowman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); ++ bukkitMap.put(TamableAnimal.class, Tameable.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); ++ bukkitMap.put(WaterAnimal.class, WaterMob.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); ++ bukkitMap.put(WitherBoss.class, Wither.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); ++ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = clazz.getName(); ++ name = name.substring(name.lastIndexOf(".") + 1); ++ boolean flag = false; ++ // inner classes ++ if (name.contains("$")) { ++ String cut = name.substring(name.indexOf("$") + 1); ++ if (cut.length() <= 2) { ++ name = name.replace("Entity", ""); ++ name = name.replace("$", "_"); ++ flag = true; ++ } else { ++ // mapped, wooo ++ name = cut; ++ } ++ } ++ name = name.replace("PathfinderGoal", ""); ++ StringBuilder sb = new StringBuilder(); ++ for (char c : name.toCharArray()) { ++ if (c >= 'A' && c <= 'Z') { ++ sb.append("_"); ++ sb.append(Character.toLowerCase(c)); ++ } else { ++ sb.append(c); ++ } ++ } ++ name = sb.toString(); ++ name = name.replaceFirst("_", ""); ++ ++ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { ++ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); ++ } ++ ++ // did we rename this key? ++ return deobfuscationMap.getOrDefault(name, name); ++ } ++ ++ public static EnumSet vanillaToPaper(OptimizedSmallEnumSet types) { ++ EnumSet goals = EnumSet.noneOf(GoalType.class); ++ for (GoalType type : GoalType.values()) { ++ if (types.hasElement(paperToVanilla(type))) { ++ goals.add(type); ++ } ++ } ++ return goals; ++ } ++ ++ public static GoalType vanillaToPaper(Goal.Flag type) { ++ switch (type) { ++ case MOVE: ++ return GoalType.MOVE; ++ case LOOK: ++ return GoalType.LOOK; ++ case JUMP: ++ return GoalType.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return GoalType.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return GoalType.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); ++ } ++ } ++ ++ public static EnumSet paperToVanilla(EnumSet types) { ++ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static Goal.Flag paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return Goal.Flag.MOVE; ++ case LOOK: ++ return Goal.Flag.LOOK; ++ case JUMP: ++ return Goal.Flag.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return Goal.Flag.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return Goal.Flag.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); ++ } ++ } ++ ++ public static GoalKey getKey(Class goalClass) { ++ String name = getUsableName(goalClass); ++ if (ignored.contains(name)) { ++ //noinspection unchecked ++ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); ++ } ++ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); ++ } ++ ++ public static Class getEntity(Class goalClass) { ++ //noinspection unchecked ++ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { ++ for (Constructor ctor : key.getDeclaredConstructors()) { ++ for (int i = 0; i < ctor.getParameterCount(); i++) { ++ Class param = ctor.getParameterTypes()[i]; ++ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (RangedAttackMob.class.isAssignableFrom(param)) { ++ return RangedEntity.class; ++ } ++ } ++ } ++ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? ++ }); ++ } ++ ++ public static Class toBukkitClass(Class nmsClass) { ++ Class bukkitClass = bukkitMap.get(nmsClass); ++ if (bukkitClass == null) { ++ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? ++ } ++ return bukkitClass; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14ddf844be10c04522aa3ec125fa7a0f540b10c2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,55 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import GoalKey; ++import net.minecraft.world.entity.ai.goal.Goal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends Goal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setTypes(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ if (this.getGoalTypes().size() == 0) { ++ this.getGoalTypes().addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void onTaskReset() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ public GoalKey getKey() { ++ return handle.getKey(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +new file mode 100644 +index 0000000000000000000000000000000000000000..615f571b08da27b0da7c53e3be172959e82e4ab1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,222 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.ai.goal.Goal; ++import net.minecraft.world.entity.ai.goal.GoalSelector; ++import net.minecraft.world.entity.ai.goal.WrappedGoal; ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++ ++public class PaperMobGoals implements MobGoals { ++ ++ private final Map> instanceCache = new HashMap<>(); ++ ++ @Override ++ public void addGoal(T mob, int priority, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); ++ } ++ ++ @Override ++ public void removeGoal(T mob, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ if (goal instanceof PaperCustomGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal((Goal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getTasks()) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (Goal g : toRemove) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(g); ++ } ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob) { ++ for (GoalType type : GoalType.values()) { ++ removeAllGoals(mob, type); ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob, GoalType type) { ++ for (Goal goal : getAllGoals(mob, type)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public void removeGoal(T mob, GoalKey key) { ++ for (Goal goal : getGoals(mob, key)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public boolean hasGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Goal getGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return g; ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public Collection> getGoals(T mob, GoalKey key) { ++ Set> goals = new HashSet<>(); ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ goals.add(g); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getAllGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (WrappedGoal item : getHandle(craftMob, type).getTasks()) { ++ if (!item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ for (WrappedGoal item : getHandle(craftMob, internalType).getTasks()) { ++ if (item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getRunningGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ getHandle(craftMob, type).getExecutingGoals() ++ .filter(item -> item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ getHandle(craftMob, internalType).getExecutingGoals() ++ .filter(item -> !item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, GoalType type) { ++ if (type == GoalType.TARGET) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d4fe9d517a05f99c715ab73d5baf1deb6a732068 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,62 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import GoalKey; ++import java.util.EnumSet; ++import net.minecraft.world.entity.ai.goal.Goal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final Goal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(Goal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle.getGoalTypes()); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate2(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive2(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.onTaskReset(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +index 9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f..b3329c6fcd6758a781a51f5ba8f5052ac1c77b49 100644 +--- a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java ++++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +@@ -64,4 +64,8 @@ public final class OptimizedSmallEnumSet> { + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } ++ ++ public boolean hasElement(final E element) { ++ return (this.backingSet & (1L << element.ordinal())) != 0; ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index acc6306d659cd65a043d12cd42dcbaf55aaf5250..f85dfd8b57cf81ad7c6b12753fdd42e93f772f9e 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -8,11 +8,17 @@ public abstract class Goal { + private final EnumSet flags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + +- public Goal() {} ++ // Paper start make sure goaltypes is never empty ++ public Goal() { ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ // paper end + +- public abstract boolean canUse(); ++ public boolean canUse() { return this.shouldActivate(); } public boolean shouldActivate() { return false;} public boolean shouldActivate2() { return canUse(); } // Paper - OBFHELPER, for both directions... + +- public boolean canContinueToUse() { ++ public boolean canContinueToUse() { return this.shouldStayActive(); } public boolean shouldStayActive2() { return canContinueToUse(); } public boolean shouldStayActive() { // Paper - OBFHELPER, for both directions... + return this.canUse(); + } + +@@ -20,19 +26,23 @@ public abstract class Goal { + return true; + } + +- public void start() {} ++ public void start() { this.start(); } public void start() {} // Paper - OBFHELPER + + public void stop() { + onTaskReset(); // Paper + } + public void onTaskReset() {} // Paper + +- public void tick() {} ++ public void tick() { this.tick(); } public void tick() {} // Paper OBFHELPER + +- public void setFlags(EnumSet controls) { ++ public void setFlags(EnumSet controls) { this.setTypes(controls); } public void setTypes(EnumSet enumset) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + this.goalTypes.clear(); +- this.goalTypes.addAllUnchecked(controls); ++ this.goalTypes.addAllUnchecked(enumset); ++ // make sure its never empty ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } + // Paper end - remove streams from pathfindergoalselector + } + +@@ -48,7 +58,7 @@ public abstract class Goal { + + public static enum Flag { + +- MOVE, LOOK, JUMP, TARGET; ++ MOVE, LOOK, JUMP, TARGET, UNKNOWN_BEHAVIOR; // Paper - add unknown + + private Flag() {} + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 5da2d780c17522e07c733a5e23b17ec760c7b342..a03f72f67948efab3b000dfa1d48061abf7cc02f 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -28,7 +28,7 @@ public class GoalSelector { + } + }; + private final Map lockedFlags = new EnumMap(Goal.Flag.class); +- private final Set availableGoals = Sets.newLinkedHashSet(); private Set getTasks() { return availableGoals; }// Paper - OBFHELPER ++ private final Set availableGoals = Sets.newLinkedHashSet(); public final Set getTasks() { return availableGoals; }// Paper - OBFHELPER // Paper - private -> public + private final Supplier profiler; + private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector +@@ -39,7 +39,7 @@ public class GoalSelector { + this.profiler = profiler; + } + +- public void addGoal(int priority, Goal goal) { ++ public void addGoal(int priority, Goal goal) {addGoal(priority, goal);} public void addGoal(int priority, Goal goal) { // Paper - OBFHELPER + this.availableGoals.add(new WrappedGoal(priority, goal)); + } + +@@ -58,7 +58,7 @@ public class GoalSelector { + } + // Paper end + +- public void removeGoal(Goal goal) { ++ public void removeGoal(Goal goal) {removeGoal(goal);} public void removeGoal(Goal goal) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + for (Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { + WrappedGoal goalWrapped = iterator.next(); +@@ -154,6 +154,7 @@ public class GoalSelector { + gameprofilerfiller.pop(); + } + ++ public final Stream getExecutingGoals() { return getRunningGoals(); } // Paper - OBFHELPER + public Stream getRunningGoals() { + return this.availableGoals.stream().filter(WrappedGoal::isRunning); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +index 984146b2b6eb3e498433b1c4971397848166d9c9..06fe2248a52c180ffabe0a6fe0cf155b78d4752d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +@@ -5,8 +5,8 @@ import javax.annotation.Nullable; + + public class WrappedGoal extends Goal { + +- private final Goal goal; +- private final int priority; ++ private final Goal goal; public Goal getGoal() {return goal;} // Paper - OBFHELPER ++ private final int priority; public int getPriority() {return priority;} // Paper - OBFHELPER + private boolean isRunning; + + public WrappedGoal(int priority, Goal goal) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1bfe96443877e460d22513d59ebc3b5988e8eb43..c6dc314a1735bf849ee1572e01335909bed9b455 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2390,5 +2390,11 @@ public final class CraftServer implements Server { + public boolean isStopping() { + return net.minecraft.server.MinecraftServer.getServer().hasStopped(); + } ++ ++ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); ++ @Override ++ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { ++ return mobGoals; ++ } + // Paper end + } +diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0525216a8469613c3e0d4b5774a82f69e70fb16 +--- /dev/null ++++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +@@ -0,0 +1,104 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import org.junit.Assert; ++import org.junit.Test; ++ ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++import org.bukkit.entity.Mob; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ScanResult; ++ ++public class VanillaMobGoalTest { ++ ++ @Test ++ public void testKeys() { ++ List> deprecated = new ArrayList<>(); ++ List> keys = new ArrayList<>(); ++ for (Field field : VanillaGoal.class.getFields()) { ++ if (field.getType().equals(GoalKey.class)) { ++ try { ++ GoalKey goalKey = (GoalKey) field.get(null); ++ if (field.getAnnotation(Deprecated.class) != null) { ++ deprecated.add(goalKey); ++ } else { ++ keys.add(goalKey); ++ } ++ } catch (IllegalAccessException e) { ++ System.out.println("Skipping " + field.getName() + ": " + e.getMessage()); ++ } ++ } ++ } ++ ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { ++ classes = scanResult.getSubclasses(PathfinderGoal.class.getName()).loadClasses(); ++ } ++ ++ List> vanillaNames = classes.stream() ++ .filter(VanillaMobGoalTest::hasNoEnclosingClass) ++ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) ++ .map(goalClass -> MobGoalHelper.getKey((Class) goalClass)) ++ .collect(Collectors.toList()); ++ ++ List> missingFromAPI = new ArrayList<>(vanillaNames); ++ missingFromAPI.removeAll(keys); ++ missingFromAPI.removeIf(k -> MobGoalHelper.ignored.contains(k.getNamespacedKey().getKey())); ++ List> missingFromVanilla = new ArrayList<>(keys); ++ missingFromVanilla.removeAll(vanillaNames); ++ ++ boolean shouldFail = false; ++ if (missingFromAPI.size() != 0) { ++ System.out.println("Missing from API: "); ++ for (GoalKey key : missingFromAPI) { ++ System.out.println("GoalKey<" + key.getEntityClass().getSimpleName() + "> " + key.getNamespacedKey().getKey().toUpperCase() + ++ " = GoalKey.of(" + key.getEntityClass().getSimpleName() + ".class, NamespacedKey.minecraft(\"" + key.getNamespacedKey().getKey() + "\"));"); ++ } ++ shouldFail = true; ++ } ++ if (missingFromVanilla.size() != 0) { ++ System.out.println("Missing from vanilla: "); ++ missingFromVanilla.forEach(System.out::println); ++ shouldFail = true; ++ } ++ ++ if (deprecated.size() != 0) { ++ System.out.println("Deprecated (might want to remove them at some point): "); ++ deprecated.forEach(System.out::println); ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++ ++ private static boolean hasNoEnclosingClass(Class clazz) { ++ return clazz.getEnclosingClass() == null || hasNoEnclosingClass(clazz.getSuperclass()); ++ } ++ ++ @Test ++ public void testBukkitMap() { ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft.world.entity").scan()) { ++ classes = scanResult.getSubclasses("net.minecraft.world.entity.EntityInsentient").loadClasses(); ++ } ++ Assert.assertNotEquals("There are supposed to be more than 0 entity types!", Collections.emptyList(), classes); ++ ++ boolean shouldFail = false; ++ for (Class nmsClass : classes) { ++ Class bukkitClass = MobGoalHelper.toBukkitClass((Class) nmsClass); ++ if (bukkitClass == null) { ++ shouldFail = true; ++ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, " + nmsClass.getSimpleName().replace("Entity", "") + ".class);"); ++ } ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++} diff --git a/Remapped-Spigot-Server-Patches/0465-Use-distance-map-to-optimise-entity-tracker.patch b/Remapped-Spigot-Server-Patches/0465-Use-distance-map-to-optimise-entity-tracker.patch new file mode 100644 index 000000000..16a647e32 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0465-Use-distance-map-to-optimise-entity-tracker.patch @@ -0,0 +1,413 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:18:05 -0700 +Subject: [PATCH] Use distance map to optimise entity tracker + +Use the distance map to find candidate players for tracking. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e5ad635a480d32e7a10ee92c65cfc18a98beafad..74f393ffa2ae2d0e25b3f0b674cef7a987e985d3 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1652,6 +1652,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); ++ // Paper start - use distance map to optimise tracker ++ public static boolean isLegacyTrackingEntity(Entity entity) { ++ return entity.isLegacyTrackingEntity; ++ } ++ ++ // inlined EnumMap, TrackingRange.TrackingRangeType ++ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; ++ final int[] entityTrackerTrackRanges; ++ ++ private int convertSpigotRangeToVanilla(final int vanilla) { ++ return MinecraftServer.getServer().applyTrackingRangeScale(vanilla); ++ } ++ // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // Paper start - use distance map to optimise entity tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; ++ int trackRange = this.entityTrackerTrackRanges[i]; ++ ++ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +- ++ // Paper start - use distance map to optimise tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ this.playerEntityTrackerTrackMaps[i].remove(player); ++ } ++ // Paper end - use distance map to optimise tracker + } + + void updateMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // Paper start - use distance map to optimise entity tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; ++ int trackRange = this.entityTrackerTrackRanges[i]; ++ ++ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + // Paper end + +@@ -244,6 +279,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper + this.setViewDistance(i); + this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper ++ // Paper start - use distance map to optimise entity tracker ++ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; ++ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; ++ ++ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig; ++ ++ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { ++ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; ++ int configuredSpigotValue; ++ switch (trackingRangeType) { ++ case PLAYER: ++ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; ++ break; ++ case ANIMAL: ++ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; ++ break; ++ case MONSTER: ++ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; ++ break; ++ case MISC: ++ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; ++ break; ++ case OTHER: ++ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; ++ break; ++ case ENDERDRAGON: ++ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; ++ break; ++ default: ++ throw new IllegalStateException("Missing case for enum " + trackingRangeType); ++ } ++ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); ++ ++ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); ++ this.entityTrackerTrackRanges[ordinal] = trackRange; ++ ++ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -1490,17 +1564,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void move(ServerPlayer player) { +- ObjectIterator objectiterator = this.entityMap.values().iterator(); +- +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- +- if (playerchunkmap_entitytracker.entity == player) { +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); +- } else { +- playerchunkmap_entitytracker.updatePlayer(player); +- } +- } ++ // Paper - delay this logic for the entity tracker tick, no need to duplicate it + + int i = Mth.floor(player.getX()) >> 4; + int j = Mth.floor(player.getZ()) >> 4; +@@ -1616,7 +1680,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); ++ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -1659,7 +1723,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.tracker = null; // Paper - We're no longer tracked + } + ++ // Paper start - optimised tracker ++ private final void processTrackQueue() { ++ this.level.timings.tracker1.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ // update tracker entry ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ } ++ } finally { ++ this.level.timings.tracker1.stopTiming(); ++ } ++ ++ ++ this.level.timings.tracker2.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.tick(); ++ } ++ } finally { ++ this.level.timings.tracker2.stopTiming(); ++ } ++ } ++ // Paper end - optimised tracker ++ + protected void tick() { ++ // Paper start - optimized tracker ++ if (true) { ++ this.processTrackQueue(); ++ return; ++ } ++ // Paper end - optimized tracker + List list = Lists.newArrayList(); + List list1 = this.level.players(); + +@@ -1728,23 +1822,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); +- ObjectIterator objectiterator = this.entityMap.values().iterator(); ++ // Paper start - optimise entity tracker ++ // use the chunk entity list, not the whole trackedEntities map... ++ Entity[] entities = chunk.entities.getRawData(); ++ for (int i = 0, size = chunk.entities.size(); i < size; ++i) { ++ Entity entity = entities[i]; ++ if (entity == player) { ++ continue; ++ } ++ ChunkMap.TrackedEntity tracker = this.entityMap.get(entity.getId()); ++ if (tracker != null) { // dumb plugins... move on... ++ tracker.updatePlayer(player); ++ } + +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- Entity entity = playerchunkmap_entitytracker.entity; ++ // keep the vanilla logic here - this is REQUIRED or else passengers and their vehicles disappear! ++ // (and god knows what the leash thing is) + +- if (entity != player && entity.xChunk == chunk.getPos().x && entity.zChunk == chunk.getPos().z) { +- playerchunkmap_entitytracker.updatePlayer(player); +- if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { +- list.add(entity); +- } ++ if (entity instanceof Mob && ((Mob)entity).getLeashHolder() != null) { ++ list.add(entity); ++ } + +- if (!entity.getPassengers().isEmpty()) { +- list1.add(entity); +- } ++ if (!entity.getPassengers().isEmpty()) { ++ list1.add(entity); + } + } ++ // Paper end - optimise entity tracker + + Iterator iterator; + Entity entity1; +@@ -1782,7 +1884,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + public class TrackedEntity { + +- private final ServerEntity serverEntity; ++ final ServerEntity serverEntity; // Paper - private -> package private + private final Entity entity; + private final int range; + private SectionPos lastSectionPos; +@@ -1799,6 +1901,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastSectionPos = SectionPos.of(entity); + } + ++ // Paper start - use distance map to optimise tracker ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; ++ ++ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; ++ this.lastTrackerCandidates = newTrackerCandidates; ++ ++ if (newTrackerCandidates != null) { ++ Object[] rawData = newTrackerCandidates.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ Object raw = rawData[i]; ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)raw; ++ this.updatePlayer(player); ++ } ++ } ++ ++ if (oldTrackerCandidates == newTrackerCandidates) { ++ // this is likely the case. ++ // means there has been no range changes, so we can just use the above for tracking. ++ return; ++ } ++ ++ // stuff could have been removed, so we need to check the trackedPlayers set ++ // for players that were removed ++ ++ for (ServerPlayer player : this.seenBy.toArray(new ServerPlayer[0])) { // avoid CME ++ if (newTrackerCandidates == null || !newTrackerCandidates.contains(player)) { ++ this.updatePlayer(player); ++ } ++ } ++ } ++ // Paper end - use distance map to optimise tracker ++ + public boolean equals(Object object) { + return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; + } +@@ -1899,7 +2037,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int j = entity.getType().clientTrackingRange() * 16; + j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + +- if (j > i) { ++ if (j < i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic + i = j; + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 1609ab94c86e964421f996d4d46aef30f8b8e696..d797873db52ba265ac4478f9f3c6344badd4739e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -100,6 +100,7 @@ public class ServerEntity { + this.wasOnGround = entity.isOnGround(); + } + ++ public final void tick() { this.sendChanges(); } // Paper - OBFHELPER + public void sendChanges() { + List list = this.entity.getPassengers(); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2b48c4a2b512c42bed2c767db90a28898c74286a..d9bb00752ac81b2171d3ad25fd84904467a18e3b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -46,6 +46,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.ServerLevel; +@@ -294,6 +295,21 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + // CraftBukkit end + ++ // Paper start - optimise entity tracking ++ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); ++ ++ public boolean isLegacyTrackingEntity = false; ++ ++ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { ++ this.isLegacyTrackingEntity = isLegacyTrackingEntity; ++ } ++ ++ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { ++ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] ++ .getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ // Paper end - optimise entity tracking ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = Lists.newArrayList(); +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index b03fa9024c7f0238e1379f6ae4486db5300a70e9..7b6011be849ecffdd791d439f70ae5dffc96f264 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -20,6 +20,7 @@ public class TrackingRange + */ + public static int getEntityTrackingRange(Entity entity, int defaultRange) + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return defaultRange; // Paper - enderdragon is exempt + SpigotWorldConfig config = entity.level.spigotConfig; + if ( entity instanceof ServerPlayer ) + { +@@ -43,8 +44,48 @@ public class TrackingRange + return config.miscTrackingRange; + } else + { +- if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } ++ ++ // Paper start - optimise entity tracking ++ // copied from above, TODO check on update ++ public static TrackingRangeType getTrackingRangeType(Entity entity) ++ { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt ++ if ( entity instanceof ServerPlayer ) ++ { ++ return TrackingRangeType.PLAYER; ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: ++ return TrackingRangeType.MONSTER; ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return TrackingRangeType.ANIMAL; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end ++ { ++ return TrackingRangeType.MISC; ++ } else ++ { ++ return TrackingRangeType.OTHER; ++ } ++ } ++ ++ public static enum TrackingRangeType { ++ PLAYER, ++ ANIMAL, ++ MONSTER, ++ MISC, ++ OTHER, ++ ENDERDRAGON; ++ } ++ // Paper end - optimise entity tracking + } diff --git a/Remapped-Spigot-Server-Patches/0466-Optimize-isOutsideRange-to-use-distance-maps.patch b/Remapped-Spigot-Server-Patches/0466-Optimize-isOutsideRange-to-use-distance-maps.patch new file mode 100644 index 000000000..adc6c1406 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0466-Optimize-isOutsideRange-to-use-distance-maps.patch @@ -0,0 +1,384 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize isOutsideRange to use distance maps + +Use a distance map to find the players in range quickly + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index d12d5459c847d3f0d655c85e31d81c27b7a2face..0147798c0285f64b8d767dfb2709d92f66ac72ef 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -69,6 +69,18 @@ public class ChunkHolder { + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave + ++ // Paper start - optimise isOutsideOfRange ++ // cached here to avoid a map lookup ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; ++ ++ void updateRanges() { ++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ } ++ // Paper end - optimise isOutsideOfRange ++ + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +@@ -85,6 +97,7 @@ public class ChunkHolder { + this.queueLevel = this.oldTicketLevel; + this.setTicketLevel(level); + this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper ++ this.updateRanges(); // Paper - optimise isOutsideOfRange + } + + // Paper start +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 41147be189f764c2346cf015890a148f9730bc0f..56ca469bf930bcced88efdafc78f464c756a5be9 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -208,6 +208,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return MinecraftServer.getServer().applyTrackingRangeScale(vanilla); + } + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ // A note about the naming used here: ++ // Previously, mojang used a "spawn range" of 8 for controlling both ticking and ++ // mob spawn range. However, spigot makes the spawn range configurable by ++ // checking if the chunk is in the tick range (8) and the spawn range ++ // obviously this means a spawn range > 8 cannot be implemented ++ ++ // these maps are named after spigot's uses ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); +@@ -221,6 +232,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +@@ -229,6 +243,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerEntityTrackerTrackMaps[i].remove(player); + } + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void updateMaps(ServerPlayer player) { +@@ -243,6 +261,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + // Paper end + +@@ -274,7 +295,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); + this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); + this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); +- this.distanceManager = new ChunkMap.ChunkDistanceManager(workerExecutor, mainThreadExecutor); ++ this.distanceManager = new ChunkMap.ChunkDistanceManager(workerExecutor, mainThreadExecutor); this.distanceManager.chunkMap = this; // Paper + this.overworldDataStorage = supplier; + this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper + this.setViewDistance(i); +@@ -318,6 +339,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -337,6 +390,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + ++ private static double getDistanceSquaredFromChunk(ChunkPos chunkPos, Entity entity) { return euclideanDistanceSquared(chunkPos, entity); } // Paper - OBFHELPER + private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { + double d0 = (double) (pos.x * 16 + 8); + double d1 = (double) (pos.z * 16 + 8); +@@ -515,6 +569,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + if (holder != null) { + holder.setTicketLevel(level); ++ holder.updateRanges(); // Paper - optimise isOutsideOfRange + } + + if (holder != null) { +@@ -1493,30 +1548,53 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return isOutsideOfRange(chunkcoordintpair, false); + } + +- boolean isOutsideOfRange(ChunkPos chunkcoordintpair, boolean reducedRange) { +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; ++ // Paper start - optimise isOutsideOfRange ++ final boolean isOutsideOfRange(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ return this.isOutsideOfRange(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); ++ } + +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- // Spigot end +- long i = chunkcoordintpair.toLong(); ++ final boolean isOutsideOfRange(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { ++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance ++ // tested and confirmed via System.nanoTime() ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; + +- return !this.distanceManager.hasPlayersNearby(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { +- // Paper start - +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- double blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); +- } ++ if (playersInRange == null) { ++ return true; ++ } + +- return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot +- // Paper end +- }); ++ Object[] backingSet = playersInRange.getBackingSet(); ++ ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (player.lastEntitySpawnRadiusSquared > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } else { ++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16); ++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (range > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } ++ // no players in range ++ return true; + } ++ // Paper end - optimise isOutsideOfRange + + private boolean skipPlayer(ServerPlayer player) { + return player.isSpectator() && !this.level.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 90429d3f5c5b725098cfb001d54c70608f3df7bb..91c672531087430c47365657a3219ab5980d3467 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -37,7 +37,7 @@ public abstract class DistanceManager { + private final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); +- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); ++ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); + // Paper start use a queue, but still keep unique requirement + public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { +@@ -56,6 +56,8 @@ public abstract class DistanceManager { + private final Executor mainThreadExecutor; + private long ticketTickCounter; + ++ ChunkMap chunkMap; // Paper ++ + protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor) { + mainThreadExecutor.getClass(); + ProcessorHandle mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute); +@@ -100,7 +102,7 @@ public abstract class DistanceManager { + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + + public boolean runAllUpdates(ChunkMap chunkStorage) { +- this.naturalSpawnChunkCounter.runAllUpdates(); ++ //this.f.a(); // Paper - no longer used + this.playerTicketManager.runAllUpdates(); + int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -236,7 +238,7 @@ public abstract class DistanceManager { + ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { + return new ObjectOpenHashSet(); + })).add(player); +- this.naturalSpawnChunkCounter.update(i, 0, true); ++ //this.f.update(i, 0, true); // Paper - no longer used + this.playerTicketManager.update(i, 0, true); + } + +@@ -248,7 +250,7 @@ public abstract class DistanceManager { + if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); +- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); ++ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); + } + +@@ -272,13 +274,17 @@ public abstract class DistanceManager { + } + + public int getNaturalSpawnChunkCount() { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.size(); ++ // Paper start - use distance map to implement ++ // note: this is the spawn chunk count ++ return this.chunkMap.playerChunkTickRangeMap.size(); ++ // Paper end - use distance map to implement + } + + public boolean hasPlayersNearby(long i) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.containsKey(i); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(i) != null; ++ // Paper end - use distance map to implement + } + + public String getDebugStatus() { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 79fb63c40dd0543a6f629e78f390f23f34992ba1..52c2e81f2e2bcd74d4e9aac3ecb5ab618e289abd 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -753,6 +753,37 @@ public class ServerChunkCache extends ChunkSource { + boolean flag1 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !level.players().isEmpty(); // CraftBukkit + + if (!flag) { ++ // Paper start - optimize isOutisdeRange ++ ChunkMap playerChunkMap = this.chunkMap; ++ for (ServerPlayer player : this.level.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int viewDistance = this.chunkMap.getEffectiveViewDistance(); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; ++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; ++ ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); ++ event.callEvent(); ++ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.playerChunkTickRangeMap.getLastViewDistance(player) == -1) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance ++ int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.getX()); ++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.getZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in isOutsideRange ++ player.playerNaturallySpawnedEvent = event; ++ } ++ // Paper end - optimize isOutisdeRange + this.level.getProfiler().push("pollingChunks"); + int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + boolean flag2 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit +@@ -782,15 +813,7 @@ public class ServerChunkCache extends ChunkSource { + this.level.getProfiler().pop(); + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper +- //Paper start - call player naturally spawn event +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (ServerPlayer entityPlayer : this.level.players()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - moved up + this.level.timings.chunkTicks.startTiming(); // Paper + final int[] chunksTicked = {0}; this.chunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); +@@ -807,9 +830,9 @@ public class ServerChunkCache extends ChunkSource { + LevelChunk chunk = (LevelChunk) optional1.get(); + ChunkPos chunkcoordintpair = playerchunk.getPos(); + +- if (!this.chunkMap.noPlayersCloseForSpawning(chunkcoordintpair)) { ++ if (!this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange + chunk.setInhabitedTime(chunk.getInhabitedTime() + j); +- if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot ++ if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange + NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 24c508ade61a6ad90b0ef73cdc995f531ef18263..95f1f4727a8e2000931e6f36b862e3ad28334a69 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -247,6 +247,8 @@ public class ServerPlayer extends Player implements ContainerListener { + + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + ++ double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks ++ + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { + super(world, world.getSpawn(), world.getSharedSpawnAngle(), profile); + this.respawnDimension = Level.OVERWORLD; diff --git a/Remapped-Spigot-Server-Patches/0467-Stop-copy-on-write-operations-for-updating-light-dat.patch b/Remapped-Spigot-Server-Patches/0467-Stop-copy-on-write-operations-for-updating-light-dat.patch new file mode 100644 index 000000000..e9e6a043f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0467-Stop-copy-on-write-operations-for-updating-light-dat.patch @@ -0,0 +1,320 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Apr 2020 04:05:38 -0700 +Subject: [PATCH] Stop copy-on-write operations for updating light data + +Causes huge memory allocations + gc issues + +diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +index 9f33fa8f84d10f8f4089030074ad6c0d81269ce8..a1ad4d73ddaf6afe97a1f1ff7e0622b52fac8761 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +@@ -10,7 +10,7 @@ import net.minecraft.world.level.chunk.LightChunkGetter; + public class BlockLightSectionStorage extends LayerLightSectionStorage { + + protected BlockLightSectionStorage(LightChunkGetter chunkProvider) { +- super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new Long2ObjectOpenHashMap())); ++ super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), false)); // Paper - avoid copying light data + } + + @Override +@@ -23,13 +23,13 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage { + +- public BlockDataLayerStorageMap(Long2ObjectOpenHashMap arrays) { +- super(arrays); ++ public BlockDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object long2objectopenhashmap, boolean isVisible) { // Paper - avoid copying light data ++ super(long2objectopenhashmap, isVisible); // Paper - avoid copying light data + } + + @Override + public BlockLightSectionStorage.BlockDataLayerStorageMap copy() { +- return new BlockLightSectionStorage.BlockDataLayerStorageMap(this.map.clone()); ++ return new BlockDataLayerStorageMap(this.data, true); // Paper - avoid copying light data + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +index 01ae1c811862f56317a90e3811fe2ef4b593695f..4c9041f1c1cb4b3ec114fbd0c5d4db50a6f2526d 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java ++++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +@@ -9,10 +9,23 @@ public abstract class DataLayerStorageMap> { + private final long[] lastSectionKeys = new long[2]; + private final DataLayer[] lastSections = new DataLayer[2]; + private boolean cacheEnabled; +- protected final Long2ObjectOpenHashMap map; +- +- protected DataLayerStorageMap(Long2ObjectOpenHashMap arrays) { +- this.map = arrays; ++ protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data; // Paper - avoid copying light data ++ protected final boolean isVisible; // Paper - avoid copying light data ++ java.util.function.Function lookup; // Paper - faster branchless lookup ++ ++ // Paper start - avoid copying light data ++ protected DataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, boolean isVisible) { ++ if (isVisible) { ++ data.performUpdatesLockMap(); ++ } ++ this.data = data; ++ this.isVisible = isVisible; ++ if (isVisible) { ++ lookup = data::getVisibleAsync; ++ } else { ++ lookup = data::getUpdating; ++ } ++ // Paper end - avoid copying light data + this.clearCache(); + this.cacheEnabled = true; + } +@@ -20,16 +33,17 @@ public abstract class DataLayerStorageMap> { + public abstract M copy(); + + public void copyDataLayer(long pos) { +- this.map.put(pos, ((DataLayer) this.map.get(pos)).copy()); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data + this.clearCache(); + } + + public boolean hasLayer(long chunkPos) { +- return this.map.containsKey(chunkPos); ++ return lookup.apply(chunkPos) != null; // Paper - avoid copying light data + } + + @Nullable +- public DataLayer getLayer(long chunkPos) { ++ public final DataLayer getLayer(long chunkPos) { // Paper - final + if (this.cacheEnabled) { + for (int j = 0; j < 2; ++j) { + if (chunkPos == this.lastSectionKeys[j]) { +@@ -38,7 +52,7 @@ public abstract class DataLayerStorageMap> { + } + } + +- DataLayer nibblearray = (DataLayer) this.map.get(chunkPos); ++ DataLayer nibblearray = lookup.apply(chunkPos); // Paper - avoid copying light data + + if (nibblearray == null) { + return null; +@@ -59,11 +73,13 @@ public abstract class DataLayerStorageMap> { + + @Nullable + public DataLayer removeLayer(long chunkPos) { +- return (DataLayer) this.map.remove(chunkPos); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ return (DataLayer) this.data.queueRemove(chunkPos); // Paper - avoid copying light data + } + + public void setLayer(long pos, DataLayer data) { +- this.map.put(pos, data); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ this.data.queueUpdate(pos, data); // Paper - avoid copying light data + } + + public void clearCache() { +@@ -71,7 +87,6 @@ public abstract class DataLayerStorageMap> { + this.lastSectionKeys[i] = Long.MAX_VALUE; + this.lastSections[i] = null; + } +- + } + + public void disableCache() { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +index 45be10a0d7c26e4b6e5738ba994ce343265210f5..177dae992d13674bb285a60b8427df9ea843dc99 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +@@ -26,8 +26,8 @@ public abstract class LayerLightSectionStorage> + protected final LongSet dataSectionSet = new LongOpenHashSet(); + protected final LongSet toMarkNoData = new LongOpenHashSet(); + protected final LongSet toMarkData = new LongOpenHashSet(); +- protected volatile M visibleSectionData; +- protected final M updatingSectionData; ++ protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change ++ protected final M updatingSectionData; // Paper - diff on change, should be "updating" + protected final LongSet changedSections = new LongOpenHashSet(); + protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); + protected final Long2ObjectMap queuedSections = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); +@@ -41,8 +41,8 @@ public abstract class LayerLightSectionStorage> + this.layer = lightType; + this.chunkSource = chunkProvider; + this.updatingSectionData = lightData; +- this.visibleSectionData = lightData.copy(); +- this.visibleSectionData.disableCache(); ++ this.e_visible = lightData.copy(); // Paper - avoid copying light data ++ this.e_visible.disableCache(); // Paper - avoid copying light data + } + + protected boolean storingLightForSection(long sectionPos) { +@@ -51,7 +51,15 @@ public abstract class LayerLightSectionStorage> + + @Nullable + protected DataLayer getDataLayer(long sectionPos, boolean cached) { +- return this.getDataLayer(cached ? this.updatingSectionData : this.visibleSectionData, sectionPos); ++ // Paper start - avoid copying light data ++ if (cached) { ++ return this.getDataLayer(this.updatingSectionData, sectionPos); ++ } else { ++ synchronized (this.visibleUpdateLock) { ++ return this.getDataLayer(this.e_visible, sectionPos); ++ } ++ } ++ // Paper end - avoid copying light data + } + + @Nullable +@@ -364,10 +372,12 @@ public abstract class LayerLightSectionStorage> + + protected void swapSectionMap() { + if (!this.changedSections.isEmpty()) { ++ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + M m0 = this.updatingSectionData.copy(); + + m0.disableCache(); +- this.visibleSectionData = m0; ++ this.e_visible = m0; // Paper - avoid copying light data ++ } // Paper - avoid copying light data + this.changedSections.clear(); + } + +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +index c304637ae8f80c65b58e8ba8a27609b532bb1184..410fcfa8c01b7e3d3e3829ebdb92a11badff16ea 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +@@ -23,15 +23,16 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage(), new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Paper - avoid copying light data + } + + @Override + protected int getLightValue(long blockPos) { + long j = SectionPos.blockToSection(blockPos); + int k = SectionPos.y(j); +- SkyLightSectionStorage.SkyDataLayerStorageMap lightenginestoragesky_a = (SkyLightSectionStorage.SkyDataLayerStorageMap) this.visibleSectionData; +- int l = lightenginestoragesky_a.topSections.get(SectionPos.getZeroNode(j)); ++ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data ++ SkyLightSectionStorage.SkyDataLayerStorageMap lightenginestoragesky_a = (SkyLightSectionStorage.SkyDataLayerStorageMap) this.e_visible; // Paper - avoid copying light data - must be after lock acquire ++ int l = lightenginestoragesky_a.otherData.getVisibleAsync(SectionPos.getZeroNode(j)); // Paper - avoid copying light data + + if (l != lightenginestoragesky_a.currentLowestY && k < l) { + DataLayer nibblearray = this.getDataLayer(lightenginestoragesky_a, j); // Paper - decompile fix +@@ -52,6 +53,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage j) { + ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).currentLowestY = j; +- ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).topSections.defaultReturnValue(((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).currentLowestY); ++ ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).otherData.queueDefaultReturnValue(((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).currentLowestY); // Paper - avoid copying light data + } + + long k = SectionPos.getZeroNode(sectionPos); +- int l = ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).topSections.get(k); ++ int l = ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).otherData.getUpdating(k); // Paper - avoid copying light data + + if (l < j + 1) { +- ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).topSections.put(k, j + 1); ++ ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).otherData.queueUpdate(k, j + 1); // Paper - avoid copying light data + if (this.columnsWithSkySources.contains(k)) { + this.queueAddSource(sectionPos); + if (l > ((SkyLightSectionStorage.SkyDataLayerStorageMap) this.updatingSectionData).currentLowestY) { +@@ -107,7 +109,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage= k; + } +@@ -327,18 +329,21 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage { + + private int currentLowestY; +- private final Long2IntOpenHashMap topSections; +- +- public SkyDataLayerStorageMap(Long2ObjectOpenHashMap arrays, Long2IntOpenHashMap columnToTopSection, int minSectionY) { +- super(arrays); +- this.topSections = columnToTopSection; +- columnToTopSection.defaultReturnValue(minSectionY); +- this.currentLowestY = minSectionY; ++ private final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData; // Paper - avoid copying light data ++ ++ // Paper start - avoid copying light data ++ public SkyDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData, int i, boolean isVisible) { ++ super(data, isVisible); ++ this.otherData = otherData; ++ otherData.queueDefaultReturnValue(i); ++ // Paper end - avoid copying light data ++ this.currentLowestY = i; + } + + @Override + public SkyLightSectionStorage.SkyDataLayerStorageMap copy() { +- return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.map.clone(), this.topSections.clone(), this.currentLowestY); ++ this.otherData.performUpdatesLockMap(); // Paper - avoid copying light data ++ return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.data, this.otherData, this.currentLowestY, true); // Paper - avoid copying light data + } + } + } diff --git a/Remapped-Spigot-Server-Patches/0468-No-Tick-view-distance-implementation.patch b/Remapped-Spigot-Server-Patches/0468-No-Tick-view-distance-implementation.patch new file mode 100644 index 000000000..9fee42c2d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0468-No-Tick-view-distance-implementation.patch @@ -0,0 +1,819 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 21:23:34 -0700 +Subject: [PATCH] No-Tick view distance implementation + +Implements world view distance getters/setters + +Per-Player is absent due to difficulty of maintaining +the diff required to make it happen. + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 5bcf9cefc29eb20e2cfbfb49e2b2662ec394a87e..a696474b4bd0e32a26dadfbc1257580ee596f0c0 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -156,7 +156,8 @@ public class TimingsExport extends Thread { + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +- pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()) ++ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()), ++ pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance()) + )); + })); + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 46ac6d91422423f1e03b86d3efa3241f2599000d..6463d3e4837d032a35654a035f42b8a805e0e286 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -632,4 +632,9 @@ public class PaperWorldConfig { + phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative); + phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs); + } ++ ++ public int noTickViewDistance; ++ private void viewDistance() { ++ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 9abef8550a89df5e15ac28de1a5549d064f29122..d18497a33dc53f6b465e659967bf8c98731c46c0 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -638,7 +638,8 @@ public final class MCUtil { + }); + + worldData.addProperty("name", world.getWorld().getName()); +- worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()); ++ worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.getRawNoTickViewDistance()); + worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); + worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); + worldData.addProperty("visible-chunk-count", visibleChunks.size()); +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 0147798c0285f64b8d767dfb2709d92f66ac72ef..9ebcfca10071cc42d4f1df02c25de5042c065f38 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -81,6 +81,18 @@ public class ChunkHolder { + } + // Paper end - optimise isOutsideOfRange + ++ // Paper start - no-tick view distance ++ public final LevelChunk getSendingChunk() { ++ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used ++ // in Chunk's neighbour callback ++ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); ++ if (ret != null && ret.areNeighboursLoaded(1)) { ++ return ret; ++ } ++ return null; ++ } ++ // Paper end - no-tick view distance ++ + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +@@ -240,7 +252,7 @@ public class ChunkHolder { + } + + public void blockChanged(BlockPos blockposition) { +- LevelChunk chunk = this.getTickingChunk(); ++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + byte b0 = (byte) SectionPos.blockToSectionCoord(blockposition.getY()); +@@ -256,7 +268,7 @@ public class ChunkHolder { + } + + public void sectionLightChanged(LightLayer type, int y) { +- LevelChunk chunk = this.getTickingChunk(); ++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + chunk.setUnsaved(true); +@@ -338,9 +350,48 @@ public class ChunkHolder { + } + + private void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { +- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> { +- entityplayer.connection.send(packet); +- }); ++ // Paper start - per player view distance ++ // there can be potential desync with player's last mapped section and the view distance map, so use the ++ // view distance map here. ++ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos); ++ if (players == null) { ++ return; ++ } ++ ++ if (onlyOnWatchDistanceEdge) { // flag -> border only ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ ++ int viewDistance = viewDistanceMap.getLastViewDistance(player); ++ long lastPosition = viewDistanceMap.getLastCoordinate(player); ++ ++ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - this.pos.x); ++ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - this.pos.z); ++ ++ if (Math.max(distX, distZ) == viewDistance) { ++ player.connection.send(packet); ++ } ++ } ++ } else { ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ player.connection.send(packet); ++ } ++ } ++ ++ return; ++ // Paper end - per player view distance + } + + public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 56ca469bf930bcced88efdafc78f464c756a5be9..6b51a082cf42bc3ffc550614e385d3956c5f2efb 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -57,12 +57,14 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; + import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; ++import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; + import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; + import net.minecraft.network.protocol.game.DebugPackets; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.ChunkProgressListener; ++import net.minecraft.server.network.ServerGamePacketListenerImpl; + import net.minecraft.util.CsvOutput; + import net.minecraft.util.Mth; + import net.minecraft.util.profiling.ProfilerFiller; +@@ -144,7 +146,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private boolean modified; + private final ChunkTaskPriorityQueueSorter queueSorter; + private final ProcessorHandle> worldgenMailbox; +- private final ProcessorHandle> mainThreadMailbox; ++ public final ProcessorHandle> mainThreadMailbox; // Paper - private -> public ++ // Paper start ++ final ProcessorHandle> mailboxLight; ++ public void addLightTask(ChunkHolder playerchunk, Runnable run) { ++ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run)); ++ } ++ // Paper end + public final ChunkProgressListener progressListener; + public final ChunkMap.ChunkDistanceManager distanceManager; public final DistanceManager getChunkDistanceManager() { return this.distanceManager; } // Paper - OBFHELPER + private final AtomicInteger tickingGenerated; +@@ -219,6 +227,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int noTickViewDistance; ++ public final int getRawNoTickViewDistance() { ++ return this.noTickViewDistance; ++ } ++ public final int getEffectiveNoTickViewDistance() { ++ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance; ++ } ++ public final int getLoadViewDistance() { ++ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance()); ++ } ++ ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap; ++ // Paper end - no-tick view distance + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); +@@ -235,6 +259,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int effectiveTickViewDistance = this.getEffectiveViewDistance(); ++ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); ++ ++ if (!this.cannotLoadChunks(player)) { ++ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance); ++ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) ++ } ++ ++ player.needsChunkCenterUpdate = true; ++ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured ++ player.needsChunkCenterUpdate = false; ++ // Paper end - no-tick view distance + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +@@ -247,6 +284,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ this.playerViewDistanceBroadcastMap.remove(player); ++ this.playerViewDistanceTickMap.remove(player); ++ this.playerViewDistanceNoTickMap.remove(player); ++ // Paper end - no-tick view distance + } + + void updateMaps(ServerPlayer player) { +@@ -264,6 +306,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int effectiveTickViewDistance = this.getEffectiveViewDistance(); ++ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); ++ ++ if (!this.cannotLoadChunks(player)) { ++ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance); ++ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) ++ } ++ ++ player.needsChunkCenterUpdate = true; ++ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured ++ player.needsChunkCenterUpdate = false; ++ // Paper end - no-tick view distance + } + // Paper end + +@@ -371,6 +426,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + }); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance); ++ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (newState.size() != 1) { ++ return; ++ } ++ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); ++ if (chunk == null || !chunk.areNeighboursLoaded(2)) { ++ return; ++ } ++ ++ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); ++ ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (newState != null) { ++ return; ++ } ++ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); ++ ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update ++ }); ++ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (player.needsChunkCenterUpdate) { ++ player.needsChunkCenterUpdate = false; ++ player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ)); ++ } ++ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded ++ }); ++ // Paper end - no-tick view distance + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -1199,15 +1293,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + completablefuture1.thenAcceptAsync((either) -> { + either.mapLeft((chunk) -> { + this.tickingGenerated.getAndIncrement(); +- Packet[] apacket = new Packet[2]; +- +- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { +- this.playerLoadedChunk(entityplayer, apacket, chunk); +- }); ++ // Paper - no-tick view distance - moved to Chunk neighbour update + return Either.left(chunk); + }); + }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); ++ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again) + }); + return completablefuture1; + } +@@ -1302,32 +1392,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + +- protected void setViewDistance(int watchDistance) { +- int j = Mth.clamp(watchDistance + 1, 3, 33); ++ public void setViewDistance(int watchDistance) { // Paper - public ++ int j = Mth.clamp(watchDistance + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 + + if (j != this.viewDistance) { + int k = this.viewDistance; + + this.viewDistance = j; +- this.distanceManager.updatePlayerTickets(this.viewDistance); +- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator(); ++ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending ++ } + +- while (objectiterator.hasNext()) { +- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); +- ChunkPos chunkcoordintpair = playerchunk.getPos(); +- Packet[] apacket = new Packet[2]; ++ } + +- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { +- int l = checkerboardDistance(chunkcoordintpair, entityplayer, true); +- boolean flag = l <= k; +- boolean flag1 = l <= this.viewDistance; ++ // Paper start - no-tick view distance ++ public final void setNoTickViewDistance(int viewDistance) { ++ viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32); + +- this.updateChunkTracking(entityplayer, chunkcoordintpair, apacket, flag, flag1); +- }); ++ this.noTickViewDistance = viewDistance; ++ int loadViewDistance = this.getLoadViewDistance(); ++ this.distanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2 ++ ++ if (this.level != null && this.level.players != null) { // this can be called from constructor, where these aren't set ++ for (ServerPlayer player : this.level.players) { ++ ServerGamePacketListenerImpl connection = player.connection; ++ if (connection != null) { ++ // moved in from PlayerList ++ connection.send(new ClientboundSetChunkCacheRadiusPacket(loadViewDistance)); ++ } ++ this.updateMaps(player); + } + } +- + } ++ // Paper end - no-tick view distance + + protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, Packet[] packets, boolean withinMaxWatchDistance, boolean withinViewDistance) { + if (player.level == this.level) { +@@ -1335,7 +1431,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); + + if (playerchunk != null) { +- LevelChunk chunk = playerchunk.getTickingChunk(); ++ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + this.playerLoadedChunk(player, packets, chunk); +@@ -1596,6 +1692,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end - optimise isOutsideOfRange + ++ private boolean cannotLoadChunks(ServerPlayer entityplayer) { return this.skipPlayer(entityplayer); } // Paper - OBFHELPER + private boolean skipPlayer(ServerPlayer player) { + return player.isSpectator() && !this.level.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); + } +@@ -1623,13 +1720,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.removePlayerFromDistanceMaps(player); // Paper - distance maps + } + +- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { +- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) { +- ChunkPos chunkcoordintpair = new ChunkPos(k, l); +- +- this.updateChunkTracking(player, chunkcoordintpair, new Packet[2], !added, added); +- } +- } ++ // Paper - broadcast view distance map handles this (see remove/add calls above) + + } + +@@ -1637,7 +1728,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + SectionPos sectionposition = SectionPos.of((Entity) entityplayer); + + entityplayer.setLastSectionPos(sectionposition); +- entityplayer.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); ++ // Paper - distance map handles this now + return sectionposition; + } + +@@ -1682,6 +1773,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int k1; + int l1; + ++ /* // Paper start - replaced by distance map + if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) { + k1 = Math.min(i, i1) - this.viewDistance; + l1 = Math.min(j, j1) - this.viewDistance; +@@ -1690,36 +1782,36 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + for (int k2 = k1; k2 <= i2; ++k2) { + for (int l2 = l1; l2 <= j2; ++l2) { +- ChunkPos chunkcoordintpair = new ChunkPos(k2, l2); +- boolean flag3 = checkerboardDistance(chunkcoordintpair, i1, j1) <= this.viewDistance; +- boolean flag4 = checkerboardDistance(chunkcoordintpair, i, j) <= this.viewDistance; ++ ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k2, l2); ++ boolean flag3 = a(chunkcoordintpair, i1, j1) <= this.viewDistance; ++ boolean flag4 = a(chunkcoordintpair, i, j) <= this.viewDistance; + +- this.updateChunkTracking(player, chunkcoordintpair, new Packet[2], flag3, flag4); ++ this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], flag3, flag4); + } + } + } else { +- ChunkPos chunkcoordintpair1; ++ ChunkCoordIntPair chunkcoordintpair1; + boolean flag5; + boolean flag6; + + for (k1 = i1 - this.viewDistance; k1 <= i1 + this.viewDistance; ++k1) { + for (l1 = j1 - this.viewDistance; l1 <= j1 + this.viewDistance; ++l1) { +- chunkcoordintpair1 = new ChunkPos(k1, l1); ++ chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1); + flag5 = true; + flag6 = false; +- this.updateChunkTracking(player, chunkcoordintpair1, new Packet[2], true, false); ++ this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], true, false); + } + } + + for (k1 = i - this.viewDistance; k1 <= i + this.viewDistance; ++k1) { + for (l1 = j - this.viewDistance; l1 <= j + this.viewDistance; ++l1) { +- chunkcoordintpair1 = new ChunkPos(k1, l1); ++ chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1); + flag5 = false; + flag6 = true; +- this.updateChunkTracking(player, chunkcoordintpair1, new Packet[2], false, true); ++ this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], false, true); + } + } +- } ++ }*/ // Paper end - replaced by distance map + + this.updateMaps(player); // Paper - distance maps + +@@ -1727,11 +1819,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Override + public Stream getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { +- return this.playerMap.a(chunkPos.toLong()).filter((entityplayer) -> { +- int i = b(chunkcoordintpair, entityplayer, true); ++ // Paper start - per player view distance ++ // there can be potential desync with player's last mapped section and the view distance map, so use the ++ // view distance map here. ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkPos); + +- return i > this.viewDistance ? false : !flag || i == this.viewDistance; +- }); ++ if (inRange == null) { ++ return Stream.empty(); ++ } ++ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it ++ List players = new java.util.ArrayList<>(); ++ Object[] backingSet = inRange.getBackingSet(); ++ ++ if (onlyOnWatchDistanceEdge) { // flag -> border only ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player); ++ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player); ++ ++ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x); ++ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z); ++ if (Math.max(distX, distZ) == viewDistance) { ++ players.add(player); ++ } ++ } ++ } else { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ players.add(player); ++ } ++ } ++ return players.stream(); ++ // Paper end - per player view distance + } + + public void addEntity(Entity entity) { // Paper - protected -> public +@@ -1889,7 +2016,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + +- private final void sendChunk(ServerPlayer entityplayer, Packet[] apacket, LevelChunk chunk) { this.playerLoadedChunk(entityplayer, apacket, chunk); } // Paper - OBFHELPER ++ // Paper start ++ private static int getLightMask(final LevelChunk chunk) { ++ final ChunkSection[] chunkSections = chunk.getSections(); ++ int mask = 0; ++ ++ for (int i = 0; i < chunkSections.length; ++i) { ++ /* ++ ++ ++Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section. ++Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d. ++ ++ */ ++ mask |= (ChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i; ++ } ++ ++ return mask; ++ } ++ ++ private static int getCeilingLightMask(final LevelChunk chunk) { ++ int mask = getLightMask(chunk); ++ ++ /* ++ It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below. ++ We then invert this, so we'd have 110000 and compare that to the "main" chunk. ++ This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights. ++ ++ so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow. ++ @TODO: Implement Leafs suggestion ++ either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then ++ */ ++ mask |= mask >> 1; ++ mask |= mask >> 2; ++ mask |= mask >> 4; ++ mask |= mask >> 8; ++ mask |= mask >> 16; ++ ++ return mask; ++ } ++ // Paper end ++ ++ public final void sendChunk(ServerPlayer entityplayer, Packet[] apacket, LevelChunk chunk) { this.playerLoadedChunk(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void playerLoadedChunk(ServerPlayer player, Packet[] packets, LevelChunk chunk) { + if (packets[0] == null) { + packets[0] = new ClientboundLevelChunkPacket(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(player, chunk, 65535)); // Paper - Anti-Xray - Bypass +@@ -2075,7 +2243,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ChunkPos chunkcoordintpair = new ChunkPos(this.entity.xChunk, this.entity.zChunk); + ChunkHolder playerchunk = ChunkMap.this.getVisibleChunkIfPresent(chunkcoordintpair.toLong()); + +- if (playerchunk != null && playerchunk.getTickingChunk() != null) { ++ if (playerchunk != null && playerchunk.getSendingChunk() != null) { // Paper - no-tick view distance + flag1 = ChunkMap.checkerboardDistance(chunkcoordintpair, player, false) <= ChunkMap.this.viewDistance; + } + } +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 91c672531087430c47365657a3219ab5980d3467..c9b4025f6c3d1be7bca2ff7337dd86e37d21b53e 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -269,8 +269,8 @@ public abstract class DistanceManager { + return s; + } + +- protected void updatePlayerTickets(int viewDistance) { +- this.playerTicketManager.updateViewDistance(viewDistance); ++ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change ++ this.playerTicketManager.updateViewDistance(i); + } + + public int getNaturalSpawnChunkCount() { +@@ -388,7 +388,7 @@ public abstract class DistanceManager { + + private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) { + if (oldWithinViewDistance != withinViewDistance) { +- Ticket ticket = new Ticket<>(TicketType.PLAYER, DistanceManager.PLAYER_TICKET_LEVEL, new ChunkPos(pos)); ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance + + if (withinViewDistance) { + DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 95f1f4727a8e2000931e6f36b862e3ad28334a69..8e4cef60b760be385df81a74834d026f856a78c5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -249,6 +249,8 @@ public class ServerPlayer extends Player implements ContainerListener { + + double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + ++ boolean needsChunkCenterUpdate; // Paper - no-tick view distance ++ + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { + super(world, world.getSpawn(), world.getSharedSpawnAngle(), profile); + this.respawnDimension = Level.OVERWORLD; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ffc8c9ee8b1768dd809189858ee45658fb9bf1c5..8e00747c1a717836d12a43aa48d667bf801167b0 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -250,7 +250,7 @@ public abstract class PlayerList { + boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); + + // Spigot - view distance +- playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); ++ playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName()))); + playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); +@@ -904,7 +904,7 @@ public abstract class PlayerList { + // CraftBukkit start + LevelData worlddata = worldserver1.getLevelData(); + entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag)); +- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot ++ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance + entityplayer1.setLevel(worldserver1); + entityplayer1.removed = false; + entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.yRot, entityplayer1.xRot)); +@@ -1372,7 +1372,7 @@ public abstract class PlayerList { + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; +- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); ++ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5860e7866724abd35bde2a5710d9c92799e5de67..67ab681a9c9157a420de5fd872bde1fc0de24561 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -525,8 +525,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.setBlocksDirty(blockposition, iblockdata1, iblockdata2); + } + +- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement ++ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below + this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); ++ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance ++ // if copied from above ++ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { ++ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); ++ // Paper end - per player view distance + } + + if ((i & 1) != 0) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 8a14bdda4a408ec1e2b51efeb35467835f62b42c..dbea2a4370ccf24a5084cdabeecbc81f206e910a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -27,9 +27,14 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.network.protocol.Packet; + import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.TicketType; + import net.minecraft.util.Mth; + import net.minecraft.world.Container; + import net.minecraft.world.entity.Entity; +@@ -242,7 +247,51 @@ public class LevelChunk implements ChunkAccess { + } + + protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { ++ // Paper start - no-tick view distance ++ ServerChunkCache chunkProviderServer = ((ServerLevel)this.world).getChunkSource(); ++ ChunkMap chunkMap = chunkProviderServer.chunkMap; ++ // this code handles the addition of ticking tickets - the distance map handles the removal ++ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { ++ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) { ++ // now we're ready for entity ticking ++ chunkProviderServer.mainThreadProcessor.execute(() -> { ++ // double check that this condition still holds. ++ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { ++ chunkProviderServer.addTicketAtLevel(TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update ++ } ++ }); ++ } ++ } + ++ // this code handles the chunk sending ++ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { ++ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) { ++ // now we're ready to send ++ chunkMap.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(chunkMap.getUpdatingChunkIfPresent(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap ++ // double check that this condition still holds. ++ if (!LevelChunk.this.areNeighboursLoaded(1)) { ++ return; ++ } ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(LevelChunk.this.coordinateKey); ++ if (inRange == null) { ++ return; ++ } ++ ++ // broadcast ++ Object[] backingSet = inRange.getBackingSet(); ++ Packet[] chunkPackets = new Packet[2]; ++ for (int index = 0, len = backingSet.length; index < len; ++index) { ++ Object temp = backingSet[index]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ chunkMap.sendChunk(player, chunkPackets, LevelChunk.this); ++ } ++ }))); ++ } ++ } ++ // Paper end - no-tick view distance + } + + public final boolean isAnyNeighborsLoaded() { +@@ -1131,7 +1180,7 @@ public class LevelChunk implements ChunkAccess { + BlockState iblockdata = this.getBlockState(blockposition); + BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, (LevelAccessor) this.world, blockposition); + +- this.world.setBlock(blockposition, iblockdata1, 20); ++ this.world.setBlock(blockposition, iblockdata1, 20 | 2); // Paper - We send chunks before they're ticking ready, so we need to notify here + } + + this.postProcessing[i].clear(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 1a839242e359fa32f32d0e571c6e918ac39642e9..4fc44390f432ef13c9952aa22bbb29bc8bf47975 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -31,6 +31,7 @@ import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; + import net.minecraft.network.protocol.game.ClientboundSetTimePacket; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.DistanceManager; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.Ticket; +@@ -2532,10 +2533,39 @@ public class CraftWorld implements World { + // Spigot start + @Override + public int getViewDistance() { +- return world.spigotConfig.viewDistance; ++ return getHandle().getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance + } + // Spigot end + ++ // Paper start - per player view distance ++ @Override ++ public void setViewDistance(int viewDistance) { ++ if (viewDistance < 2 || viewDistance > 32) { ++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); ++ } ++ ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; ++ if (viewDistance != chunkMap.getEffectiveViewDistance()) { ++ chunkMap.setViewDistance(viewDistance); ++ } ++ } ++ ++ @Override ++ public int getNoTickViewDistance() { ++ return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance(); ++ } ++ ++ @Override ++ public void setNoTickViewDistance(int viewDistance) { ++ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) { ++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); ++ } ++ ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; ++ if (viewDistance != chunkMap.getRawNoTickViewDistance()) { ++ chunkMap.setNoTickViewDistance(viewDistance); ++ } ++ } ++ // Paper end - per player view distance ++ + // Spigot start + private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot() + { +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 8cbafad53d20366a36493f22160c4fa3e4ac3eaf..20d5da61fc0594e86c68ea8fb5ebe5517f27f126 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -4,6 +4,7 @@ import java.util.Collection; + import net.minecraft.core.BlockPos; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.FlyingMob; +@@ -192,7 +193,7 @@ public class ActivationRange + maxRange = Math.max( maxRange, waterActivationRange ); + maxRange = Math.max( maxRange, villagerActivationRange ); + // Paper end +- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); ++ maxRange = Math.min( ( ((ServerLevel)world).getChunkSource().chunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance + + for ( Player player : world.players() ) + { diff --git a/Remapped-Spigot-Server-Patches/0469-Add-villager-reputation-API.patch b/Remapped-Spigot-Server-Patches/0469-Add-villager-reputation-API.patch new file mode 100644 index 000000000..771a37b8c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0469-Add-villager-reputation-API.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 22 Apr 2020 23:29:20 +0200 +Subject: [PATCH] Add villager reputation API + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c6072615e95bf51c83b2f728fc3288a7043a89af +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java +@@ -0,0 +1,11 @@ ++package com.destroystokyo.paper.entity.villager; ++// Must have own package due to package-level constructor. ++ ++import Reputation; ++ ++public final class ReputationConstructor { ++ // Abuse the package-level constructor. ++ public static Reputation construct(int[] values) { ++ return new Reputation(values); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index c4ece3ac4863067b12c10772debd1b1454bec5b4..0204f05d989d45c0848f810d1953adf0992ce3c2 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -27,7 +27,7 @@ import net.minecraft.core.SerializableUUID; + + public class GossipContainer { + +- private final Map gossips = Maps.newHashMap(); ++ private final Map gossips = Maps.newHashMap(); public Map getReputations() { return this.gossips; } // Paper - add getter for reputations + + public GossipContainer() {} + +@@ -142,11 +142,11 @@ public class GossipContainer { + return k > type.max ? Math.max(type.max, left) : k; + } + +- static class EntityGossips { ++ public static class EntityGossips { // Paper - make public + + private final Object2IntMap entries; + +- private EntityGossips() { ++ public EntityGossips() { // Paper - make public - update CraftVillager setReputation on change + this.entries = new Object2IntOpenHashMap(); + } + +@@ -200,6 +200,28 @@ public class GossipContainer { + public void remove(GossipType gossipType) { + this.entries.removeInt(gossipType); + } ++ ++ // Paper start - Add villager reputation API ++ private static final com.destroystokyo.paper.entity.villager.ReputationType[] REPUTATION_TYPES = com.destroystokyo.paper.entity.villager.ReputationType.values(); ++ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() { ++ int[] reputation = new int[REPUTATION_TYPES.length]; ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.TRADING.ordinal()] = entries.getOrDefault(GossipType.TRADING, 0); ++ return com.destroystokyo.paper.entity.villager.ReputationConstructor.construct(reputation); ++ } ++ ++ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) { ++ int val; ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE)) != 0) this.entries.put(GossipType.MAJOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE)) != 0) this.entries.put(GossipType.MAJOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE)) != 0) this.entries.put(GossipType.MINOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE)) != 0) this.entries.put(GossipType.MINOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.TRADING)) != 0) this.entries.put(GossipType.TRADING, val); ++ } ++ // Paper end + } + + static class GossipEntry { +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index a83a7d37f3d769535161fda46fca6f71dcc4d515..e9912551e6a19d6ad3b20fad1b716577b9d28f99 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -1037,6 +1037,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.numberOfRestocksToday = 0; + } + ++ public GossipContainer getReputation() { return this.getGossips(); } // Paper - OBFHELPER + public GossipContainer getGossips() { + return this.gossips; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index d0b933cfd02b237bfe85011831dab6e8e966496e..e3d4214ef6360b4a9949a73ba3d665ad08733b43 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -16,6 +16,13 @@ import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; + import org.bukkit.entity.Villager.Type; + ++// Paper start ++import com.destroystokyo.paper.entity.villager.Reputation; ++import com.google.common.collect.Maps; ++import java.util.Map; ++import java.util.UUID; ++// Paper end ++ + public class CraftVillager extends CraftAbstractVillager implements Villager { + + public CraftVillager(CraftServer server, net.minecraft.world.entity.npc.Villager entity) { +@@ -125,4 +132,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + public static VillagerProfession bukkitToNmsProfession(Profession bukkit) { + return Registry.VILLAGER_PROFESSION.get(CraftNamespacedKey.toMinecraft(bukkit.getKey())); + } ++ ++ // Paper start - Add villager reputation API ++ @Override ++ public Reputation getReputation(UUID uniqueId) { ++ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips rep = getHandle().getReputation().getReputations().get(uniqueId); ++ if (rep == null) { ++ return new Reputation(Maps.newHashMap()); ++ } ++ ++ return rep.getPaperReputation(); ++ } ++ ++ @Override ++ public Map getReputations() { ++ return getHandle().getReputation().getReputations().entrySet() ++ .stream() ++ .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getPaperReputation())); ++ } ++ ++ @Override ++ public void setReputation(UUID uniqueId, Reputation reputation) { ++ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips nmsReputation = ++ getHandle().getReputation().getReputations().computeIfAbsent( ++ uniqueId, ++ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips() ++ ); ++ nmsReputation.assignFromPaperReputation(reputation); ++ } ++ ++ @Override ++ public void setReputations(Map reputations) { ++ for (Map.Entry entry : reputations.entrySet()) { ++ setReputation(entry.getKey(), entry.getValue()); ++ } ++ } ++ ++ @Override ++ public void clearReputations() { ++ getHandle().getReputation().getReputations().clear(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0470-Fix-Light-Command.patch b/Remapped-Spigot-Server-Patches/0470-Fix-Light-Command.patch new file mode 100644 index 000000000..382b91a80 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0470-Fix-Light-Command.patch @@ -0,0 +1,182 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 7 May 2020 19:17:36 -0400 +Subject: [PATCH] Fix Light Command + +This lets you run /paper fixlight (max 5) to automatically +fix all light data in the chunks. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index ff718bc7f521575e6a670e17fcf59a2d30841705..528c860fc0c04431e0ebb2ae6bc96bf9c2d04789 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -11,16 +11,20 @@ import com.google.common.collect.Maps; + import com.google.gson.JsonObject; + import com.google.gson.internal.Streams; + import com.google.gson.stream.JsonWriter; ++import net.minecraft.core.BlockPos; ++import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ThreadedLevelLightEngine; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.level.ChunkPos; +-import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.chunk.LevelChunk; + import org.apache.commons.lang3.tuple.MutablePair; + import org.apache.commons.lang3.tuple.Pair; + import org.bukkit.Bukkit; +@@ -31,6 +35,7 @@ import org.bukkit.command.Command; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.entity.Player; + + import java.io.File; +@@ -39,10 +44,12 @@ import java.io.PrintStream; + import java.io.StringWriter; + import java.time.LocalDateTime; + import java.time.format.DateTimeFormatter; ++import java.util.ArrayDeque; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; + import java.util.Collections; ++import java.util.Deque; + import java.util.Iterator; + import java.util.List; + import java.util.Locale; +@@ -52,7 +59,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight").build(); + + public PaperCommand(String name) { + super(name); +@@ -173,6 +180,9 @@ public class PaperCommand extends Command { + case "syncloadinfo": + this.doSyncLoadInfo(sender, args); + break; ++ case "fixlight": ++ this.doFixLight(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -190,6 +200,77 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doFixLight(CommandSender sender, String[] args) { ++ if (!(sender instanceof Player)) { ++ sender.sendMessage("Only players can use this command"); ++ return; ++ } ++ int radius = 2; ++ if (args.length > 1) { ++ try { ++ radius = Math.min(5, Integer.parseInt(args[1])); ++ } catch (Exception e) { ++ sender.sendMessage("Not a number"); ++ return; ++ } ++ ++ } ++ ++ CraftPlayer player = (CraftPlayer) sender; ++ ServerPlayer handle = player.getHandle(); ++ ServerLevel world = (ServerLevel) handle.level; ++ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ ++ BlockPos center = MCUtil.toBlockPosition(player.getLocation()); ++ Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); ++ updateLight(sender, world, lightengine, queue); ++ } ++ ++ private void updateLight(CommandSender sender, ServerLevel world, ThreadedLevelLightEngine lightengine, Deque queue) { ++ ChunkPos coord = queue.poll(); ++ if (coord == null) { ++ sender.sendMessage("All Chunks Light updated"); ++ return; ++ } ++ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { ++ if (ex != null) { ++ sender.sendMessage("Error loading chunk " + coord); ++ updateLight(sender, world, lightengine, queue); ++ return; ++ } ++ LevelChunk chunk = (LevelChunk) either.left().orElse(null); ++ if (chunk == null) { ++ updateLight(sender, world, lightengine, queue); ++ return; ++ } ++ lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue ++ sender.sendMessage("Updating Light " + coord); ++ int cx = chunk.getPos().x << 4; ++ int cz = chunk.getPos().z << 4; ++ for (int y = 0; y < world.getHeight(); y++) { ++ for (int x = 0; x < 16; x++) { ++ for (int z = 0; z < 16; z++) { ++ BlockPos pos = new BlockPos(cx + x, y, cz + z); ++ lightengine.checkBlock(pos); ++ } ++ } ++ } ++ lightengine.tryScheduleUpdate(); ++ ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); ++ if (visibleChunk != null) { ++ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { ++ MinecraftServer.getServer().processQueue.add(() -> { ++ visibleChunk.sendPacketToTrackedPlayers(new ClientboundLightUpdatePacket(chunk.getPos(), lightengine, true), false); ++ updateLight(sender, world, lightengine, queue); ++ }); ++ }); ++ } else { ++ updateLight(sender, world, lightengine, queue); ++ } ++ lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize); ++ }, MinecraftServer.getServer()); ++ } ++ + private void doSyncLoadInfo(CommandSender sender, String[] args) { + if (!SyncLoadFinder.ENABLED) { + sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 9ebcfca10071cc42d4f1df02c25de5042c065f38..d907872d80f840b343419f49a6708082da6f921b 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -349,6 +349,7 @@ public class ChunkHolder { + + } + ++ public void sendPacketToTrackedPlayers(Packet packet, boolean flag) { broadcast(packet, flag); } // Paper - OBFHELPER + private void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { + // Paper start - per player view distance + // there can be potential desync with player's last mapped section and the view distance map, so use the +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 6b51a082cf42bc3ffc550614e385d3956c5f2efb..67f748d5955453ba4873b0c9bb741b5bfe52d655 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -344,11 +344,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ProcessorHandle mailbox = ProcessorHandle.of("main", mainThreadExecutor::tell); + + this.progressListener = worldGenerationProgressListener; +- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(workerExecutor, "light"); ++ ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(workerExecutor, "light"); // Paper + + this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), workerExecutor, Integer.MAX_VALUE); + this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); + this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); ++ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper + this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); + this.distanceManager = new ChunkMap.ChunkDistanceManager(workerExecutor, mainThreadExecutor); this.distanceManager.chunkMap = this; // Paper + this.overworldDataStorage = supplier; diff --git a/Remapped-Spigot-Server-Patches/0471-Fix-PotionEffect-ignores-icon-flag.patch b/Remapped-Spigot-Server-Patches/0471-Fix-PotionEffect-ignores-icon-flag.patch new file mode 100644 index 000000000..5968f9b0c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0471-Fix-PotionEffect-ignores-icon-flag.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Fri, 8 May 2020 00:49:18 -0400 +Subject: [PATCH] Fix PotionEffect ignores icon flag + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 5dac3bf5a117bfbf57798238f0614558deafcd1b..067eaf1e05ced344eb168431403f3fe786eafddf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -408,7 +408,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { +- getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); ++ getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } + diff --git a/Remapped-Spigot-Server-Patches/0472-Optimize-brigadier-child-sorting-performance.patch b/Remapped-Spigot-Server-Patches/0472-Optimize-brigadier-child-sorting-performance.patch new file mode 100644 index 000000000..d213e040b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0472-Optimize-brigadier-child-sorting-performance.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: virustotalop +Date: Thu, 16 Apr 2020 20:51:32 -0700 +Subject: [PATCH] Optimize brigadier child sorting performance + + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index 120234605433165d1c78986b5f0f130e64c5a20a..5c35cef42af4053332c02b4960c227fe95d4c197 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -26,7 +26,7 @@ import java.util.stream.Collectors; + import net.minecraft.commands.CommandSourceStack; + + public abstract class CommandNode implements Comparable> { +- private Map> children = Maps.newLinkedHashMap(); ++ private Map> children = Maps.newTreeMap(); //Paper - Switch to tree map for automatic sorting + private Map> literals = Maps.newLinkedHashMap(); + private Map> arguments = Maps.newLinkedHashMap(); + private final Predicate requirement; +@@ -106,8 +106,7 @@ public abstract class CommandNode implements Comparable> { + arguments.put(node.getName(), (ArgumentCommandNode) node); + } + } +- +- children = children.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); ++ //Paper - Remove manual sorting, it is no longer needed + } + + public void findAmbiguities(final AmbiguityConsumer consumer) { diff --git a/Remapped-Spigot-Server-Patches/0473-Potential-bed-API.patch b/Remapped-Spigot-Server-Patches/0473-Potential-bed-API.patch new file mode 100644 index 000000000..efd170350 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0473-Potential-bed-API.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Sun, 10 May 2020 23:06:30 -0400 +Subject: [PATCH] Potential bed API + +Adds a new method to fetch the location of a player's bed without generating any sync loads. + +getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index ae6faa331fcbefd99ee1cd92c88926d767fc50ee..878a62e04962aafeaf192075fbe08e319298a800 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -12,6 +12,7 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; + import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.MenuProvider; + import net.minecraft.world.entity.Entity; +@@ -126,6 +127,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return getHandle().sleepCounter; + } + ++ // Paper start - Potential bed api ++ @Override ++ public Location getPotentialBedLocation() { ++ ServerPlayer handle = (ServerPlayer) getHandle(); ++ BlockPos bed = handle.getRespawnPosition(); ++ if (bed == null) { ++ return null; ++ } ++ ++ ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension()); ++ if (worldServer == null) { ++ return null; ++ } ++ return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); ++ } ++ // Paper end + @Override + public boolean sleep(Location location, boolean force) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/Remapped-Spigot-Server-Patches/0474-Wait-for-Async-Tasks-during-shutdown.patch b/Remapped-Spigot-Server-Patches/0474-Wait-for-Async-Tasks-during-shutdown.patch new file mode 100644 index 000000000..dea3aa2ef --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0474-Wait-for-Async-Tasks-during-shutdown.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:16:17 -0400 +Subject: [PATCH] Wait for Async Tasks during shutdown + +Server.reload() had this logic to give time for tasks to shutdown, +however shutdown did not... + +Adds a 5 second grace period for any async tasks to finish and warns +if any are still running after that delay just as reload does. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 74f393ffa2ae2d0e25b3f0b674cef7a987e985d3..f530c739b6aee3718eb5d0e0e6a09d882d817c68 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -892,6 +892,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException e) {} ++ pollCount++; ++ } ++ ++ List overdueWorkers = getScheduler().getActiveWorkers(); ++ for (BukkitWorker worker : overdueWorkers) { ++ Plugin plugin = worker.getOwner(); ++ String author = ""; ++ if (plugin.getDescription().getAuthors().size() > 0) { ++ author = plugin.getDescription().getAuthors().get(0); ++ } ++ getLogger().log(Level.SEVERE, String.format( ++ "Nag author: '%s' of '%s' about the following: %s", ++ author, ++ plugin.getDescription().getName(), ++ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies." ++ )); ++ } ++ } ++ // Paper end ++ + @Override + public void reloadData() { + ReloadCommand.reload(console); diff --git a/Remapped-Spigot-Server-Patches/0475-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/Remapped-Spigot-Server-Patches/0475-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch new file mode 100644 index 000000000..99d8f1263 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0475-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 9 May 2020 02:01:48 -0400 +Subject: [PATCH] Ensure EntityRaider respects game and entity rules for + picking up items + + +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index 6406b0a03b67ea61083b704cd24b9b25a0f33c87..5502615be430d9eba0c1c68e3f10826d75b08672 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -523,7 +523,7 @@ public abstract class Raider extends PatrollingMonster { + + public class ObtainRaidLeaderBannerGoal extends Goal { + +- private final T mob; ++ private final T mob; private T getRaider() { return mob; } // Paper - obfhelper + + public ObtainRaidLeaderBannerGoal(T entityraider) { // CraftBukkit - decompile error + this.mob = entityraider; +@@ -532,6 +532,7 @@ public abstract class Raider extends PatrollingMonster { + + @Override + public boolean canUse() { ++ if (!getRaider().level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !getRaider().canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + Raid raid = this.mob.getCurrentRaid(); + + if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) { diff --git a/Remapped-Spigot-Server-Patches/0476-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/Remapped-Spigot-Server-Patches/0476-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 000000000..99a535566 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0476-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,173 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 May 2020 23:01:26 -0400 +Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed + +This fixes exploits that let players destroy bedrock by Pistons, explosions +and Mushrooom/Tree generation. + +These blocks are designed to not be broken except by creative players/commands. +So protect them from a multitude of methods of destroying them. + +A config is provided if you rather let players use these exploits, and let +them destroy the worlds End Portals and get on top of the nether easy. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 78271b400c79578d043b20a5389a37b1bef9a70d..5f3b0d95cc7e6a0434d78ea7305a70689c41c71c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -416,4 +416,17 @@ public class PaperConfig { + private static void midTickChunkTasks() { + midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks); + } ++ ++ public static boolean allowBlockPermanentBreakingExploits = false; ++ private static void allowBlockPermanentBreakingExploits() { ++ if (config.contains("allow-perm-block-break-exploits")) { ++ allowBlockPermanentBreakingExploits = config.getBoolean("allow-perm-block-break-exploits", false); ++ config.set("allow-perm-block-break-exploits", null); ++ } ++ ++ config.set("settings.unsupported-settings.allow-permanent-block-break-exploits-readme", "This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks."); ++ allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits); ++ ++ } ++ + } +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 667a6d645034c67639c01b8221591877bcb87b35..0f0a5fa2be5a7c69291b593a04cad83e069ba5b1 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -151,6 +151,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = new BlockPos(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper + FluidState fluid = iblockdata.getFluidState(); // Paper + Optional optional = this.damageCalculator.a(this, this.level, blockposition, iblockdata, fluid); + +@@ -304,7 +305,7 @@ public class Explosion { + BlockState iblockdata = this.level.getBlockState(blockposition); + Block block = iblockdata.getBlock(); + +- if (!iblockdata.isAir()) { ++ if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper + BlockPos blockposition1 = blockposition.immutable(); + + this.level.getProfiler().push("explosion_blocks"); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 67ab681a9c9157a420de5fd872bde1fc0de24561..9b50b8030174338c04b60d441b980131e1d593e4 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -422,6 +422,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end + CraftBlockState blockstate = capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index fca5d175cbef24fb0ee2d0bbedc8d1c0af3eb528..5b84ee4091e354c4b6500f58a31931f2a6827ffc 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -62,6 +62,19 @@ public class Block extends BlockBehaviour implements ItemLike { + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; + // Paper start ++ public final boolean isDestroyable() { ++ return com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits || ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.BARRIER && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; ++ } + public co.aikar.timings.Timing timing; + public co.aikar.timings.Timing getTiming() { + if (timing == null) { +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index dc9584a30c18d964afd9cc118c81c24a80beba63..40a18302dd682e5ade4ec77ac7f316b6c0f8c112 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -193,6 +193,12 @@ public class PistonBaseBlock extends DirectionalBlock { + @Override + public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - prevent retracting when we're facing the wrong way + + if (!world.isClientSide) { + boolean flag = this.getNeighborSignal(world, pos, enumdirection); +@@ -224,7 +230,7 @@ public class PistonBaseBlock extends DirectionalBlock { + BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); + + world.setBlock(pos, iblockdata1, 20); +- world.setBlockEntity(pos, MovingPistonBlock.newMovingBlockEntity((BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(pos, MovingPistonBlock.newMovingBlockEntity((BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change, j is facing direction - copy this above + world.blockUpdated(pos, iblockdata1.getBlock()); + iblockdata1.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -253,7 +259,14 @@ public class PistonBaseBlock extends DirectionalBlock { + } + } + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.setAir(headPos, false); ++ } else { ++ ((ServerLevel)world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - fix headless pistons breaking blocks + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 57eedaeedaa24bd274fb55c6e4521f1305382645..df2836b071158729728411f5b228cc38dddd4d4e 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -189,7 +189,7 @@ public abstract class BlockBehaviour { + + @Deprecated + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || context.getItemInHand().getItem() != this.asItem()); ++ return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || context.getItemInHand().getItem() != this.asItem()) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().abilities.instabuild)); // Paper + } + + @Deprecated +@@ -393,7 +393,11 @@ public abstract class BlockBehaviour { + public Block getBlock() { + return (Block) this.owner; + } +- ++ // Paper start ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end + public Material getMaterial() { + return this.material; + } +@@ -483,7 +487,7 @@ public abstract class BlockBehaviour { + } + + public PushReaction getPistonPushReaction() { +- return this.getBlock().getPistonPushReaction(this.asState()); ++ return !isDestroyable() ? PushReaction.BLOCK : this.getBlock().getPistonPushReaction(this.asState()); // Paper + } + + public boolean isSolidRender(BlockGetter world, BlockPos pos) { diff --git a/Remapped-Spigot-Server-Patches/0477-Optimize-NibbleArray-to-use-pooled-buffers.patch b/Remapped-Spigot-Server-Patches/0477-Optimize-NibbleArray-to-use-pooled-buffers.patch new file mode 100644 index 000000000..9cdcec040 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0477-Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 23:30:30 -0400 +Subject: [PATCH] Optimize NibbleArray to use pooled buffers + +Massively reduces memory allocation of 2048 byte buffers by using +an object pool for these. + +Uses lots of advanced new capabilities of the Paper codebase :) + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java +index bc1b4cc2e0a4181bde5ac05ce0a20a651cb0c4c3..902f14e2e5ac5aa11b545a68ac69e9b0282df7f4 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java +@@ -1,12 +1,16 @@ + package net.minecraft.network.protocol.game; + + import com.google.common.collect.Lists; ++import io.netty.channel.ChannelFuture; // Paper ++ + import java.io.IOException; + import java.util.Iterator; + import java.util.List; + import net.minecraft.core.SectionPos; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.chunk.DataLayer; +@@ -24,14 +28,43 @@ public class ClientboundLightUpdatePacket implements Packet blockUpdates; + private boolean trustEdges; + ++ // Paper start ++ java.lang.Runnable cleaner1; ++ java.lang.Runnable cleaner2; ++ java.util.concurrent.atomic.AtomicInteger remainingSends = new java.util.concurrent.atomic.AtomicInteger(0); ++ ++ @Override ++ public void onPacketDispatch(ServerPlayer player) { ++ remainingSends.incrementAndGet(); ++ } ++ ++ @Override ++ public void onPacketDispatchFinish(ServerPlayer player, ChannelFuture future) { ++ if (remainingSends.decrementAndGet() <= 0) { ++ // incase of any race conditions, schedule this delayed ++ MCUtil.scheduleTask(5, () -> { ++ if (remainingSends.get() == 0) { ++ cleaner1.run(); ++ cleaner2.run(); ++ } ++ }, "Light Packet Release"); ++ } ++ } ++ ++ @Override ++ public boolean hasFinishListener() { ++ return true; ++ } ++ ++ // Paper end + public ClientboundLightUpdatePacket() {} + + public ClientboundLightUpdatePacket(ChunkPos chunkcoordintpair, LevelLightEngine lightengine, boolean flag) { + this.x = chunkcoordintpair.x; + this.z = chunkcoordintpair.z; + this.trustEdges = flag; +- this.skyUpdates = Lists.newArrayList(); +- this.blockUpdates = Lists.newArrayList(); ++ this.skyUpdates = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.skyUpdates, DataLayer::releaseBytes); // Paper ++ this.blockUpdates = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.blockUpdates, DataLayer::releaseBytes); // Paper + + for (int i = 0; i < 18; ++i) { + DataLayer nibblearray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, -1 + i)); +@@ -42,7 +75,7 @@ public class ClientboundLightUpdatePacket implements Packet BYTE_2048 = new PooledObjects<>(() -> new byte[2048], maxPoolSize); ++ public static void releaseBytes(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE && bytes.length == 2048) { ++ System.arraycopy(EMPTY_NIBBLE, 0, bytes, 0, 2048); ++ BYTE_2048.release(bytes); ++ } ++ } ++ ++ public DataLayer markPoolSafe(byte[] bytes) { ++ if (bytes != EMPTY_NIBBLE) this.data = bytes; ++ return markPoolSafe(); ++ } ++ public DataLayer markPoolSafe() { ++ poolSafe = true; ++ return this; ++ } ++ public byte[] getIfSet() { ++ return this.data != null ? this.data : EMPTY_NIBBLE; ++ } ++ public byte[] getCloneIfSet() { ++ if (data == null) { ++ return EMPTY_NIBBLE; ++ } ++ byte[] ret = BYTE_2048.acquire(); ++ System.arraycopy(getIfSet(), 0, ret, 0, 2048); ++ return ret; ++ } ++ ++ public DataLayer cloneAndSet(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE) { ++ this.data = BYTE_2048.acquire(); ++ System.arraycopy(bytes, 0, this.data, 0, 2048); ++ } ++ return this; ++ } ++ boolean poolSafe = false; ++ public java.lang.Runnable cleaner; ++ private void registerCleaner() { ++ if (!poolSafe) { ++ cleaner = MCUtil.registerCleaner(this, this.data, DataLayer::releaseBytes); ++ } else { ++ cleaner = MCUtil.once(() -> DataLayer.releaseBytes(this.data)); ++ } ++ } ++ // Paper end ++ @Nullable protected byte[] data; ++ + + public DataLayer() {} + + public DataLayer(byte[] abyte) { ++ // Paper start ++ this(abyte, false); ++ } ++ public DataLayer(byte[] abyte, boolean isSafe) { + this.data = abyte; ++ if (!isSafe) this.data = getCloneIfSet(); // Paper - clone for safety ++ registerCleaner(); ++ // Paper end + if (abyte.length != 2048) { + throw (IllegalArgumentException) Util.pauseInIde((Throwable) (new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length))); + } +@@ -46,7 +106,8 @@ public class DataLayer { + + public void set(int index, int value) { // PAIL: private -> public + if (this.data == null) { +- this.data = new byte[2048]; ++ this.data = BYTE_2048.acquire(); // Paper ++ registerCleaner();// Paper + } + + int k = this.getPosition(index); +@@ -68,14 +129,36 @@ public class DataLayer { + public byte[] getData() { + if (this.data == null) { + this.data = new byte[2048]; ++ } else { // Paper start ++ // Accessor may need this object past garbage collection so need to clone it and return pooled value ++ // If we know its safe for pre GC access, use asBytesPoolSafe(). If you just need read, use getIfSet() ++ Runnable cleaner = this.cleaner; ++ if (cleaner != null) { ++ this.data = this.data.clone(); ++ cleaner.run(); // release the previously pooled value ++ this.cleaner = null; ++ } ++ } ++ // Paper end ++ ++ return this.data; ++ } ++ ++ @Nonnull ++ public byte[] asBytesPoolSafe() { ++ if (this.data == null) { ++ this.data = BYTE_2048.acquire(); // Paper ++ registerCleaner(); // Paper + } + ++ //noinspection ConstantConditions + return this.data; + } ++ // Paper end + + public DataLayer copy() { return this.copy(); } // Paper - OBFHELPER + public DataLayer copy() { +- return this.data == null ? new DataLayer() : new DataLayer((byte[]) this.data.clone()); ++ return this.data == null ? new DataLayer() : new DataLayer(this.data); // Paper - clone in ctor + } + + public String toString() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 6c28a611b9b79c3322ab07883972c07b3bfc3073..1e58958c3d7b10da5a5f22fc9591d9183e53e3cc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -435,11 +435,11 @@ public class ChunkSerializer { + } + + if (nibblearray != null && !nibblearray.isEmpty()) { +- nbttagcompound2.putByteArray("BlockLight", nibblearray.getData()); ++ nbttagcompound2.putByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper + } + + if (nibblearray1 != null && !nibblearray1.isEmpty()) { +- nbttagcompound2.putByteArray("SkyLight", nibblearray1.getData()); ++ nbttagcompound2.putByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper + } + + nbttaglist.add(nbttagcompound2); +diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +index 4c9041f1c1cb4b3ec114fbd0c5d4db50a6f2526d..54cca3b376e5ce02936edc8b9c17e67e17f07147 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java ++++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.lighting; + + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import javax.annotation.Nullable; ++import net.minecraft.server.MCUtil; + import net.minecraft.world.level.chunk.DataLayer; + + public abstract class DataLayerStorageMap> { +@@ -34,7 +35,9 @@ public abstract class DataLayerStorageMap> { + + public void copyDataLayer(long pos) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data +- this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data ++ DataLayer updating = this.data.getUpdating(pos); // Paper - pool nibbles ++ this.data.queueUpdate(pos, new DataLayer().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone ++ if (updating.cleaner != null) MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it + this.clearCache(); + } + +diff --git a/src/main/java/net/minecraft/world/level/lighting/FlatDataLayer.java b/src/main/java/net/minecraft/world/level/lighting/FlatDataLayer.java +index 9b95ae0ff193d7f52650f406c70e76e3f7e07e1c..c0d356ac4d54b952fe9ddaf5125b07177ac44d1f 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/FlatDataLayer.java ++++ b/src/main/java/net/minecraft/world/level/lighting/FlatDataLayer.java +@@ -10,7 +10,7 @@ public class FlatDataLayer extends DataLayer { + + public FlatDataLayer(DataLayer nibblearray, int i) { + super(128); +- System.arraycopy(nibblearray.getData(), i * 128, this.data, 0, 128); ++ System.arraycopy(nibblearray.getIfSet(), i * 128, this.data, 0, 128); // Paper + } + + @Override +@@ -20,7 +20,7 @@ public class FlatDataLayer extends DataLayer { + + @Override + public byte[] getData() { +- byte[] abyte = new byte[2048]; ++ byte[] abyte = BYTE_2048.acquire(); // Paper + + for (int i = 0; i < 16; ++i) { + System.arraycopy(this.data, 0, abyte, i * 128, 128); +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +index 177dae992d13674bb285a60b8427df9ea843dc99..5757bcfded35f112d52a7c81586850ba50e0d8dd 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +@@ -156,7 +156,7 @@ public abstract class LayerLightSectionStorage> + protected DataLayer createDataLayer(long sectionPos) { + DataLayer nibblearray = (DataLayer) this.queuedSections.get(sectionPos); + +- return nibblearray != null ? nibblearray : new DataLayer(); ++ return nibblearray != null ? nibblearray : new DataLayer().markPoolSafe(); // Paper + } + + protected void clearQueuedSectionBlocks(LayerLightEngine storage, long sectionPos) { +@@ -338,12 +338,12 @@ public abstract class LayerLightSectionStorage> + + protected void queueSectionData(long sectionPos, @Nullable DataLayer array, boolean flag) { + if (array != null) { +- this.queuedSections.put(sectionPos, array); ++ DataLayer remove = this.queuedSections.put(sectionPos, array); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + if (!flag) { + this.untrustedSections.add(sectionPos); + } + } else { +- this.queuedSections.remove(sectionPos); ++ DataLayer remove = this.queuedSections.remove(sectionPos); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + } + + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +index 410fcfa8c01b7e3d3e3829ebdb92a11badff16ea..88f168f9d4c29cfc93500227bf8a60de4b6e4d8a 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +@@ -172,9 +172,9 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage +Date: Mon, 27 Apr 2020 02:48:06 -0700 +Subject: [PATCH] Reduce MutableInt allocations from light engine + +We can abuse the fact light is single threaded and share an instance +per light engine instance + +diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +index 8979101a52537f4ec03a5f43030264b8e72fcea4..709fc42057f8a0282c3c942067e63abb874d9042 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +@@ -16,6 +16,7 @@ public final class BlockLightEngine extends LayerLightEngine= 15) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +index d99890121bdf1e499e364bdc953e628c04d69b95..ff1fbc46776b26ca56c3293e40ed55028230ec46 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +@@ -15,6 +15,7 @@ public final class SkyLightEngine extends LayerLightEngine= 15) { + return level; + } else { +- MutableInt mutableint = new MutableInt(); ++ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded + BlockState iblockdata = this.getStateAndOpacity(targetId, mutableint); + + if (mutableint.getValue() >= 15) { diff --git a/Remapped-Spigot-Server-Patches/0479-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/Remapped-Spigot-Server-Patches/0479-Reduce-allocation-of-Vec3D-by-entity-tracker.patch new file mode 100644 index 000000000..675146728 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0479-Reduce-allocation-of-Vec3D-by-entity-tracker.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Apr 2020 00:04:16 -0700 +Subject: [PATCH] Reduce allocation of Vec3D by entity tracker + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 67f748d5955453ba4873b0c9bb741b5bfe52d655..738f1183ce663db7c67d2f0289823390a7f06a0e 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -95,7 +95,6 @@ import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; +-import net.minecraft.world.phys.Vec3; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; +@@ -2233,9 +2232,14 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + public void updatePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { +- Vec3 vec3d = player.position().subtract(this.entity.position()); // MC-155077, SPIGOT-5113 ++ // Paper start - remove allocation of Vec3D here ++ //Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113 ++ double vec3d_dx = player.getX() - this.entity.getX(); ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ double vec3d_dz = player.getZ() - this.entity.getZ(); ++ // Paper end - remove allocation of Vec3D here + int i = Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); +- boolean flag = vec3d.x >= (double) (-i) && vec3d.x <= (double) i && vec3d.z >= (double) (-i) && vec3d.z <= (double) i && this.entity.broadcastToPlayer(player); ++ boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.entity.broadcastToPlayer(player); // Paper - remove allocation of Vec3D here + + if (flag) { + boolean flag1 = this.entity.forcedLoading; +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index d797873db52ba265ac4478f9f3c6344badd4739e..75e2274578c2c28de3d786372df0b4102337a2cc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -154,8 +154,12 @@ public class ServerEntity { + ++this.teleportDelay; + i = Mth.floor(this.entity.yRot * 256.0F / 360.0F); + j = Mth.floor(this.entity.xRot * 256.0F / 360.0F); +- Vec3 vec3d = this.entity.position().subtract(ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp)); +- boolean flag1 = vec3d.lengthSqr() >= 7.62939453125E-6D; ++ // Paper start - reduce allocation of Vec3D here ++ double vec3d_dx = this.entity.getX() - 2.44140625E-4D*(this.xp); ++ double vec3d_dy = this.entity.getY() - 2.44140625E-4D*(this.yp); ++ double vec3d_dz = this.entity.getZ() - 2.44140625E-4D*(this.zp); ++ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; ++ // Paper end - reduce allocation of Vec3D here + Packet packet1 = null; + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; +@@ -172,9 +176,11 @@ public class ServerEntity { + // CraftBukkit end + + if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { +- long k = ClientboundMoveEntityPacket.entityToPacket(vec3d.x); +- long l = ClientboundMoveEntityPacket.entityToPacket(vec3d.y); +- long i1 = ClientboundMoveEntityPacket.entityToPacket(vec3d.z); ++ // Paper start - remove allocation of Vec3D here ++ long k = ClientboundMoveEntityPacket.entityToPacket(vec3d_dx); ++ long l = ClientboundMoveEntityPacket.entityToPacket(vec3d_dy); ++ long i1 = ClientboundMoveEntityPacket.entityToPacket(vec3d_dz); ++ // Paper end - remove allocation of Vec3D here + boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; + + if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) { diff --git a/Remapped-Spigot-Server-Patches/0480-Ensure-safe-gateway-teleport.patch b/Remapped-Spigot-Server-Patches/0480-Ensure-safe-gateway-teleport.patch new file mode 100644 index 000000000..3ca91f50a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0480-Ensure-safe-gateway-teleport.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Fri, 15 May 2020 01:10:03 -0400 +Subject: [PATCH] Ensure safe gateway teleport + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index b70e0633435a272ae1e9fbd12d7f18862de0b951..3491956cb09b825bbfc99667b058d67cef127332 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -86,9 +86,14 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity implements + } else if (!this.level.isClientSide) { + List list = this.level.getEntitiesOfClass(Entity.class, new AABB(this.getBlockPos()), TheEndGatewayBlockEntity::canEntityTeleport); + +- if (!list.isEmpty()) { +- this.teleportEntity((Entity) list.get(this.level.random.nextInt(list.size()))); ++ // Paper start ++ for (Entity entity : list) { ++ if (entity.canChangeDimensions()) { ++ this.teleportEntity(entity); ++ break; ++ } + } ++ // Paper end + + if (this.age % 2400L == 0L) { + this.triggerCooldown(); diff --git a/Remapped-Spigot-Server-Patches/0481-Add-option-for-console-having-all-permissions.patch b/Remapped-Spigot-Server-Patches/0481-Add-option-for-console-having-all-permissions.patch new file mode 100644 index 000000000..61d650c22 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0481-Add-option-for-console-having-all-permissions.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:12:15 +0200 +Subject: [PATCH] Add option for console having all permissions + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 5f3b0d95cc7e6a0434d78ea7305a70689c41c71c..7f140333c2e62012fa572c1a061d84432426997f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -429,4 +429,9 @@ public class PaperConfig { + + } + ++ public static boolean consoleHasAllPermissions = false; ++ private static void consoleHasAllPermissions() { ++ consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); ++ } ++ + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 525cd44411b344bc4b5d43c087094fea88fa41a6..4817b8ab259d348b48bc325d34ba9351ffe951df 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1886,7 +1886,7 @@ public abstract class Player extends LivingEntity { + } + } + +- protected void removeEntitiesOnShoulder() { ++ public void removeEntitiesOnShoulder() { // Paper - protected -> public + if (this.timeEntitySatOnShoulder + 20L < this.level.getGameTime()) { + // CraftBukkit start + if (this.spawnEntityFromShoulder(this.getShoulderEntityLeft())) { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index af986adfdb547cb61fbd52f0f89858f1a9e52cc3..80a67deaeaae3b3f0ceb9a298de5bb38b8ee707b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -86,5 +86,15 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { + this.sendRawMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); + } ++ ++ @Override ++ public boolean hasPermission(String name) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +index d0757d116ee689041c0e64e622d2c36e0b0bcaf1..7b53b5a0857fc0ce0463db319f86a1f79833ab93 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +@@ -39,4 +39,16 @@ public class CraftRemoteConsoleCommandSender extends ServerCommandSender impleme + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of remote controller."); + } ++ ++ // Paper start ++ @Override ++ public boolean hasPermission(String name) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0482-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch b/Remapped-Spigot-Server-Patches/0482-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch new file mode 100644 index 000000000..134641846 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0482-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 23 May 2020 01:31:06 -0400 +Subject: [PATCH] Fix Non Full Status Chunk NBT Memory Leak + +Any full status chunk that was requested for any status less than full +would hold onto their entire nbt tree and every variable in that function. + +This was due to use of a lambda that persists on the Chunk object +until that chunk reaches FULL status. + +With introduction of no tick, we greatly increased the number of non +full chunks so this was really starting to hurt. + +We further improve it by making a copy of the nbt tag with only the memory +it needs, so that we dont have to hold a copy to the entire compound. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 1e58958c3d7b10da5a5f22fc9591d9183e53e3cc..0adf14af9841cd3a20a8b2c0c320eb06794ef261 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -26,6 +26,7 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.LongArrayTag; + import net.minecraft.nbt.ShortTag; ++import net.minecraft.nbt.Tag; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -199,15 +200,9 @@ public class ChunkSerializer { + object2 = protochunkticklist1; + } + +- object = new LevelChunk(worldserver.getLevel(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, (chunk) -> { +- postLoadChunk(nbttagcompound1, chunk); +- // CraftBukkit start - load chunk persistent data from nbt +- net.minecraft.nbt.Tag persistentBase = nbttagcompound1.get("ChunkBukkitValues"); +- if (persistentBase instanceof CompoundTag) { +- chunk.persistentDataContainer.putAll((CompoundTag) persistentBase); +- } +- // CraftBukkit end +- }); ++ object = new LevelChunk(worldserver.getLevel(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys. ++ createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here ++ );// Paper end + } else { + ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter + +@@ -313,6 +308,50 @@ public class ChunkSerializer { + return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading + } + } ++ // Paper start ++ ++ /** ++ * This wrapper will error out if any key is accessed that wasn't copied so we can catch it easy on an update ++ */ ++ private static class SafeNBTCopy extends CompoundTag { ++ private final java.util.Set keys = new java.util.HashSet(); ++ public SafeNBTCopy(CompoundTag base, String... keys) { ++ for (String key : keys) { ++ this.keys.add(key); ++ final Tag nbtBase = base.get(key); ++ if (nbtBase != null) { ++ this.put(key, nbtBase); ++ } ++ } ++ } ++ ++ @Override ++ public boolean contains(String key) { ++ if (super.contains(key)) { ++ return true; ++ } else if (keys.contains(key)) { ++ return false; ++ } ++ throw new IllegalStateException("Missing Key " + key + " in SafeNBTCopy"); ++ } ++ ++ @Override ++ public boolean contains(String key, int type) { ++ return contains(key) && super.contains(key, type); ++ } ++ } ++ private static java.util.function.Consumer createLoadEntitiesConsumer(CompoundTag nbt) { ++ return (chunk) -> { ++ postLoadChunk(nbt, chunk); ++ // CraftBukkit start - load chunk persistent data from nbt ++ Tag persistentBase = nbt.get("ChunkBukkitValues"); ++ if (persistentBase instanceof CompoundTag) { ++ chunk.persistentDataContainer.putAll((CompoundTag) persistentBase); ++ } ++ // CraftBukkit end ++ }; ++ } ++ // Paper end + + // Paper start - async chunk save for unload + public static final class AsyncSaveData { diff --git a/Remapped-Spigot-Server-Patches/0483-Workaround-for-Client-Lag-Spikes-MC-162253.patch b/Remapped-Spigot-Server-Patches/0483-Workaround-for-Client-Lag-Spikes-MC-162253.patch new file mode 100644 index 000000000..7918002d5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0483-Workaround-for-Client-Lag-Spikes-MC-162253.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 12 May 2020 23:02:43 +0200 +Subject: [PATCH] Workaround for Client Lag Spikes (MC-162253) + +When crossing certain chunk boundaries, the client needlessly +calculates light maps for chunk neighbours. In some specific map +configurations, these calculations cause a 500ms+ freeze on the Client. + +This patch basically serves as a workaround by sending light maps +to the client, so that it doesn't attempt to calculate them. +This mitigates the frametime impact to a minimum (but it's still there). + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 738f1183ce663db7c67d2f0289823390a7f06a0e..8070acde38c47c364c1d26ec3b7d65da037554a5 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -85,6 +85,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.ImposterProtoChunk; + import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; + import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.level.chunk.ProtoChunk; + import net.minecraft.world.level.chunk.UpgradeData; +@@ -2018,7 +2019,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + // Paper start + private static int getLightMask(final LevelChunk chunk) { +- final ChunkSection[] chunkSections = chunk.getSections(); ++ final LevelChunkSection[] chunkSections = chunk.getSections(); + int mask = 0; + + for (int i = 0; i < chunkSections.length; ++i) { +@@ -2029,7 +2030,7 @@ Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section + Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d. + + */ +- mask |= (ChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i; ++ mask |= (LevelChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i; + } + + return mask; +@@ -2060,9 +2061,68 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + public final void sendChunk(ServerPlayer entityplayer, Packet[] apacket, LevelChunk chunk) { this.playerLoadedChunk(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void playerLoadedChunk(ServerPlayer player, Packet[] packets, LevelChunk chunk) { + if (packets[0] == null) { ++ // Paper start - add 8 for light fix workaround ++ if (packets.length != 10) { // in case Plugins call sendChunk, resize ++ packets = new Packet[10]; ++ } ++ // Paper end + packets[0] = new ClientboundLevelChunkPacket(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(player, chunk, 65535)); // Paper - Anti-Xray - Bypass + packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, true); ++ ++ // Paper start - Fix MC-162253 ++ final int lightMask = getLightMask(chunk); ++ int i = 1; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ ++i; ++ ++ if (!chunk.isNeighbourLoaded(x, z)) { ++ continue; ++ } ++ ++ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z); ++ final int updateLightMask = lightMask & ~getCeilingLightMask(neighbor); ++ ++ if (updateLightMask == 0) { ++ continue; ++ } ++ ++ packets[i] = new ClientboundLightUpdatePacket(new ChunkPos(chunk.getPos().x + x, chunk.getPos().z + z), lightEngine, updateLightMask, 0, true); ++ } ++ } ++ } ++ ++ final int viewDistance = playerViewDistanceBroadcastMap.getLastViewDistance(player); ++ final long lastPosition = playerViewDistanceBroadcastMap.getLastCoordinate(player); ++ ++ int j = 1; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ ++j; ++ ++ Packet packet = packets[j]; ++ if (packet == null) { ++ continue; ++ } ++ ++ final int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - (chunk.getPos().x + x)); ++ final int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - (chunk.getPos().z + z)); ++ ++ if (Math.max(distX, distZ) > viewDistance) { ++ continue; ++ } ++ player.connection.send(packet); ++ } + } ++ // Paper end - Fix MC-162253 + + player.trackChunk(chunk.getPos(), packets[0], packets[1]); + DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index dbea2a4370ccf24a5084cdabeecbc81f206e910a..9b76dc15417eef420804e5184a6d684e1137a746 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -279,7 +279,7 @@ public class LevelChunk implements ChunkAccess { + + // broadcast + Object[] backingSet = inRange.getBackingSet(); +- Packet[] chunkPackets = new Packet[2]; ++ Packet[] chunkPackets = new Packet[10]; + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof ServerPlayer)) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 5e7f6000df129100ef306703f325af9f60da8ae6..cc7d930c1fcd7157efc181d766e1639669f6eab9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -107,6 +107,7 @@ public class LevelChunkSection { + return this.nonEmptyBlockCount == 0; + } + ++ public static boolean isEmpty(@Nullable LevelChunkSection chunksection) { return isEmpty(chunksection) ; } // Paper - OBFHELPER + public static boolean isEmpty(@Nullable LevelChunkSection section) { + return section == LevelChunk.EMPTY_SECTION || section.isEmpty(); + } diff --git a/Remapped-Spigot-Server-Patches/0484-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Remapped-Spigot-Server-Patches/0484-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch new file mode 100644 index 000000000..c64155c42 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0484-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -0,0 +1,1352 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Apr 2020 03:56:07 -0400 +Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks + +Mark chunks that are blocking main thread for world generation as urgent + +Implements a general priority system so that chunks that are sorted in +the generator queues can prioritize certain chunks over another. + +Urgent chunks will jump to the front of the line, ensuring that a +sync chunk load on an ungenerated chunk does not lag the server for +a long period of time if the servers generator queues are filled with +lots of chunks already. + +This massively reduces the lag spikes from sync chunk gens. + +Then we further prioritize loading order so nearby chunks have higher +priority than distant chunks, reducing the pressure a high no tick +view distance holds on you. + +Chunks in front of the player have higher priority, to help with +fast traveling players keep up with their movement. + +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +index 499aff1f1e1ffc01ba8f9de43ca17899525a306f..97b85587525ddb62af9bfc8785b48727a6135599 100644 +--- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +@@ -108,7 +108,7 @@ public final class ChunkTaskManager { + } + + static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z) { +- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); + } + + static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) { +@@ -129,6 +129,30 @@ public final class ChunkTaskManager { + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString())); + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel())); + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.getCurrentPriority()); ++ ++ if (!chunkHolder.neighbors.isEmpty()) { ++ if (indent >= maxDepth) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)"); ++ return; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); ++ for (ChunkHolder neighbor : chunkHolder.neighbors.keySet()) { ++ ChunkStatus status = neighbor.getChunkHolderStatus(); ++ if (status != null && status.isAtLeastStatus(ChunkHolder.getStatus(neighbor.getTicketLevel()))) { ++ continue; ++ } ++ int nx = neighbor.pos.x; ++ int nz = neighbor.pos.z; ++ if (seenChunks.contains(neighbor)) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); ++ continue; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); ++ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth); ++ } ++ } ++ + } + } + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index d18497a33dc53f6b465e659967bf8c98731c46c0..9a5737caf250dd2cc7f244248226f69117b27bad 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -631,10 +631,10 @@ public final class MCUtil { + + // sorting by coordinate makes the log easier to read + allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> { +- if (v1.location.x != v2.location.x) { +- return Integer.compare(v1.location.x, v2.location.x); ++ if (v1.pos.x != v2.pos.x) { ++ return Integer.compare(v1.pos.x, v2.pos.x); + } +- return Integer.compare(v1.location.z, v2.location.z); ++ return Integer.compare(v1.pos.z, v2.pos.z); + }); + + worldData.addProperty("name", world.getWorld().getName()); +@@ -667,14 +667,15 @@ public final class MCUtil { + for (ChunkHolder playerChunk : allChunks) { + JsonObject chunkData = new JsonObject(); + +- Set> tickets = chunkMapDistance.tickets.get(playerChunk.pos.pair()); ++ Set> tickets = chunkMapDistance.tickets.get(playerChunk.pos.toLong()); + ChunkStatus status = getChunkStatus(playerChunk); + +- chunkData.addProperty("x", playerChunk.location.x); +- chunkData.addProperty("z", playerChunk.location.z); ++ chunkData.addProperty("x", playerChunk.pos.x); ++ chunkData.addProperty("z", playerChunk.pos.z); + chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("priority", playerChunk.getCurrentPriority()); + chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString()); +- chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.pair())); ++ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.toLong())); + chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); + + JsonArray ticketsData = new JsonArray(); +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index d907872d80f840b343419f49a6708082da6f921b..ce320672d7602c94dd75ad857435dca6ac3bab56 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Either; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + import it.unimi.dsi.fastutil.shorts.ShortArraySet; + import it.unimi.dsi.fastutil.shorts.ShortSet; + import java.util.List; +@@ -19,6 +20,7 @@ import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; + import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; ++import net.minecraft.server.MCUtil; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LightLayer; +@@ -52,8 +54,8 @@ public class ChunkHolder { + private CompletableFuture chunkToSave; + public int oldTicketLevel; + private int ticketLevel; +- private int queueLevel; +- final ChunkPos pos; // Paper - private -> package ++ volatile int queueLevel; public final int getCurrentPriority() { return queueLevel; } // Paper - OBFHELPER - make volatile since this is concurrently accessed ++ public final ChunkPos pos; // Paper - private -> public + private boolean hasChangedSections; + private final ShortSet[] changedBlocksPerSection; + private int blockChangedLightSectionFilter; +@@ -65,6 +67,7 @@ public class ChunkHolder { + private boolean resendLight; + + private final ChunkMap chunkMap; // Paper ++ public ServerLevel getWorld() { return chunkMap.level; } // Paper + + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave +@@ -92,6 +95,120 @@ public class ChunkHolder { + return null; + } + // Paper end - no-tick view distance ++ // Paper start - Chunk gen/load priority system ++ volatile int neighborPriority = -1; ++ volatile int priorityBoost = 0; ++ public final java.util.concurrent.ConcurrentHashMap neighbors = new java.util.concurrent.ConcurrentHashMap<>(); ++ public final Long2ObjectOpenHashMap neighborPriorities = new Long2ObjectOpenHashMap<>(); ++ ++ private int getDemandedPriority() { ++ int priority = neighborPriority; // if we have a neighbor priority, use it ++ int myPriority = getMyPriority(); ++ ++ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) { ++ priority = myPriority; ++ } ++ ++ return Math.max(1, Math.min(Math.max(ticketLevel, ChunkMap.MAX_CHUNK_DISTANCE), priority)); ++ } ++ ++ private int getMyPriority() { ++ if (priorityBoost == DistanceManager.URGENT_PRIORITY) { ++ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents ++ } ++ return ticketLevel - priorityBoost; ++ } ++ ++ private int getNeighborsPriority() { ++ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1; ++ } ++ ++ public void onNeighborRequest(ChunkHolder neighbor, ChunkStatus status) { ++ neighbor.setNeighborPriority(this, getNeighborsPriority()); ++ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> { ++ if (currentWantedStatus == null || !currentWantedStatus.isAtLeastStatus(status)) { ++ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus); ++ return status; ++ } else { ++ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus); ++ return currentWantedStatus; ++ } ++ }); ++ ++ } ++ ++ public void onNeighborDone(ChunkHolder neighbor, ChunkStatus chunkstatus, ChunkAccess chunk) { ++ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> { ++ if (wantedStatus != null && chunkstatus.isAtLeastStatus(wantedStatus)) { ++ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus); ++ neighbor.removeNeighborPriority(this); ++ return null; ++ } else { ++ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus); ++ return wantedStatus; ++ } ++ }); ++ } ++ ++ private void removeNeighborPriority(ChunkHolder requester) { ++ synchronized (neighborPriorities) { ++ neighborPriorities.remove(requester.pos.toLong()); ++ recalcNeighborPriority(); ++ } ++ checkPriority(); ++ } ++ ++ ++ private void setNeighborPriority(ChunkHolder requester, int priority) { ++ synchronized (neighborPriorities) { ++ neighborPriorities.put(requester.pos.toLong(), Integer.valueOf(priority)); ++ recalcNeighborPriority(); ++ } ++ checkPriority(); ++ } ++ ++ private void recalcNeighborPriority() { ++ neighborPriority = -1; ++ if (!neighborPriorities.isEmpty()) { ++ synchronized (neighborPriorities) { ++ for (Integer neighbor : neighborPriorities.values()) { ++ if (neighbor < neighborPriority || neighborPriority == -1) { ++ neighborPriority = neighbor; ++ } ++ } ++ } ++ } ++ } ++ private void checkPriority() { ++ if (getCurrentPriority() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); ++ } ++ ++ public final double getDistance(ServerPlayer player) { ++ return getDistance(player.getX(), player.getZ()); ++ } ++ public final double getDistance(double blockX, double blockZ) { ++ int cx = MCUtil.fastFloor(blockX) >> 4; ++ int cz = MCUtil.fastFloor(blockZ) >> 4; ++ final double x = pos.x - cx; ++ final double z = pos.z - cz; ++ return (x * x) + (z * z); ++ } ++ ++ public final double getDistanceFrom(BlockPos pos) { ++ return getDistance(pos.getX(), pos.getZ()); ++ } ++ ++ @Override ++ public String toString() { ++ return "PlayerChunk{" + ++ "location=" + pos + ++ ", ticketLevel=" + ticketLevel + "/" + getStatus(this.ticketLevel) + ++ ", chunkHolderStatus=" + getChunkHolderStatus() + ++ ", neighborPriority=" + getNeighborsPriority() + ++ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + getCurrentPriority() + ++ '}'; ++ } ++ // Paper end + + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); +@@ -194,6 +311,18 @@ public class ChunkHolder { + } + return null; + } ++ public static ChunkStatus getNextStatus(ChunkStatus status) { ++ if (status == ChunkStatus.FULL) { ++ return status; ++ } ++ return CHUNK_STATUSES.get(status.getStatusIndex() + 1); ++ } ++ public CompletableFuture> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) { ++ return ensureMain(getFutureIfPresentUnchecked(chunkstatus)); ++ } ++ public CompletableFuture ensureMain(CompletableFuture future) { ++ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor); ++ } + // Paper end + + public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { +@@ -440,6 +569,7 @@ public class ChunkHolder { + return this.queueLevel; + } + ++ private void setPriority(int i) { setQueueLevel(i); } // Paper - OBFHELPER + private void setQueueLevel(int level) { + this.queueLevel = level; + } +@@ -458,7 +588,7 @@ public class ChunkHolder { + // CraftBukkit start + // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. + if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { +- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + LevelChunk chunk = (LevelChunk)either.left().orElse(null); + if (chunk != null) { + chunkStorage.callbackExecutor.execute(() -> { +@@ -523,12 +653,13 @@ public class ChunkHolder { + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +- this.fullChunkFuture = chunkStorage.unpackTicks(this); this.fullChunkFuture.thenAccept((either) -> { ++ this.fullChunkFuture = chunkStorage.unpackTicks(this); ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk fullChunk = either.left().get(); + ChunkHolder.this.isFullChunkReady = true; + fullChunk.playerChunk = ChunkHolder.this; ++ this.chunkMap.distanceManager.clearPriorityTickets(pos); + + + } +@@ -553,7 +684,7 @@ public class ChunkHolder { + + if (!flag4 && flag5) { + // Paper start - cache ticking ready status +- this.tickingChunkFuture = chunkStorage.postProcess(this); this.tickingChunkFuture.thenAccept((either) -> { ++ this.tickingChunkFuture = chunkStorage.postProcess(this); ensureMain(this.tickingChunkFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk tickingChunk = either.left().get(); +@@ -584,7 +715,7 @@ public class ChunkHolder { + } + + // Paper start - cache ticking ready status +- this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); this.entityTickingChunkFuture.thenAccept((either) -> { ++ this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); ensureMain(this.entityTickingChunkFuture).thenAccept((either) -> { // Paper ensureMain + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk entityTickingChunk = either.left().get(); +@@ -604,12 +735,29 @@ public class ChunkHolder { + this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } + +- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); ++ // Paper start - raise IO/load priority if priority changes, use our preferred priority ++ priorityBoost = chunkMap.distanceManager.getChunkPriority(pos); ++ int priority = getDemandedPriority(); ++ if (getCurrentPriority() > priority) { ++ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; ++ if (priority <= 10) { ++ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; ++ } else if (priority <= 20) { ++ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; ++ } ++ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); ++ } ++ if (getCurrentPriority() != priority) { ++ this.onLevelChange.onLevelChange(this.pos, this::getCurrentPriority, priority, this::setPriority); // use preferred priority ++ int neighborsPriority = getNeighborsPriority(); ++ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); ++ } ++ // Paper end + this.oldTicketLevel = this.ticketLevel; + // CraftBukkit start + // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. + if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { +- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + LevelChunk chunk = (LevelChunk)either.left().orElse(null); + if (chunk != null) { + chunkStorage.callbackExecutor.execute(() -> { +@@ -691,6 +839,7 @@ public class ChunkHolder { + + public interface LevelChangeListener { + ++ default void changePriority(ChunkPos chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { onLevelChange(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER + void onLevelChange(ChunkPos pos, IntSupplier levelGetter, int targetLevel, IntConsumer levelSetter); + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 8070acde38c47c364c1d26ec3b7d65da037554a5..7a1f6d1807757a43a7aa471db651404c06720820 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; // Paper + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; + import it.unimi.dsi.fastutil.longs.LongIterator; +@@ -51,6 +52,7 @@ import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; + import net.minecraft.Util; ++import net.minecraft.core.BlockPos; + import net.minecraft.core.SectionPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.protocol.Packet; +@@ -102,6 +104,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + import org.bukkit.entity.Player; // CraftBukkit ++import org.spigotmc.AsyncCatcher; + + public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { + +@@ -139,6 +142,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; + private final BlockableEventLoop mainThreadExecutor; ++ final java.util.concurrent.Executor mainInvokingExecutor; // Paper + public final ChunkGenerator generator; + private final Supplier overworldDataStorage; public final Supplier getWorldPersistentDataSupplier() { return this.overworldDataStorage; } // Paper - OBFHELPER + private final PoiManager poiManager; +@@ -176,6 +180,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Override + public void execute(Runnable runnable) { ++ AsyncCatcher.catchOp("Callback Executor execute"); + if (queued == null) { + queued = new java.util.ArrayDeque<>(); + } +@@ -184,6 +189,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Override + public void run() { ++ AsyncCatcher.catchOp("Callback Executor run"); + if (queued == null) { + return; + } +@@ -338,6 +344,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.level = worldserver; + this.generator = chunkGenerator; + this.mainThreadExecutor = mainThreadExecutor; ++ // Paper start ++ this.mainInvokingExecutor = (run) -> { ++ if (MCUtil.isMainThread()) { ++ run.run(); ++ } else { ++ mainThreadExecutor.execute(run); ++ } ++ }; ++ // Paper end + ProcessorMailbox threadedmailbox = ProcessorMailbox.create(workerExecutor, "worldgen"); + + mainThreadExecutor.getClass(); +@@ -432,6 +447,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ checkHighPriorityChunks(player); + if (newState.size() != 1) { + return; + } +@@ -450,7 +466,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); + ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update +- }); ++ ChunkMap.this.level.getChunkSource().clearPriorityTickets(chunkPos); ++ }, (player, prevPos, newPos) -> { ++ player.lastHighPriorityChecked = -1; // reset and recheck ++ checkHighPriorityChunks(player); ++ }); + this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, +@@ -467,6 +487,115 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + // Paper end - no-tick view distance + } ++ // Paper start - Chunk Prioritization ++ public void queueHolderUpdate(ChunkHolder playerchunk) { ++ Runnable runnable = () -> { ++ if (isUnloading(playerchunk)) { ++ return; // unloaded ++ } ++ distanceManager.pendingChunkUpdates.add(playerchunk); ++ if (!distanceManager.pollingPendingChunkUpdates) { ++ level.getChunkSource().runDistanceManagerUpdates(); ++ } ++ }; ++ if (MCUtil.isMainThread()) { ++ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks... ++ runnable.run(); ++ } else { ++ mainThreadExecutor.execute(runnable); ++ } ++ } ++ ++ private boolean isUnloading(ChunkHolder playerchunk) { ++ return playerchunk == null || toDrop.contains(playerchunk.pos.toLong()); ++ } ++ ++ private void updateChunkPriorityMap(Long2IntOpenHashMap map, long chunk, int level) { ++ int prev = map.getOrDefault(chunk, -1); ++ if (level > prev) { ++ map.put(chunk, level); ++ } ++ } ++ ++ public void checkHighPriorityChunks(ServerPlayer player) { ++ int currentTick = MinecraftServer.currentTick; ++ if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players ++ return; ++ } ++ player.lastHighPriorityChecked = currentTick; ++ Long2IntOpenHashMap priorities = new Long2IntOpenHashMap(); ++ ++ int viewDistance = getEffectiveNoTickViewDistance(); ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ // Prioritize circular near ++ double playerChunkX = Mth.floor(player.getX()) >> 4; ++ double playerChunkZ = Mth.floor(player.getZ()) >> 4; ++ pos.setValues(player.getX(), 0, player.getZ()); ++ double twoThirdModifier = 2D / 3D; ++ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ // Prioritize immediate ++ if (dist <= 4) { ++ updateChunkPriorityMap(priorities, coord.toLong(), (int) (27 - dist)); ++ return; ++ } ++ ++ // Prioritize nearby chunks ++ updateChunkPriorityMap(priorities, coord.toLong(), (int) (20 - dist * twoThirdModifier)); ++ }); ++ ++ // Prioritize Frustum near 3 ++ ChunkPos front3 = player.getChunkInFront(3); ++ pos.setValues(front3.x << 4, 0, front3.z << 4); ++ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ ++ // Prioritize Frustum near 5 ++ if (viewDistance > 4) { ++ ChunkPos front5 = player.getChunkInFront(5); ++ pos.setValues(front5.x << 4, 0, front5.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ } ++ ++ // Prioritize Frustum far 7 ++ if (viewDistance > 6) { ++ ChunkPos front7 = player.getChunkInFront(7); ++ pos.setValues(front7.x << 4, 0, front7.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) { ++ return; ++ } ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ } ++ ++ if (priorities.isEmpty()) return; ++ distanceManager.delayDistanceManagerTick = true; ++ priorities.long2IntEntrySet().fastForEach(entry -> distanceManager.markHighPriority(new ChunkPos(entry.getLongKey()), entry.getIntValue())); ++ distanceManager.delayDistanceManagerTick = false; ++ level.getChunkSource().runDistanceManagerUpdates(); ++ ++ } ++ ++ private boolean shouldSkipPrioritization(ChunkPos coord) { ++ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.toLong()) == null) return true; ++ ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong()); ++ return chunk != null && (chunk.isFullChunkReady()); ++ } ++ // Paper end + + public void updatePlayerMobTypeMap(Entity entity) { + if (!this.level.paperConfig.perPlayerMobSpawns) { +@@ -596,6 +725,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + List>> list = Lists.newArrayList(); + int j = centerChunk.x; + int k = centerChunk.z; ++ ChunkHolder requestingNeighbor = getUpdatingChunkIfPresent(centerChunk.toLong()); // Paper + + for (int l = -margin; l <= margin; ++l) { + for (int i1 = -margin; i1 <= margin; ++i1) { +@@ -614,6 +744,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(j1); + CompletableFuture> completablefuture = playerchunk.getOrScheduleFuture(chunkstatus, this); ++ // Paper start ++ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) { ++ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); ++ completablefuture.thenAccept(either -> { ++ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null)); ++ }); ++ } ++ // Paper end + + list.add(completablefuture); + } +@@ -1081,14 +1219,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }; + + CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z); ++ ChunkHolder playerChunk = getUpdatingChunkIfPresent(pos.toLong()); ++ int chunkPriority = playerChunk != null ? playerChunk.getCurrentPriority() : 33; ++ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; ++ ++ if (chunkPriority <= 10) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; ++ } else if (chunkPriority <= 20) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; ++ } ++ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; + if (chunkSaveFuture != null) { +- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); +- this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); ++ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); + } else { +- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); ++ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority); + } ++ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, priority); + return ret; + // Paper end + } +@@ -1233,7 +1379,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + long i = playerchunk.getPos().toLong(); + + playerchunk.getClass(); +- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, playerchunk::getTicketLevel)); ++ mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, () -> 1)); // Paper - final loads are always urgent! + }); + } + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index c9b4025f6c3d1be7bca2ff7337dd86e37d21b53e..e41f388e8350010a471410436adf15a906f07e97 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -21,7 +21,10 @@ import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; + import javax.annotation.Nullable; ++import net.minecraft.core.BlockPos; + import net.minecraft.core.SectionPos; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.SortedArraySet; + import net.minecraft.util.thread.ProcessorHandle; + import net.minecraft.world.level.ChunkPos; +@@ -29,6 +32,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunk; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.spigotmc.AsyncCatcher; // Paper + + public abstract class DistanceManager { + +@@ -52,7 +56,7 @@ public abstract class DistanceManager { + private final ChunkTaskPriorityQueueSorter ticketThrottler; + private final ProcessorHandle> ticketThrottlerInput; + private final ProcessorHandle ticketThrottlerReleaser; +- private final LongSet ticketsToRelease = new LongOpenHashSet(); ++ private final LongSet ticketsToRelease = new LongOpenHashSet(); public final LongSet getOnPlayerTicketAddQueue() { return ticketsToRelease; } // Paper - OBFHELPER + private final Executor mainThreadExecutor; + private long ticketTickCounter; + +@@ -90,6 +94,7 @@ public abstract class DistanceManager { + } + + private static int getTicketLevelAt(SortedArraySet> arraysetsorted) { ++ AsyncCatcher.catchOp("ChunkMapDistance::getLowestTicketLevel"); // Paper + return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1; + } + +@@ -103,6 +108,7 @@ public abstract class DistanceManager { + + public boolean runAllUpdates(ChunkMap chunkStorage) { + //this.f.a(); // Paper - no longer used ++ AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.playerTicketManager.runAllUpdates(); + int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -113,11 +119,13 @@ public abstract class DistanceManager { + + // Paper start + if (!this.pendingChunkUpdates.isEmpty()) { ++ this.pollingPendingChunkUpdates = true; try { + while(!this.pendingChunkUpdates.isEmpty()) { + ChunkHolder remove = this.pendingChunkUpdates.remove(); + remove.isUpdateQueued = false; + remove.updateFutures(chunkStorage); + } ++ } finally { this.pollingPendingChunkUpdates = false; } + // Paper end + return true; + } else { +@@ -153,8 +161,10 @@ public abstract class DistanceManager { + return flag; + } + } ++ boolean pollingPendingChunkUpdates = false; // Paper + + private boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper + SortedArraySet> arraysetsorted = this.getTickets(i); + int j = getTicketLevelAt(arraysetsorted); + Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); // CraftBukkit - decompile error +@@ -168,7 +178,9 @@ public abstract class DistanceManager { + } + + private boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper + SortedArraySet> arraysetsorted = this.getTickets(i); ++ int oldLevel = getTicketLevelAt(arraysetsorted); // Paper + + boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { +@@ -179,7 +191,8 @@ public abstract class DistanceManager { + this.tickets.remove(i); + } + +- this.ticketTracker.update(i, getTicketLevelAt(arraysetsorted), false); ++ int newLevel = getTicketLevelAt(arraysetsorted); // Paper ++ if (newLevel > oldLevel) this.ticketTracker.update(i, newLevel, false); // Paper + return removed; // CraftBukkit + } + +@@ -188,6 +201,135 @@ public abstract class DistanceManager { + this.addTicketAtLevel(type, pos, level, argument); + } + ++ // Paper start ++ public static final int PRIORITY_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE; ++ public static final int URGENT_PRIORITY = 29; ++ public boolean delayDistanceManagerTick = false; ++ public boolean markUrgent(ChunkPos coords) { ++ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY); ++ } ++ public boolean markHighPriority(ChunkPos coords, int priority) { ++ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); ++ return addPriorityTicket(coords, TicketType.PRIORITY, priority); ++ } ++ ++ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { ++ delayDistanceManagerTick = true; ++ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); ++ int finalPriority = priority; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkPos center, int radius) { ++ delayDistanceManagerTick = true; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); ++ } ++ ++ private boolean hasPlayerTicket(ChunkPos coords, int level) { ++ SortedArraySet> tickets = this.tickets.get(coords.toLong()); ++ if (tickets == null || tickets.isEmpty()) { ++ return false; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getType() == TicketType.PLAYER && ticket.getTicketLevel() == level) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ private boolean addPriorityTicket(ChunkPos coords, TicketType ticketType, int priority) { ++ AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); ++ long pair = coords.toLong(); ++ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair); ++ boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33); ++ ++ if (needsTicket) { ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); ++ getOnPlayerTicketAddQueue().add(pair); ++ addTicket(pair, ticket); ++ } ++ if ((chunk != null && chunk.isFullChunkReady())) { ++ if (needsTicket) { ++ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); ++ } ++ return needsTicket; ++ } ++ ++ boolean success; ++ if (!(success = updatePriorityTicket(coords, ticketType, priority))) { ++ Ticket ticket = new Ticket(ticketType, PRIORITY_TICKET_LEVEL, coords); ++ ticket.priority = priority; ++ success = this.addTicket(pair, ticket); ++ } else { ++ if (chunk == null) { ++ chunk = chunkMap.getUpdatingChunkIfPresent(pair); ++ } ++ chunkMap.queueHolderUpdate(chunk); ++ } ++ ++ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true); ++ ++ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); ++ ++ return success; ++ } ++ ++ private boolean updatePriorityTicket(ChunkPos coords, TicketType type, int priority) { ++ SortedArraySet> tickets = this.tickets.get(coords.toLong()); ++ if (tickets == null) { ++ return false; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getType() == type) { ++ // We only support increasing, not decreasing, too complicated ++ ticket.setCurrentTick(this.ticketTickCounter); ++ ticket.priority = Math.max(ticket.priority, priority); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ public int getChunkPriority(ChunkPos coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority"); ++ SortedArraySet> tickets = this.tickets.get(coords.toLong()); ++ if (tickets == null) { ++ return 0; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getType() == TicketType.URGENT) { ++ return URGENT_PRIORITY; ++ } ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getType() == TicketType.PRIORITY && ticket.priority > 0) { ++ return ticket.priority; ++ } ++ } ++ return 0; ++ } ++ ++ public void clearPriorityTickets(ChunkPos coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); ++ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); ++ } ++ ++ public void clearUrgent(ChunkPos coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); ++ this.removeTicket(coords.toLong(), new Ticket(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords)); ++ } ++ // Paper end + public boolean addTicketAtLevel(TicketType ticketType, ChunkPos chunkcoordintpair, int level, T identifier) { + return this.addTicket(chunkcoordintpair.toLong(), new Ticket<>(ticketType, level, identifier)); + // CraftBukkit end +@@ -358,7 +500,7 @@ public abstract class DistanceManager { + + class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker { + +- private int viewDistance = 0; ++ private int viewDistance = 0; private int getViewDistance() { return viewDistance; } private void setViewDistance(int value) { this.viewDistance = value; } // Paper - OBFHELPER + private final Long2IntMap queueLevels = Long2IntMaps.synchronize(new Long2IntOpenHashMap()); + private final LongSet toUpdate = new LongOpenHashSet(); + +@@ -374,41 +516,68 @@ public abstract class DistanceManager { + + public void updateViewDistance(int watchDistance) { + ObjectIterator objectiterator = this.chunks.long2ByteEntrySet().iterator(); ++ // Paper start - set the view distance before scheduling chunk loads/unloads ++ int lastViewDistance = getViewDistance(); ++ setViewDistance(watchDistance); ++ // Paper end + + while (objectiterator.hasNext()) { + Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (Long2ByteMap.Entry) objectiterator.next(); // Paper - decompile fix + byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); + long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); + +- this.onLevelChange(j, b0, this.haveTicketFor(b0), b0 <= watchDistance - 2); ++ this.onLevelChange(j, b0, b0 <= lastViewDistance - 2, this.haveTicketFor(b0)); // Paper + } + +- this.viewDistance = watchDistance; ++ //this.e = i; // Paper - view distance is now set further up + } + + private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) { + if (oldWithinViewDistance != withinViewDistance) { +- Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance ++ ChunkPos coords = new ChunkPos(pos); // Paper ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); // Paper - no-tick view distance + + if (withinViewDistance) { +- DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { ++ scheduleChunkLoad(pos, MinecraftServer.currentTick, distance, (priority) -> { // Paper - smarter ticket delay based on frustum and distance ++ // Paper start - recheck its still valid if not cancel ++ if (!isChunkInRange(pos)) { ++ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { ++ DistanceManager.this.mainThreadExecutor.execute(() -> { ++ DistanceManager.this.removeTicket(pos, ticket); ++ DistanceManager.this.clearPriorityTickets(coords); ++ }); ++ }, pos, false)); ++ return; ++ } ++ // abort early if we got a ticket already ++ if (hasPlayerTicket(coords, 33)) return; ++ // skip player ticket throttle for near chunks ++ if (priority <= 3) { ++ DistanceManager.this.addTicket(pos, ticket); ++ DistanceManager.this.ticketsToRelease.add(pos); ++ return; ++ } ++ // Paper end ++ DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { // CraftBukkit - decompile error + DistanceManager.this.mainThreadExecutor.execute(() -> { +- if (this.haveTicketFor(this.getLevel(pos))) { ++ if (isChunkInRange(pos)) { if (!hasPlayerTicket(coords, 33)) { // Paper - high priority might of already added it + DistanceManager.this.addTicket(pos, ticket); + DistanceManager.this.ticketsToRelease.add(pos); +- } else { +- DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { ++ }} else { // Paper ++ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { // CraftBukkit - decompile error + }, pos, false)); + } + + }); + }, pos, () -> { +- return distance; ++ return Math.min(ChunkMap.MAX_CHUNK_DISTANCE, priority); // Paper + })); ++ }); // Paper + } else { + DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { + DistanceManager.this.mainThreadExecutor.execute(() -> { + DistanceManager.this.removeTicket(pos, ticket); ++ DistanceManager.this.clearPriorityTickets(coords); // Paper + }); + }, pos, true)); + } +@@ -416,6 +585,101 @@ public abstract class DistanceManager { + + } + ++ // Paper start - smart scheduling of player tickets ++ private boolean isChunkInRange(long i) { ++ return this.isLoadedChunkLevel(this.getChunkLevel(i)); ++ } ++ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer task) { ++ long elapsed = MinecraftServer.currentTick - startTick; ++ ChunkPos chunkPos = new ChunkPos(i); ++ ChunkHolder updatingChunk = chunkMap.getUpdatingChunkIfPresent(i); ++ if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !isChunkInRange(i) || getChunkPriority(chunkPos) > 0) { // Copied from above ++ // no longer needed ++ task.accept(1); ++ return; ++ } ++ ++ int desireDelay = 0; ++ double minDist = Double.MAX_VALUE; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i); ++ if (elapsed == 0 && initialDistance <= 4) { ++ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6 ++ minDist = initialDistance; ++ } else if (players != null) { ++ Object[] backingSet = players.getBackingSet(); ++ ++ BlockPos blockPos = chunkPos.asPosition(); ++ ++ boolean isFront = false; ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ for (int index = 0, len = backingSet.length; index < len; ++index) { ++ if (!(backingSet[index] instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer) backingSet[index]; ++ ++ ChunkPos pointInFront = player.getChunkInFront(5); ++ pos.setValues(pointInFront.x << 4, 0, pointInFront.z << 4); ++ double frontDist = MCUtil.distanceSq(pos, blockPos); ++ ++ pos.setValues(player.getX(), 0, player.getZ()); ++ double center = MCUtil.distanceSq(pos, blockPos); ++ ++ double dist = Math.min(frontDist, center); ++ if (!isFront) { ++ ChunkPos pointInBack = player.getChunkInFront(-7); ++ pos.setValues(pointInBack.x << 4, 0, pointInBack.z << 4); ++ double backDist = MCUtil.distanceSq(pos, blockPos); ++ if (frontDist < backDist) { ++ isFront = true; ++ } ++ } ++ if (dist < minDist) { ++ minDist = dist; ++ } ++ } ++ if (minDist == Double.MAX_VALUE) { ++ minDist = 15; ++ } else { ++ minDist = Math.sqrt(minDist) / 16; ++ } ++ if (minDist > 4) { ++ int desiredTimeDelayMax = isFront ? ++ (minDist < 10 ? 7 : 15) : // Front ++ (minDist < 10 ? 15 : 45); // Back ++ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32); ++ } ++ } else { ++ minDist = initialDistance; ++ desireDelay = 1; ++ } ++ long delay = desireDelay - elapsed; ++ if (delay <= 0 && minDist > 4 && minDist < Double.MAX_VALUE) { ++ boolean hasAnyNeighbor = false; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) continue; ++ long pair = ChunkPos.asLong(chunkPos.x + x, chunkPos.z + z); ++ ChunkHolder neighbor = chunkMap.getUpdatingChunkIfPresent(pair); ++ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null; ++ if (current != null && current.isAtLeastStatus(ChunkStatus.LIGHT)) { ++ hasAnyNeighbor = true; ++ } ++ } ++ } ++ if (!hasAnyNeighbor) { ++ delay += 20; ++ } ++ } ++ if (delay <= 0) { ++ task.accept((int) minDist); ++ } else { ++ int taskDelay = (int) Math.min(delay, minDist >= 10 ? 40 : (minDist < 6 ? 5 : 20)); ++ MCUtil.scheduleTask(taskDelay, () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer"); ++ } ++ } ++ // Paper end ++ + @Override + public void runAllUpdates() { + super.runAllUpdates(); +@@ -447,6 +711,7 @@ public abstract class DistanceManager { + + } + ++ private boolean isLoadedChunkLevel(int i) { return haveTicketFor(i); } // Paper - OBFHELPER + private boolean haveTicketFor(int distance) { + return distance <= this.viewDistance - 2; + } +@@ -463,6 +728,7 @@ public abstract class DistanceManager { + this.chunks.defaultReturnValue((byte) (i + 2)); + } + ++ protected final int getChunkLevel(long i) { return getLevel(i); } // Paper - OBFHELPER + @Override + protected int getLevel(long id) { + return this.chunks.get(id); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 52c2e81f2e2bcd74d4e9aac3ecb5ab618e289abd..f36badcafbad7fb4537ffdf54d9e266ae3d72459 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -467,6 +467,26 @@ public class ServerChunkCache extends ChunkSource { + public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { + this.distanceManager.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); + } ++ ++ public boolean markUrgent(ChunkPos coords) { ++ return this.distanceManager.markUrgent(coords); ++ } ++ ++ public boolean markHighPriority(ChunkPos coords, int priority) { ++ return this.distanceManager.markHighPriority(coords, priority); ++ } ++ ++ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { ++ this.distanceManager.markAreaHighPriority(center, priority, radius); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkPos center, int radius) { ++ this.distanceManager.clearAreaPriorityTickets(center, radius); ++ } ++ ++ public void clearPriorityTickets(ChunkPos coords) { ++ this.distanceManager.clearPriorityTickets(coords); ++ } + // Paper end + + @Nullable +@@ -505,6 +525,8 @@ public class ServerChunkCache extends ChunkSource { + + if (!completablefuture.isDone()) { // Paper + // Paper start - async chunk io/loading ++ ChunkPos pair = new ChunkPos(x1, z1); ++ this.distanceManager.markUrgent(pair); + this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); + // Paper end +@@ -513,6 +535,8 @@ public class ServerChunkCache extends ChunkSource { + this.mainThreadProcessor.managedBlock(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug + this.level.timings.syncChunkLoad.stopTiming(); // Paper ++ this.distanceManager.clearPriorityTickets(pair); // Paper ++ this.distanceManager.clearUrgent(pair); // Paper + } // Paper + ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { + return ichunkaccess1; +@@ -565,10 +589,12 @@ public class ServerChunkCache extends ChunkSource { + if (flag && !currentlyUnloading) { + // CraftBukkit end + this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); ++ if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper + if (this.chunkAbsent(playerchunk, l)) { + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + + gameprofilerfiller.push("chunkLoad"); ++ distanceManager.delayDistanceManagerTick = false; // Paper - ensure this is never false + this.runDistanceManagerUpdates(); + playerchunk = this.getVisibleChunkIfPresent(k); + gameprofilerfiller.pop(); +@@ -577,8 +603,13 @@ public class ServerChunkCache extends ChunkSource { + } + } + } +- +- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); ++ // Paper start ++ CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); ++ if (isUrgent) { ++ future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); ++ } ++ return future; ++ // Paper end + } + + private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { +@@ -630,6 +661,7 @@ public class ServerChunkCache extends ChunkSource { + } + + public boolean runDistanceManagerUpdates() { // Paper - private -> public ++ if (distanceManager.delayDistanceManagerTick) return false; // Paper + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = this.chunkMap.promoteChunkMap(); + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 8e4cef60b760be385df81a74834d026f856a78c5..c5717f45a0110492aad41f21cc06fb8cbeb1f791 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -73,6 +73,7 @@ import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket; + import net.minecraft.network.protocol.game.ServerboundClientInformationPacket; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.PlayerAdvancements; + import net.minecraft.server.network.ServerGamePacketListenerImpl; +@@ -185,6 +186,12 @@ public class ServerPlayer extends Player implements ContainerListener { + private int lastRecordedArmor = Integer.MIN_VALUE; + private int lastRecordedLevel = Integer.MIN_VALUE; + private int lastRecordedExperience = Integer.MIN_VALUE; ++ public long lastHighPriorityChecked; // Paper ++ public void forceCheckHighPriority() { ++ lastHighPriorityChecked = -1; ++ getLevel().getChunkSource().chunkMap.checkHighPriorityChunks(this); ++ } ++ public boolean isRealPlayer; // Paper + private float lastSentHealth = -1.0E8F; + private int lastSentFood = -99999999; + private boolean lastFoodSaturationZero = true; +@@ -272,6 +279,21 @@ public class ServerPlayer extends Player implements ContainerListener { + this.maxHealthCache = this.getMaxHealth(); + this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + } ++ // Paper start ++ public BlockPos getPointInFront(double inFront) { ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yRot+90)); // MC rotates yaw 90 for some odd reason ++ final double x = getX() + inFront * Math.cos(rads); ++ final double z = getZ() + inFront * Math.sin(rads); ++ return new BlockPos(x, getY(), z); ++ } ++ ++ public ChunkPos getChunkInFront(double inFront) { ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yRot+90)); // MC rotates yaw 90 for some odd reason ++ final double x = getX() + (inFront * 16) * Math.cos(rads); ++ final double z = getZ() + (inFront * 16) * Math.sin(rads); ++ return new ChunkPos(Mth.floor(x) >> 4, Mth.floor(z) >> 4); ++ } ++ // Paper end + + // Yes, this doesn't match Vanilla, but it's the best we can do for now. + // If this is an issue, PRs are welcome +@@ -619,6 +641,7 @@ public class ServerPlayer extends Player implements ContainerListener { + if (valid && !this.isSpectator() || this.level.hasChunkAt(this.blockPosition())) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } ++ if (valid && isAlive() && connection != null) ((ServerLevel)level).getChunkSource().chunkMap.checkHighPriorityChunks(this); // Paper + + for (int i = 0; i < this.inventory.getContainerSize(); ++i) { + ItemStack itemstack = this.inventory.getItem(i); +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index c6b5f32153b63ac92df9c4b31b8de168481f79f2..c0bfe136ccb9ad4fc0f8ccdd703254205213ec8e 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -8,6 +8,7 @@ public final class Ticket implements Comparable> { + private final int ticketLevel; + public final T key; public final T getObjectReason() { return this.key; } // Paper - OBFHELPER + private long createdTick; public final long getCreationTick() { return this.createdTick; } // Paper - OBFHELPER ++ public int priority = 0; // Paper + + protected Ticket(TicketType type, int level, T argument) { + this.type = type; +@@ -56,6 +57,7 @@ public final class Ticket implements Comparable> { + return this.ticketLevel; + } + ++ public final void setCurrentTick(long i) { this.setCreatedTick(i); } // Paper - OBFHELPER + protected void setCreatedTick(long tickCreated) { + this.createdTick = tickCreated; + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 583587457790df826a8a3239a4bd1d0f1dcab1da..2444f6f676db543509b14e8c882491dc3f41b264 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -28,6 +28,8 @@ public class TicketType { + public static final TicketType PLUGIN_TICKET = create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper + public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper ++ public static final TicketType PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper ++ public static final TicketType URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper + + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3f416479e23c60ec5b4b779cce9ab62c74865ac8..0625bc7ffd07b66b27176fe62ae3061aa7c67df2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1528,6 +1528,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + this.awaitingTeleportTime = this.tickCount; + this.player.absMoveTo(d0, d1, d2, f, f1); ++ this.player.forceCheckHighPriority(); // Paper + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 8e00747c1a717836d12a43aa48d667bf801167b0..168895dab31a0d5356eb96f2642399a1c99fccab 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -271,8 +271,8 @@ public abstract class PlayerList { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; + playerChunkMap.getChunkDistanceManager().addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.toLong()); +- worldserver1.getChunkSource().runDistanceManagerUpdates(); +- worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { ++ worldserver1.getChunkSource().markAreaHighPriority(pos, 28, 3); ++ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { + ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()); + if (updatingChunk != null) { + return updatingChunk.getEntityTickingFuture(); +@@ -692,6 +692,7 @@ public abstract class PlayerList { + SocketAddress socketaddress = loginlistener.connection.getRemoteAddress(); + + ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile, new ServerPlayerGameMode(this.server.getLevel(Level.OVERWORLD))); ++ entity.isRealPlayer = true; // Paper + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress()); + +@@ -898,6 +899,7 @@ public abstract class PlayerList { + // CraftBukkit end + + worldserver1.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, new ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper ++ entityplayer1.forceCheckHighPriority(); // Player + while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < 256.0D) { + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); + } +diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +index 7ccf830146c252cff8e22553d293e02d4b53dad8..4a5f318adf5bc2ca1c3fab5d173a99cddd77ab85 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkPos.java ++++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +@@ -104,6 +104,7 @@ public class ChunkPos { + return "[" + this.x + ", " + this.z + "]"; + } + ++ public final BlockPos asPosition() { return getWorldPosition(); } // Paper - OBFHELPER + public BlockPos getWorldPosition() { + return new BlockPos(this.getMinBlockX(), 0, this.getMinBlockZ()); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 4fc44390f432ef13c9952aa22bbb29bc8bf47975..7261e22a71d219efe0949a08c5d3f10747759469 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2523,6 +2523,10 @@ public class CraftWorld implements World { + return future; + } + ++ if (!urgent) { ++ // if not urgent, at least use a slightly boosted priority ++ world.getChunkSource().markHighPriority(new ChunkPos(x, z), 1); ++ } + return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index eb366396820c9b6731469df4198e0884a431a77c..610eabd2e93f9efccee810c3b5a314bc3cc649d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -60,6 +60,7 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.ServerGamePacketListenerImpl; + import net.minecraft.server.players.UserWhiteListEntry; ++import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.LivingEntity; +@@ -69,6 +70,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.item.enchantment.Enchantments; ++import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameType; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.entity.SignBlockEntity; +@@ -848,6 +850,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); + } + ++ // Paper start ++ @Override ++ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) { ++ ((CraftWorld)loc.getWorld()).getHandle().getChunkSource().markAreaHighPriority(new ChunkPos(Mth.floor(loc.getX()) >> 4, Mth.floor(loc.getZ()) >> 4), 28, 3); // Paper - load area high priority ++ return super.teleportAsync(loc, cause); ++ } ++ // Paper end ++ + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + Preconditions.checkArgument(location != null, "location"); diff --git a/Remapped-Spigot-Server-Patches/0485-Optimize-sending-packets-to-nearby-locations-sounds-.patch b/Remapped-Spigot-Server-Patches/0485-Optimize-sending-packets-to-nearby-locations-sounds-.patch new file mode 100644 index 000000000..e8add9864 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0485-Optimize-sending-packets-to-nearby-locations-sounds-.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 23 May 2020 17:03:41 -0400 +Subject: [PATCH] Optimize sending packets to nearby locations (sounds/effects) + +Instead of using the entire world or player list, use the distance +maps to only iterate players who are even seeing the chunk the packet +is originating from. + +This will drastically cut down on packet sending cost for worlds with +lots of players in them. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 168895dab31a0d5356eb96f2642399a1c99fccab..713cc88dd067c0d918f253b1845f42c0d9eb920f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1149,16 +1149,40 @@ public abstract class PlayerList { + } + + public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { +- for (int i = 0; i < this.players.size(); ++i) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(i); ++ ServerLevel world = null; ++ if (player != null && player.level instanceof ServerLevel) { ++ world = (ServerLevel) player.level; ++ } + +- // CraftBukkit start - Test if player receiving packet can see the source of the packet +- if (player != null && player instanceof ServerPlayer && !entityplayer.getBukkitEntity().canSee(((ServerPlayer) player).getBukkitEntity())) { +- continue; ++ // Paper start ++ if (world == null) { ++ world = server.getLevel(worldKey); ++ } ++ ChunkMap chunkMap = world != null ? world.getChunkSource().chunkMap : null; ++ Object[] backingSet; ++ if (chunkMap == null) { ++ // Really shouldn't happen... ++ backingSet = world != null ? world.players.toArray() : players.toArray(); ++ } else { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearbyPlayers = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.fastFloor(x) >> 4, MCUtil.fastFloor(z) >> 4); ++ if (nearbyPlayers == null) { ++ return; + } ++ backingSet = nearbyPlayers.getBackingSet(); ++ } ++ ++ for (Object object : backingSet) { ++ if (!(object instanceof ServerPlayer)) continue; ++ ServerPlayer entityplayer = (ServerPlayer) object; ++ // Paper end ++ ++ // CraftBukkit start - Test if player receiving packet can see the source of the packet ++ //if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { // Paper ++ //continue; // Paper ++ //} // Paper + // CraftBukkit end + +- if (entityplayer != player && entityplayer.level.dimension() == worldKey) { ++ if (entityplayer != player && entityplayer.level.dimension() == worldKey && (!(player instanceof ServerPlayer) || entityplayer.getBukkitEntity().canSee(((ServerPlayer) player).getBukkitEntity()))) { // Paper + double d4 = x - entityplayer.getX(); + double d5 = y - entityplayer.getY(); + double d6 = z - entityplayer.getZ(); diff --git a/Remapped-Spigot-Server-Patches/0486-Improve-Chunk-Status-Transition-Speed.patch b/Remapped-Spigot-Server-Patches/0486-Improve-Chunk-Status-Transition-Speed.patch new file mode 100644 index 000000000..38e09f970 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0486-Improve-Chunk-Status-Transition-Speed.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 23:32:14 -0400 +Subject: [PATCH] Improve Chunk Status Transition Speed + +When a chunk is loaded from disk that has already been generated, +the server has to promote the chunk through the system to reach +it's current desired status level. + +This results in every single status transition going from the main thread +to the world gen threads, only to discover it has no work it actually +needs to do.... and then it returns back to main. + +This back and forth costs a lot of time and can really delay chunk loads +when the server is under high TPS due to their being a lot of time in +between chunk load times, as well as hogs up the chunk threads from doing +actual generation and light work. + +Additionally, the whole task system uses a lot of CPU on the server threads anyways. + +So by optimizing status transitions for status's that are already complete, +we can run them to the desired level while on main thread (where it has +to happen anyways) instead of ever jumping to world gen thread. + +This will improve chunk loading effeciency to be reduced down to the following +scenario / path: + +1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue +2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread +3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue +4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks) +5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task +6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done +7) MAIN: Task returns to main, finish processing to FULL/TICKING status + +Previously would have hopped to SERVER around 12+ times there extra. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index ce320672d7602c94dd75ad857435dca6ac3bab56..8260636da673ef095728c208db2d6237bab2db19 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -83,6 +83,13 @@ public class ChunkHolder { + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + } + // Paper end - optimise isOutsideOfRange ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ public boolean canAdvanceStatus() { ++ ChunkStatus status = getChunkHolderStatus(); ++ ChunkAccess chunk = getAvailableChunkNow(); ++ return chunk != null && (status == null || chunk.getStatus().isAtLeastStatus(getNextStatus(status))); ++ } ++ // Paper end + + // Paper start - no-tick view distance + public final LevelChunk getSendingChunk() { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7a1f6d1807757a43a7aa471db651404c06720820..acc566d14926dcf9e88f3e0837884e4c823d777c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -792,7 +792,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return either.mapLeft((list) -> { + return (LevelChunk) list.get(list.size() / 2); + }); +- }, this.mainThreadExecutor); ++ }, this.mainInvokingExecutor); // Paper + } + + @Nullable +@@ -1142,7 +1142,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ChunkAccess ichunkaccess = (ChunkAccess) optional.get(); + + if (ichunkaccess.getStatus().isOrAfter(requiredStatus)) { +- CompletableFuture completablefuture1; ++ CompletableFuture> completablefuture1; // Paper + + if (requiredStatus == ChunkStatus.LIGHT) { + completablefuture1 = this.scheduleChunkGeneration(holder, requiredStatus); +@@ -1158,7 +1158,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return this.scheduleChunkGeneration(holder, requiredStatus); + } + } +- }, this.mainThreadExecutor); ++ }, this.mainInvokingExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainInvokingExecutor); // Paper - optimize chunk status progression without jumping through thread pool - ensure main + } + } + +@@ -1279,6 +1279,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); + }); + }, (runnable) -> { ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ if (holder.canAdvanceStatus()) { ++ this.mainInvokingExecutor.execute(runnable); ++ return; ++ } ++ // Paper end + this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); + }); + } diff --git a/Remapped-Spigot-Server-Patches/0487-Fix-villager-trading-demand-MC-163962.patch b/Remapped-Spigot-Server-Patches/0487-Fix-villager-trading-demand-MC-163962.patch new file mode 100644 index 000000000..65b79cf45 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0487-Fix-villager-trading-demand-MC-163962.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 5 Jun 2020 20:02:04 -0500 +Subject: [PATCH] Fix villager trading demand - MC-163962 + +Prevent demand from going negative and tending to negative infinity + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index 9ce1c1092970618a204f87c673144152afbade99..fa74813e0fe76612023830b2fc41d41aa0b4f10e 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -109,7 +109,7 @@ public class MerchantOffer { + } + + public void updateDemand() { +- this.demand = this.demand + this.uses - (this.maxUses - this.uses); ++ this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper + } + + public ItemStack assemble() { diff --git a/Remapped-Spigot-Server-Patches/0488-Maps-shouldn-t-load-chunks.patch b/Remapped-Spigot-Server-Patches/0488-Maps-shouldn-t-load-chunks.patch new file mode 100644 index 000000000..186fc75fa --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0488-Maps-shouldn-t-load-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 7 Jun 2020 21:43:42 +0100 +Subject: [PATCH] Maps shouldn't load chunks + +Previously maps would load all chunks in a certain radius depending on + their scale when trying to update their content. This would result in + main thread chunk loads when they weren't really necessary, especially + on low view distances or "slow" async chunk loads after teleports or + other prioritisation. + + This changes it to only try to render already loaded chunks based on + the assumption that the chunks around the player will get loaded + eventually anyways and that maps will get checked for update every + five ticks that movement occur in anyways. + +diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index 1736d2eb33e5f2221210a0a4f3ceb8905555a162..05759dc6edaa790a5e3f2ca2e0ae27e53cfa4397 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -119,9 +119,9 @@ public class MapItem extends ComplexItem { + int k2 = (j / i + k1 - 64) * i; + int l2 = (k / i + l1 - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2)); ++ LevelChunk chunk = world.getChunkIfLoaded(new BlockPos(k2, 0, l2)); // Paper - Maps shouldn't load chunks + +- if (!chunk.isEmpty()) { ++ if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks + ChunkPos chunkcoordintpair = chunk.getPos(); + int i3 = k2 & 15; + int j3 = l2 & 15; diff --git a/Remapped-Spigot-Server-Patches/0489-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/Remapped-Spigot-Server-Patches/0489-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch new file mode 100644 index 000000000..356dcc464 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0489-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 7 Jun 2020 19:25:13 -0400 +Subject: [PATCH] Use seed based lookup for Treasure Maps - Fixes lag from + carto/sunken maps + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bf4e50cd1d561456c033cda2d5c5487c5e3fe1eb..61aee2c109614a014149ae5a15ad2a28c796cb9d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -415,8 +415,8 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.worldDataServer.setThundering(thundering); + } + +- @Override +- public Biome getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) { ++ public Biome getBiomeBySeed(int i, int j, int k) { return getUncachedNoiseBiome(i, j, k); } // Paper - OBFHELPER ++ @Override public Biome getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) { + return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(biomeX, biomeY, biomeZ); + } + +diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index 05759dc6edaa790a5e3f2ca2e0ae27e53cfa4397..550892bc769a58991583b16295a72a162ceea788 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -252,7 +252,7 @@ public class MapItem extends ComplexItem { + + for (l = 0; l < 128 * i; ++l) { + for (i1 = 0; i1 < 128 * i; ++i1) { +- abiomebase[l * 128 * i + i1] = worldserver.getBiome(new BlockPos((j / i - 64) * i + i1, 0, (k / i - 64) * i + l)); ++ abiomebase[l * 128 * i + i1] = worldserver.getBiomeBySeed((j / i - 64) * i + i1, 0, (k / i - 64) * i + l); // Paper + } + } + diff --git a/Remapped-Spigot-Server-Patches/0490-Optimize-Bit-Operations-by-inlining.patch b/Remapped-Spigot-Server-Patches/0490-Optimize-Bit-Operations-by-inlining.patch new file mode 100644 index 000000000..f1615b3c0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0490-Optimize-Bit-Operations-by-inlining.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 4 Jun 2020 02:24:49 -0400 +Subject: [PATCH] Optimize Bit Operations by inlining + +Inline bit operations and reduce instruction count to make these hot +operations faster + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 595abf528a7862478100770987906af1b13439fe..727af6ac84075db87615ebac51a024e6376fa3cb 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -31,14 +31,16 @@ public class BlockPos extends Vec3i { + }).stable(); + private static final Logger LOGGER = LogManager.getLogger(); + public static final BlockPos ZERO = new BlockPos(0, 0, 0); +- private static final int PACKED_X_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000)); +- private static final int PACKED_Z_LENGTH = BlockPos.PACKED_X_LENGTH; +- private static final int PACKED_Y_LENGTH = 64 - BlockPos.PACKED_X_LENGTH - BlockPos.PACKED_Z_LENGTH; +- private static final long PACKED_X_MASK = (1L << BlockPos.PACKED_X_LENGTH) - 1L; +- private static final long PACKED_Y_MASK = (1L << BlockPos.PACKED_Y_LENGTH) - 1L; +- private static final long PACKED_Z_MASK = (1L << BlockPos.PACKED_Z_LENGTH) - 1L; +- private static final int Z_OFFSET = BlockPos.PACKED_Y_LENGTH; +- private static final int X_OFFSET = BlockPos.PACKED_Y_LENGTH + BlockPos.PACKED_Z_LENGTH; ++ // Paper start - static constants ++ private static final int PACKED_X_LENGTH = 26; ++ private static final int PACKED_Z_LENGTH = 26; ++ private static final int PACKED_Y_LENGTH = 12; ++ private static final long PACKED_X_MASK = 67108863; ++ private static final long PACKED_Y_MASK = 4095; ++ private static final long PACKED_Z_MASK = 67108863; ++ private static final int Z_OFFSET = 12; ++ private static final int X_OFFSET = 38; ++ // Paper end + + public BlockPos(int x, int y, int z) { + super(x, y, z); +@@ -60,28 +62,29 @@ public class BlockPos extends Vec3i { + this(pos.getX(), pos.getY(), pos.getZ()); + } + ++ public static long getAdjacent(int baseX, int baseY, int baseZ, Direction enumdirection) { return asLong(baseX + enumdirection.getStepX(), baseY + enumdirection.getStepY(), baseZ + enumdirection.getStepZ()); } // Paper + public static long offset(long value, Direction direction) { + return offset(value, direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + + public static long offset(long value, int x, int y, int z) { +- return asLong(getX(value) + x, getY(value) + y, getZ(value) + z); ++ return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z); // Paper - simplify/inline + } + + public static int getX(long packedPos) { +- return (int) (packedPos << 64 - BlockPos.X_OFFSET - BlockPos.PACKED_X_LENGTH >> 64 - BlockPos.PACKED_X_LENGTH); ++ return (int) (packedPos >> 38); // Paper - simplify/inline + } + + public static int getY(long packedPos) { +- return (int) (packedPos << 64 - BlockPos.PACKED_Y_LENGTH >> 64 - BlockPos.PACKED_Y_LENGTH); ++ return (int) ((packedPos << 52) >> 52); // Paper - simplify/inline + } + + public static int getZ(long packedPos) { +- return (int) (packedPos << 64 - BlockPos.Z_OFFSET - BlockPos.PACKED_Z_LENGTH >> 64 - BlockPos.PACKED_Z_LENGTH); ++ return (int) ((packedPos << 26) >> 38); // Paper - simplify/inline + } + + public static BlockPos of(long packedPos) { +- return new BlockPos(getX(packedPos), getY(packedPos), getZ(packedPos)); ++ return new BlockPos((int) (packedPos >> 38), (int) ((packedPos << 52) >> 52), (int) ((packedPos << 26) >> 38)); // Paper - simplify/inline + } + + public long asLong() { +@@ -90,12 +93,7 @@ public class BlockPos extends Vec3i { + + public static long asLong(int x, int y, int z) { return asLong(x, y, z); } // Paper - OBFHELPER + public static long asLong(int x, int y, int z) { +- long l = 0L; +- +- l |= ((long) x & BlockPos.PACKED_X_MASK) << BlockPos.X_OFFSET; +- l |= ((long) y & BlockPos.PACKED_Y_MASK) << 0; +- l |= ((long) z & BlockPos.PACKED_Z_MASK) << BlockPos.Z_OFFSET; +- return l; ++ return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); // Paper - inline constants and simplify + } + + public static long getFlatIndex(long y) { +diff --git a/src/main/java/net/minecraft/core/SectionPos.java b/src/main/java/net/minecraft/core/SectionPos.java +index 700e0d7b132242bd65d13ec61c1f7036905b2767..c4bf1bc383fbdfb8ec997883aececde38206c7dd 100644 +--- a/src/main/java/net/minecraft/core/SectionPos.java ++++ b/src/main/java/net/minecraft/core/SectionPos.java +@@ -19,7 +19,7 @@ public class SectionPos extends Vec3i { + } + + public static SectionPos of(BlockPos pos) { +- return new SectionPos(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ())); ++ return new SectionPos(pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4); // Paper + } + + public static SectionPos of(ChunkPos chunkPos, int y) { +@@ -31,15 +31,23 @@ public class SectionPos extends Vec3i { + } + + public static SectionPos of(long packed) { +- return new SectionPos(x(packed), y(packed), z(packed)); ++ return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); // Paper + } + + public static long offset(long packed, Direction direction) { + return offset(packed, direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + ++ // Paper start ++ public static long getAdjacentFromBlockPos(int x, int y, int z, Direction enumdirection) { ++ return (((long) ((x >> 4) + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getStepY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getStepZ()) & 4194303L) << 20); ++ } ++ public static long getAdjacentFromSectionPos(int x, int y, int z, Direction enumdirection) { ++ return (((long) (x + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getStepY()) & 1048575L)) | (((long) (z + enumdirection.getStepZ()) & 4194303L) << 20); ++ } ++ // Paper end + public static long offset(long packed, int x, int y, int z) { +- return asLong(x(packed) + x, y(packed) + y, z(packed) + z); ++ return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static int blockToSectionCoord(int coord) { +@@ -51,11 +59,7 @@ public class SectionPos extends Vec3i { + } + + public static short sectionRelativePos(BlockPos pos) { +- int i = sectionRelative(pos.getX()); +- int j = sectionRelative(pos.getY()); +- int k = sectionRelative(pos.getZ()); +- +- return (short) (i << 8 | k << 4 | j << 0); ++ return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15); // Paper - simplify/inline + } + + public static int sectionRelativeX(short packedLocalPos) { +@@ -114,16 +118,16 @@ public class SectionPos extends Vec3i { + return this.getZ(); + } + +- public int minBlockX() { +- return this.x() << 4; ++ public final int minBlockX() { // Paper ++ return this.getX() << 4; // Paper + } + +- public int minBlockY() { +- return this.y() << 4; ++ public final int minBlockY() { // Paper ++ return this.getY() << 4; // Paper + } + +- public int minBlockZ() { +- return this.z() << 4; ++ public final int minBlockZ() { // Paper ++ return this.getZ() << 4; // Paper + } + + public int maxBlockX() { +@@ -138,8 +142,10 @@ public class SectionPos extends Vec3i { + return (this.z() << 4) + 15; + } + ++ public static long blockToSection(long i) { return blockToSection(i); } // Paper - OBFHELPER + public static long blockToSection(long blockPos) { +- return asLong(blockToSectionCoord(BlockPos.getX(blockPos)), blockToSectionCoord(BlockPos.getY(blockPos)), blockToSectionCoord(BlockPos.getZ(blockPos))); ++ // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); ++ return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static long getZeroNode(long pos) { +@@ -160,17 +166,18 @@ public class SectionPos extends Vec3i { + return new ChunkPos(this.x(), this.z()); + } + ++ // Paper start ++ public static long blockPosAsSectionLong(int i, int j, int k) { ++ return (((long) (i >> 4) & 4194303L) << 42) | (((long) (j >> 4) & 1048575L)) | (((long) (k >> 4) & 4194303L) << 20); ++ } ++ // Paper end ++ public static long asLong(int i, int j, int k) { return asLong(i, j, k); } // Paper - OBFHELPER + public static long asLong(int x, int y, int z) { +- long l = 0L; +- +- l |= ((long) x & 4194303L) << 42; +- l |= ((long) y & 1048575L) << 0; +- l |= ((long) z & 4194303L) << 20; +- return l; ++ return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + public long asLong() { +- return asLong(this.x(), this.y(), this.z()); ++ return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + public Stream blocksInside() { +@@ -178,18 +185,11 @@ public class SectionPos extends Vec3i { + } + + public static Stream cube(SectionPos center, int radius) { +- int j = center.x(); +- int k = center.y(); +- int l = center.z(); +- +- return betweenClosedStream(j - radius, k - radius, l - radius, j + radius, k + radius, l + radius); ++ return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius); // Paper - simplify/inline + } + + public static Stream aroundChunk(ChunkPos center, int radius) { +- int j = center.x; +- int k = center.z; +- +- return betweenClosedStream(j - radius, 0, k - radius, j + radius, 15, k + radius); ++ return betweenClosedStream(center.x - radius, 0, center.z - radius, center.x + radius, 15, center.z + radius); // Paper - simplify/inline + } + + public static Stream betweenClosedStream(final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ) { diff --git a/Remapped-Spigot-Server-Patches/0491-Optimize-Light-Engine.patch b/Remapped-Spigot-Server-Patches/0491-Optimize-Light-Engine.patch new file mode 100644 index 000000000..53f26fe55 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0491-Optimize-Light-Engine.patch @@ -0,0 +1,1464 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 4 Jun 2020 22:43:29 -0400 +Subject: [PATCH] Optimize Light Engine + +Massive update to light to improve performance and chunk loading/generation. + +1) Massive bit packing/unpacking optimizations and inlining. + A lot of performance has to do with constant packing and unpacking of bits. + We now inline a most bit operations, and re-use base x/y/z bits in many places. + This helps with cpu level processing to just do all the math at once instead + of having to jump in and out of function calls. + + This much logic also is likely over the JVM Inline limit for JIT too. +2) Applied a few of JellySquid's Phosphor mod optimizations such as + - ensuring we don't notify neighbor chunks when neighbor chunk doesn't need to be notified + - reduce hasLight checks in initializing light, and prob some more, they are tagged JellySquid where phosphor influence was used. +3) Optimize hot path accesses to getting updating chunk to have less branching +4) Optimize getBlock accesses to have less branching, and less unpacking +5) Have a separate urgent bucket for chunk light tasks. These tasks will always cut in line over non blocking light tasks. +6) Retain chunk priority while light tasks are enqueued. So if a task comes in at high priority but the queue is full + of tasks already at a lower priority, before the task was simply added to the end. Now it can cut in line to the front. + this applies for both urgent and non urgent tasks. +7) Buffer non urgent tasks even if queueUpdate is called multiple times to improve efficiency. +8) Fix NPE risk that crashes server in getting nibble data + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 8260636da673ef095728c208db2d6237bab2db19..9e3629884709126574a52ad44fe7523f01dbcce9 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -753,6 +753,7 @@ public class ChunkHolder { + ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; + } + chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); ++ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), getCurrentPriority(), priority); + } + if (getCurrentPriority() != priority) { + this.onLevelChange.onLevelChange(this.pos, this::getCurrentPriority, priority, this::setPriority); // use preferred priority +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index acc566d14926dcf9e88f3e0837884e4c823d777c..f4dd30c8b3326db72d3b3068ee2291de6f15de7c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -98,6 +98,7 @@ import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; ++import net.minecraft.world.level.storage.PrimaryLevelData; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; +@@ -328,6 +329,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end + ++ private final java.util.concurrent.ExecutorService lightThread; + public ChunkMap(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager definedstructuremanager, Executor workerExecutor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.getDimensionPath(worldserver.dimension()), "region"), dataFixer, flag); + //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning +@@ -359,7 +361,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ProcessorHandle mailbox = ProcessorHandle.of("main", mainThreadExecutor::tell); + + this.progressListener = worldGenerationProgressListener; +- ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(workerExecutor, "light"); // Paper ++ // Paper start - use light thread ++ ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(lightThread = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { ++ Thread thread = new Thread(r); ++ thread.setName(((PrimaryLevelData)level.getLevelData()).getLevelName() + " - Light"); ++ thread.setDaemon(true); ++ thread.setPriority(Thread.NORM_PRIORITY+1); ++ return thread; ++ }), "light"); ++ // Paper end + + this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), workerExecutor, Integer.MAX_VALUE); + this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); +@@ -705,6 +715,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end + } + ++ protected final IntSupplier getPrioritySupplier(long i) { return getChunkQueueLevel(i); } // Paper - OBFHELPER + protected IntSupplier getChunkQueueLevel(long pos) { + return () -> { + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); +@@ -832,6 +843,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + @Override + public void close() throws IOException { + try { ++ this.lightThread.shutdown(); // Paper + this.queueSorter.close(); + this.level.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line + this.poiManager.close(); +diff --git a/src/main/java/net/minecraft/server/level/SectionTracker.java b/src/main/java/net/minecraft/server/level/SectionTracker.java +index 125ae965bb539ae24c60cb992eb7cfc35fd65b25..9fa6c290373b0e0cc0e7ed84c0c2363c8ad14dd3 100644 +--- a/src/main/java/net/minecraft/server/level/SectionTracker.java ++++ b/src/main/java/net/minecraft/server/level/SectionTracker.java +@@ -1,6 +1,5 @@ + package net.minecraft.server.level; + +-import net.minecraft.core.SectionPos; + import net.minecraft.world.level.lighting.DynamicGraphMinFixedPoint; + + public abstract class SectionTracker extends DynamicGraphMinFixedPoint { +@@ -16,14 +15,20 @@ public abstract class SectionTracker extends DynamicGraphMinFixedPoint { + + @Override + protected void checkNeighborsAfterUpdate(long id, int level, boolean decrease) { ++ // Paper start ++ int x = (int) (id >> 42); ++ int y = (int) (id << 44 >> 44); ++ int z = (int) (id << 22 >> 42); ++ // Paper end + for (int k = -1; k <= 1; ++k) { + for (int l = -1; l <= 1; ++l) { + for (int i1 = -1; i1 <= 1; ++i1) { +- long j1 = SectionPos.offset(id, k, l, i1); ++ if (k == 0 && l == 0 && i1 == 0) continue; // Paper ++ long j1 = (((long) (x + k) & 4194303L) << 42) | (((long) (y + l) & 1048575L)) | (((long) (z + i1) & 4194303L) << 20); // Paper + +- if (j1 != id) { ++ //if (j1 != i) { // Paper - checked above + this.checkNeighbor(id, j1, level, decrease); +- } ++ //} // Paper + } + } + } +@@ -34,10 +39,15 @@ public abstract class SectionTracker extends DynamicGraphMinFixedPoint { + protected int getComputedLevel(long id, long excludedId, int maxLevel) { + int l = maxLevel; + ++ // Paper start ++ int x = (int) (id >> 42); ++ int y = (int) (id << 44 >> 44); ++ int z = (int) (id << 22 >> 42); ++ // Paper end + for (int i1 = -1; i1 <= 1; ++i1) { + for (int j1 = -1; j1 <= 1; ++j1) { + for (int k1 = -1; k1 <= 1; ++k1) { +- long l1 = SectionPos.offset(id, i1, j1, k1); ++ long l1 = (((long) (x + i1) & 4194303L) << 42) | (((long) (y + j1) & 1048575L)) | (((long) (z + k1) & 4194303L) << 20); // Paper + + if (l1 == id) { + l1 = Long.MAX_VALUE; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index f36badcafbad7fb4537ffdf54d9e266ae3d72459..7a615a18f1f297adfe7e046407a019d8933e9ed9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -1072,7 +1072,7 @@ public class ServerChunkCache extends ChunkSource { + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; + } else { +- ServerChunkCache.this.lightEngine.tryScheduleUpdate(); ++ ServerChunkCache.this.lightEngine.tryScheduleUpdate(); // Paper - not needed + return super.pollTask() || execChunkTask; // Paper + } + } finally { +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index cc4190b3a8904d1eaae0f542a3b3090583f5ff82..14835bfab300d305faee2db705d7386dc16427f5 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -16,6 +17,7 @@ import net.minecraft.util.thread.ProcessorMailbox; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LevelChunkSection; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -27,15 +29,149 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + private static final Logger LOGGER = LogManager.getLogger(); + private final ProcessorMailbox taskMailbox; +- private final ObjectList> lightTasks = new ObjectArrayList(); +- private final ChunkMap chunkMap; ++ // Paper start ++ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2; ++ ++ private boolean isChunkLightStatus(long pair) { ++ ChunkHolder playerChunk = playerChunkMap.getVisibleChunkIfPresent(pair); ++ if (playerChunk == null) { ++ return false; ++ } ++ ChunkStatus status = ChunkHolder.getStatus(playerChunk.getTicketLevel()); ++ return status != null && status.isAtLeastStatus(ChunkStatus.LIGHT); ++ } ++ ++ static class ChunkLightQueue { ++ public boolean shouldFastUpdate; ++ java.util.ArrayDeque pre = new java.util.ArrayDeque(); ++ java.util.ArrayDeque post = new java.util.ArrayDeque(); ++ ++ ChunkLightQueue(long chunk) {} ++ } ++ ++ static class PendingLightTask { ++ long chunkId; ++ IntSupplier priority; ++ Runnable pre; ++ Runnable post; ++ boolean fastUpdate; ++ ++ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) { ++ this.chunkId = chunkId; ++ this.priority = priority; ++ this.pre = pre; ++ this.post = post; ++ this.fastUpdate = fastUpdate; ++ } ++ } ++ ++ ++ // Retain the chunks priority level for queued light tasks ++ class LightQueue { ++ private int size = 0; ++ private final Long2ObjectLinkedOpenHashMap[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; ++ private final java.util.concurrent.ConcurrentLinkedQueue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentLinkedQueue priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private LightQueue() { ++ for (int i = 0; i < buckets.length; i++) { ++ buckets[i] = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ } ++ ++ public void changePriority(long pair, int currentPriority, int priority) { ++ this.priorityChanges.add(() -> { ++ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair); ++ if (remove != null) { ++ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove); ++ if (existing != null) { ++ remove.pre.addAll(existing.pre); ++ remove.post.addAll(existing.post); ++ } ++ } ++ }); ++ } ++ ++ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true)); ++ tryScheduleUpdate(); ++ } ++ ++ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false)); ++ } ++ public final void add(PendingLightTask update) { ++ int priority = update.priority.getAsInt(); ++ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new); ++ ++ if (update.pre != null) { ++ this.size++; ++ lightQueue.pre.add(update.pre); ++ } ++ if (update.post != null) { ++ this.size++; ++ lightQueue.post.add(update.post); ++ } ++ if (update.fastUpdate) { ++ lightQueue.shouldFastUpdate = true; ++ } ++ } ++ ++ public final boolean isEmpty() { ++ return this.size == 0 && this.pendingTasks.isEmpty(); ++ } ++ ++ public final int size() { ++ return this.size; ++ } ++ ++ public boolean poll(java.util.List pre, java.util.List post) { ++ PendingLightTask pending; ++ while ((pending = pendingTasks.poll()) != null) { ++ add(pending); ++ } ++ Runnable run; ++ while ((run = priorityChanges.poll()) != null) { ++ run.run(); ++ } ++ boolean hasWork = false; ++ Long2ObjectLinkedOpenHashMap[] buckets = this.buckets; ++ int priority = 0; ++ while (priority < MAX_PRIORITIES && !isEmpty()) { ++ Long2ObjectLinkedOpenHashMap bucket = buckets[priority]; ++ if (bucket.isEmpty()) { ++ priority++; ++ if (hasWork) { ++ return true; ++ } else { ++ continue; ++ } ++ } ++ ChunkLightQueue queue = bucket.removeFirst(); ++ this.size -= queue.pre.size() + queue.post.size(); ++ pre.addAll(queue.pre); ++ post.addAll(queue.post); ++ queue.pre.clear(); ++ queue.post.clear(); ++ hasWork = true; ++ if (queue.shouldFastUpdate) { ++ return true; ++ } ++ } ++ return hasWork; ++ } ++ } ++ ++ final LightQueue queue = new LightQueue(); ++ // Paper end ++ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper + private final ProcessorHandle> sorterMailbox; + private volatile int taskPerBatch = 5; + private final AtomicBoolean scheduled = new AtomicBoolean(); + + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { + super(chunkProvider, true, hasBlockLight); +- this.chunkMap = chunkStorage; ++ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper + this.sorterMailbox = executor; + this.taskMailbox = processor; + } +@@ -122,13 +258,9 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { +- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { +- this.lightTasks.add(Pair.of(stage, task)); +- if (this.lightTasks.size() >= this.taskPerBatch) { +- this.runUpdate(); +- } +- +- }, ChunkPos.asLong(x, z), completedLevelSupplier)); ++ // Paper start - replace method ++ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task); ++ // Paper end + } + + @Override +@@ -145,8 +277,19 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { + ChunkPos chunkcoordintpair = chunk.getPos(); + +- chunk.setLightCorrect(false); +- this.addTask(chunkcoordintpair.x, chunkcoordintpair.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { ++ // Paper start ++ //ichunkaccess.b(false); // Don't need to disable this ++ long pair = chunkcoordintpair.toLong(); ++ CompletableFuture future = new CompletableFuture<>(); ++ IntSupplier prioritySupplier = playerChunkMap.getPrioritySupplier(pair); ++ boolean[] skippedPre = {false}; ++ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> { ++ if (!isChunkLightStatus(pair)) { ++ future.complete(chunk); ++ skippedPre[0] = true; ++ return; ++ } ++ // Paper end + LevelChunkSection[] achunksection = chunk.getSections(); + + for (int i = 0; i < 16; ++i) { +@@ -164,55 +307,48 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + }); + } + +- this.chunkMap.releaseLightTicket(chunkcoordintpair); ++ // this.d.c(chunkcoordintpair); // Paper - move into post task below + }, () -> { + return "lightChunk " + chunkcoordintpair + " " + excludeBlocks; +- })); +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - merge the 2 together ++ }), () -> { ++ this.chunkMap.releaseLightTicket(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done ++ if (skippedPre[0]) return; // Paper - future's already complete + chunk.setLightCorrect(true); + super.retainData(chunkcoordintpair, false); +- return chunk; +- }, (runnable) -> { +- this.addTask(chunkcoordintpair.x, chunkcoordintpair.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable); ++ // Paper start ++ future.complete(chunk); + }); ++ return future; ++ // Paper end + } + + public void tryScheduleUpdate() { +- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { ++ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper + this.taskMailbox.tell((() -> { // Paper - decompile error + this.runUpdate(); + this.scheduled.set(false); ++ tryScheduleUpdate(); // Paper - if we still have work to do, do it! + })); + } + + } + ++ // Paper start - replace impl ++ private final java.util.List pre = new java.util.ArrayList<>(); ++ private final java.util.List post = new java.util.ArrayList<>(); + private void runUpdate() { +- int i = Math.min(this.lightTasks.size(), this.taskPerBatch); +- ObjectListIterator> objectlistiterator = this.lightTasks.iterator(); +- +- Pair pair; +- int j; +- +- for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { +- pair = (Pair) objectlistiterator.next(); +- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { +- ((Runnable) pair.getSecond()).run(); +- } ++ if (queue.poll(pre, post)) { ++ pre.forEach(Runnable::run); ++ pre.clear(); ++ super.runUpdates(Integer.MAX_VALUE, true, true); ++ post.forEach(Runnable::run); ++ post.clear(); ++ } else { ++ // might have level updates to go still ++ super.runUpdates(Integer.MAX_VALUE, true, true); + } +- +- objectlistiterator.back(j); +- super.runUpdates(Integer.MAX_VALUE, true, true); +- +- for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { +- pair = (Pair) objectlistiterator.next(); +- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { +- ((Runnable) pair.getSecond()).run(); +- } +- +- objectlistiterator.remove(); +- } +- ++ // Paper end + } + + public void setTaskPerBatch(int taskBatchSize) { +diff --git a/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java b/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java +index c763aa0c0cf49dd844af94a820103258b49021ae..195535835bdc63f7cfdebeaa957dde590262ea42 100644 +--- a/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java ++++ b/src/main/java/net/minecraft/util/thread/ProcessorMailbox.java +@@ -110,7 +110,8 @@ public class ProcessorMailbox implements ProcessorHandle, AutoCloseable, R + + } + +- @Override ++ ++ public final void queue(T t0) { tell(t0); } @Override // Paper - OBFHELPER + public void tell(T message) { + this.queue.push(message); + this.registerForExecution(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +index 83e9d8bff9a31fe13a0e22445cd6eecb7abe8561..1e8ce9894fd0a121da83020c6064b7833af1c5f2 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +@@ -11,6 +11,13 @@ import net.minecraft.server.MCUtil; + public class DataLayer { + + // Paper start ++ public static final DataLayer EMPTY_NIBBLE_ARRAY = new DataLayer() { ++ @Override ++ public byte[] getData() { ++ throw new IllegalStateException(); ++ } ++ }; ++ public long lightCacheKey = Long.MIN_VALUE; + public static byte[] EMPTY_NIBBLE = new byte[2048]; + private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); + private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8)); +diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +index 709fc42057f8a0282c3c942067e63abb874d9042..eaaaecb67966e5e366cf59f92674c82d1d87552e 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +@@ -23,9 +23,11 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int k = (int) ((blockPos << 52) >> 52); ++ int l = (int) ((blockPos << 26) >> 38); ++ // Paper end + BlockGetter iblockaccess = this.chunkSource.getChunkForLighting(j >> 4, l >> 4); + + return iblockaccess != null ? iblockaccess.getLightEmission(this.pos.set(j, k, l)) : 0; +@@ -40,25 +42,33 @@ public final class BlockLightEngine extends LayerLightEngine= 15) { + return level; + } else { +- int l = Integer.signum(BlockPos.getX(targetId) - BlockPos.getX(sourceId)); +- int i1 = Integer.signum(BlockPos.getY(targetId) - BlockPos.getY(sourceId)); +- int j1 = Integer.signum(BlockPos.getZ(targetId) - BlockPos.getZ(sourceId)); ++ // Paper start - reuse math - credit to JellySquid for idea ++ int jx = (int) (targetId >> 38); ++ int jy = (int) ((targetId << 52) >> 52); ++ int jz = (int) ((targetId << 26) >> 38); ++ int ix = (int) (sourceId >> 38); ++ int iy = (int) ((sourceId << 52) >> 52); ++ int iz = (int) ((sourceId << 26) >> 38); ++ int l = Integer.signum(jx - ix); ++ int i1 = Integer.signum(jy - iy); ++ int j1 = Integer.signum(jz - iz); ++ // Paper end + Direction enumdirection = Direction.fromNormal(l, i1, j1); + + if (enumdirection == null) { + return 15; + } else { + //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded +- BlockState iblockdata = this.getStateAndOpacity(targetId, mutableint); +- +- if (mutableint.getValue() >= 15) { ++ BlockState iblockdata = this.getBlockOptimized(jx, jy, jz, mutableint); // Paper ++ int blockedLight = mutableint.getValue(); // Paper ++ if (blockedLight >= 15) { // Paper + return 15; + } else { +- BlockState iblockdata1 = this.getStateAndOpacity(sourceId, (MutableInt) null); ++ BlockState iblockdata1 = this.getBlockOptimized(ix, iy, iz); // Paper + VoxelShape voxelshape = this.getShape(iblockdata1, sourceId, enumdirection); + VoxelShape voxelshape1 = this.getShape(iblockdata, targetId, enumdirection.getOpposite()); + +- return Shapes.faceShapeOccludes(voxelshape, voxelshape1) ? 15 : level + Math.max(1, mutableint.getValue()); ++ return Shapes.faceShapeOccludes(voxelshape, voxelshape1) ? 15 : level + Math.max(1, blockedLight); // Paper + } + } + } +@@ -66,14 +76,19 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int y = (int) ((id << 52) >> 52); ++ int z = (int) ((id << 26) >> 38); ++ long k = SectionPos.blockPosAsSectionLong(x, y, z); ++ // Paper end + Direction[] aenumdirection = BlockLightEngine.DIRECTIONS; + int l = aenumdirection.length; + + for (int i1 = 0; i1 < l; ++i1) { + Direction enumdirection = aenumdirection[i1]; +- long j1 = BlockPos.offset(id, enumdirection); +- long k1 = SectionPos.blockToSection(j1); ++ long j1 = BlockPos.getAdjacent(x, y, z, enumdirection); // Paper ++ long k1 = SectionPos.blockToSection(j1); // Paper + + if (k == k1 || ((BlockLightSectionStorage) this.storage).storingLightForSection(k1)) { + this.checkNeighbor(id, j1, level, decrease); +@@ -98,27 +113,37 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long j1 = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ DataLayer nibblearray = this.storage.updating.getUpdatingOptimized(j1); ++ // Paper end + Direction[] aenumdirection = BlockLightEngine.DIRECTIONS; + int k1 = aenumdirection.length; + + for (int l1 = 0; l1 < k1; ++l1) { + Direction enumdirection = aenumdirection[l1]; +- long i2 = BlockPos.offset(id, enumdirection); ++ // Paper start ++ int newX = baseX + enumdirection.getStepX(); ++ int newY = baseY + enumdirection.getStepY(); ++ int newZ = baseZ + enumdirection.getStepZ(); ++ long i2 = BlockPos.asLong(newX, newY, newZ); + + if (i2 != excludedId) { +- long j2 = SectionPos.blockToSection(i2); ++ long j2 = SectionPos.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + DataLayer nibblearray1; + + if (j1 == j2) { + nibblearray1 = nibblearray; + } else { +- nibblearray1 = ((BlockLightSectionStorage) this.storage).getDataLayer(j2, true); ++ nibblearray1 = ((BlockLightSectionStorage) this.storage).updating.getUpdatingOptimized(j2); // Paper + } + + if (nibblearray1 != null) { +- int k2 = this.computeLevelFromNeighbor(i2, id, this.getLevel(nibblearray1, i2)); ++ int k2 = this.computeLevelFromNeighbor(i2, id, this.getNibbleLightInverse(nibblearray1, newX, newY, newZ)); // Paper + + if (l > k2) { + l = k2; +diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +index a1ad4d73ddaf6afe97a1f1ff7e0622b52fac8761..f771ef8d841567b421b6c0529af3f0713c79eb7c 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +@@ -1,8 +1,6 @@ + package net.minecraft.world.level.lighting; + + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +-import net.minecraft.core.BlockPos; +-import net.minecraft.core.SectionPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -15,10 +13,14 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage> 38); ++ int baseY = (int) ((blockPos << 52) >> 52); ++ int baseZ = (int) ((blockPos << 26) >> 38); ++ long j = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ DataLayer nibblearray = this.e_visible.lookup.apply(j); ++ return nibblearray == null ? 0 : nibblearray.get(baseX & 15, baseY & 15, baseZ & 15); ++ // Paper end + } + + public static final class BlockDataLayerStorageMap extends DataLayerStorageMap { +diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +index 54cca3b376e5ce02936edc8b9c17e67e17f07147..ed2ed6194670016086be580dc4514d5d3d1b235b 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java ++++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +@@ -7,13 +7,18 @@ import net.minecraft.world.level.chunk.DataLayer; + + public abstract class DataLayerStorageMap> { + +- private final long[] lastSectionKeys = new long[2]; +- private final DataLayer[] lastSections = new DataLayer[2]; ++ // private final long[] b = new long[2]; // Paper - unused ++ private final DataLayer[] lastSections = new DataLayer[]{DataLayer.EMPTY_NIBBLE_ARRAY, DataLayer.EMPTY_NIBBLE_ARRAY}; private final DataLayer[] cache = lastSections; // Paper - OBFHELPER + private boolean cacheEnabled; + protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data; // Paper - avoid copying light data + protected final boolean isVisible; // Paper - avoid copying light data +- java.util.function.Function lookup; // Paper - faster branchless lookup + ++ // Paper start - faster lookups with less branching, use interface to avoid boxing instead of Function ++ public final NibbleArrayAccess lookup; ++ public interface NibbleArrayAccess { ++ DataLayer apply(long id); ++ } ++ // Paper end + // Paper start - avoid copying light data + protected DataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, boolean isVisible) { + if (isVisible) { +@@ -21,12 +26,14 @@ public abstract class DataLayerStorageMap> { + } + this.data = data; + this.isVisible = isVisible; ++ // Paper end - avoid copying light data ++ // Paper start - faster lookups with less branching + if (isVisible) { + lookup = data::getVisibleAsync; + } else { +- lookup = data::getUpdating; ++ lookup = data.getUpdatingMap()::get; // jump straight the sub map + } +- // Paper end - avoid copying light data ++ // Paper end + this.clearCache(); + this.cacheEnabled = true; + } +@@ -36,7 +43,9 @@ public abstract class DataLayerStorageMap> { + public void copyDataLayer(long pos) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data + DataLayer updating = this.data.getUpdating(pos); // Paper - pool nibbles +- this.data.queueUpdate(pos, new DataLayer().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone ++ DataLayer nibblearray = new DataLayer().markPoolSafe(updating.getCloneIfSet()); // Paper ++ nibblearray.lightCacheKey = pos; // Paper ++ this.data.queueUpdate(pos, nibblearray); // Paper - avoid copying light data - pool safe clone + if (updating.cleaner != null) MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it + this.clearCache(); + } +@@ -45,34 +54,34 @@ public abstract class DataLayerStorageMap> { + return lookup.apply(chunkPos) != null; // Paper - avoid copying light data + } + +- @Nullable +- public final DataLayer getLayer(long chunkPos) { // Paper - final +- if (this.cacheEnabled) { +- for (int j = 0; j < 2; ++j) { +- if (chunkPos == this.lastSectionKeys[j]) { +- return this.lastSections[j]; +- } +- } +- } +- +- DataLayer nibblearray = lookup.apply(chunkPos); // Paper - avoid copying light data ++ // Paper start - less branching as we know we are using cache and updating ++ public final DataLayer getUpdatingOptimized(final long i) { // Paper - final ++ final DataLayer[] cache = this.cache; ++ if (cache[0].lightCacheKey == i) return cache[0]; ++ if (cache[1].lightCacheKey == i) return cache[1]; + ++ final DataLayer nibblearray = this.lookup.apply(i); // Paper - avoid copying light data + if (nibblearray == null) { + return null; + } else { +- if (this.cacheEnabled) { +- for (int k = 1; k > 0; --k) { +- this.lastSectionKeys[k] = this.lastSectionKeys[k - 1]; +- this.lastSections[k] = this.lastSections[k - 1]; +- } +- +- this.lastSectionKeys[0] = chunkPos; +- this.lastSections[0] = nibblearray; +- } +- ++ cache[1] = cache[0]; ++ cache[0] = nibblearray; + return nibblearray; + } + } ++ // Paper end ++ ++ @Nullable ++ public final DataLayer getLayer(final long chunkPos) { // Paper - final ++ // Paper start - optimize visible case or missed updating cases ++ if (this.cacheEnabled) { ++ // short circuit to optimized ++ return getUpdatingOptimized(chunkPos); ++ } ++ ++ return this.lookup.apply(chunkPos); ++ // Paper end ++ } + + @Nullable + public DataLayer removeLayer(long chunkPos) { +@@ -82,13 +91,14 @@ public abstract class DataLayerStorageMap> { + + public void setLayer(long pos, DataLayer data) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ data.lightCacheKey = pos; // Paper + this.data.queueUpdate(pos, data); // Paper - avoid copying light data + } + + public void clearCache() { + for (int i = 0; i < 2; ++i) { +- this.lastSectionKeys[i] = Long.MAX_VALUE; +- this.lastSections[i] = null; ++ // this.b[i] = Long.MAX_VALUE; // Paper - Unused ++ this.lastSections[i] = DataLayer.EMPTY_NIBBLE_ARRAY; // Paper + } + } + +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java +index 53f38fa95f4ffad12c73d94ab1d7ecf7ee78af09..088ea8a14f1bb264b59fcec626b1a28d7f6d7c47 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java +@@ -10,6 +10,7 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.phys.shapes.Shapes; +@@ -23,10 +24,37 @@ public abstract class LayerLightEngine, S exten + protected final LightLayer layer; + protected final S storage; + private boolean runningLightUpdates; +- protected final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); protected final BlockPos.MutableBlockPos pos = pos; // Paper + private final long[] lastChunkPos = new long[2]; +- private final BlockGetter[] lastChunk = new BlockGetter[2]; ++ private final ChunkAccess[] h = new ChunkAccess[2]; // Paper + ++ // Paper start - see fully commented out method below (look for Bedrock) ++ // optimized method with less branching for when scenarios arent needed. ++ // avoid using mutable version if can ++ protected final BlockState getBlockOptimized(int x, int y, int z, MutableInt mutableint) { ++ ChunkAccess iblockaccess = this.a(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ mutableint.setValue(16); ++ return Blocks.BEDROCK.defaultBlockState(); ++ } else { ++ this.pos.setValues(x, y, z); ++ BlockState iblockdata = iblockaccess.getType(x, y, z); ++ mutableint.setValue(iblockdata.getLightBlock(this.chunkSource.getLevel(), this.pos)); ++ return iblockdata.canOcclude() && iblockdata.useShapeForLightOcclusion() ? iblockdata : Blocks.AIR.defaultBlockState(); ++ } ++ } ++ protected final BlockState getBlockOptimized(int x, int y, int z) { ++ ChunkAccess iblockaccess = this.a(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ return Blocks.BEDROCK.defaultBlockState(); ++ } else { ++ BlockState iblockdata = iblockaccess.getType(x, y, z); ++ return iblockdata.canOcclude() && iblockdata.useShapeForLightOcclusion() ? iblockdata : Blocks.AIR.defaultBlockState(); ++ } ++ } ++ // Paper end + public LayerLightEngine(LightChunkGetter chunkProvider, LightLayer type, S lightStorage) { + super(16, 256, 8192); + this.chunkSource = chunkProvider; +@@ -45,63 +73,65 @@ public abstract class LayerLightEngine, S exten + } + + @Nullable +- private BlockGetter getChunk(int chunkX, int chunkZ) { +- long k = ChunkPos.asLong(chunkX, chunkZ); ++ private ChunkAccess a(int i, int j) { // Paper ++ long k = ChunkPos.asLong(i, j); + + for (int l = 0; l < 2; ++l) { + if (k == this.lastChunkPos[l]) { +- return this.lastChunk[l]; ++ return this.h[l]; + } + } + +- BlockGetter iblockaccess = this.chunkSource.getChunkForLighting(chunkX, chunkZ); ++ ChunkAccess iblockaccess = (ChunkAccess) this.chunkSource.getChunkForLighting(i, j); // Paper + + for (int i1 = 1; i1 > 0; --i1) { + this.lastChunkPos[i1] = this.lastChunkPos[i1 - 1]; +- this.lastChunk[i1] = this.lastChunk[i1 - 1]; ++ this.h[i1] = this.h[i1 - 1]; + } + + this.lastChunkPos[0] = k; +- this.lastChunk[0] = iblockaccess; ++ this.h[0] = iblockaccess; + return iblockaccess; + } + + private void clearCache() { + Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS); +- Arrays.fill(this.lastChunk, (Object) null); ++ Arrays.fill(this.h, (Object) null); + } + +- protected BlockState getStateAndOpacity(long pos, @Nullable MutableInt mutableint) { +- if (pos == Long.MAX_VALUE) { +- if (mutableint != null) { +- mutableint.setValue(0); +- } +- +- return Blocks.AIR.defaultBlockState(); +- } else { +- int j = SectionPos.blockToSectionCoord(BlockPos.getX(pos)); +- int k = SectionPos.blockToSectionCoord(BlockPos.getZ(pos)); +- BlockGetter iblockaccess = this.getChunk(j, k); +- +- if (iblockaccess == null) { +- if (mutableint != null) { +- mutableint.setValue(16); +- } +- +- return Blocks.BEDROCK.defaultBlockState(); +- } else { +- this.pos.set(pos); +- BlockState iblockdata = iblockaccess.getBlockState(this.pos); +- boolean flag = iblockdata.canOcclude() && iblockdata.useShapeForLightOcclusion(); +- +- if (mutableint != null) { +- mutableint.setValue(iblockdata.getLightBlock(this.chunkSource.getLevel(), (BlockPos) this.pos)); +- } +- +- return flag ? iblockdata : Blocks.AIR.defaultBlockState(); +- } +- } +- } ++ // Paper start - comment out, see getBlockOptimized ++// protected IBlockData a(long i, @Nullable MutableInt mutableint) { ++// if (i == Long.MAX_VALUE) { ++// if (mutableint != null) { ++// mutableint.setValue(0); ++// } ++// ++// return Blocks.AIR.getBlockData(); ++// } else { ++// int j = SectionPosition.a(BlockPosition.b(i)); ++// int k = SectionPosition.a(BlockPosition.d(i)); ++// IBlockAccess iblockaccess = this.a(j, k); ++// ++// if (iblockaccess == null) { ++// if (mutableint != null) { ++// mutableint.setValue(16); ++// } ++// ++// return Blocks.BEDROCK.getBlockData(); ++// } else { ++// this.d.g(i); ++// IBlockData iblockdata = iblockaccess.getType(this.d); ++// boolean flag = iblockdata.l() && iblockdata.e(); ++// ++// if (mutableint != null) { ++// mutableint.setValue(iblockdata.b(this.a.getWorld(), (BlockPosition) this.d)); ++// } ++// ++// return flag ? iblockdata : Blocks.AIR.getBlockData(); ++// } ++// } ++// } ++ // Paper end + + protected VoxelShape getShape(BlockState world, long pos, Direction facing) { + return world.canOcclude() ? world.getFaceOcclusionShape(this.chunkSource.getLevel(), this.pos.set(pos), facing) : Shapes.empty(); +@@ -136,8 +166,9 @@ public abstract class LayerLightEngine, S exten + return id == Long.MAX_VALUE ? 0 : 15 - this.storage.getStoredLevel(id); + } + ++ protected int getNibbleLightInverse(DataLayer nibblearray, int x, int y, int z) { return 15 - nibblearray.get(x & 15, y & 15, z & 15); } // Paper - x/y/z version of below + protected int getLevel(DataLayer section, long blockPos) { +- return 15 - section.get(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos))); ++ return 15 - section.get((int) (blockPos >> 38) & 15, (int) ((blockPos << 52) >> 52) & 15, (int) ((blockPos << 26) >> 38) & 15); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +index 5757bcfded35f112d52a7c81586850ba50e0d8dd..17a6610b352af5d3e2cbcdf9b4d9b0d4d356b5cf 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +@@ -27,9 +27,9 @@ public abstract class LayerLightSectionStorage> + protected final LongSet toMarkNoData = new LongOpenHashSet(); + protected final LongSet toMarkData = new LongOpenHashSet(); + protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change +- protected final M updatingSectionData; // Paper - diff on change, should be "updating" ++ protected final M updatingSectionData; protected final M updating; // Paper - diff on change, should be "updating" + protected final LongSet changedSections = new LongOpenHashSet(); +- protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); ++ protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); LongSet dirty = sectionsAffectedByLightUpdates; // Paper - OBFHELPER + protected final Long2ObjectMap queuedSections = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); + private final LongSet untrustedSections = new LongOpenHashSet(); + private final LongSet columnsToRetainQueuedDataFor = new LongOpenHashSet(); +@@ -37,33 +37,33 @@ public abstract class LayerLightSectionStorage> + protected volatile boolean hasToRemove; + + protected LayerLightSectionStorage(LightLayer lightType, LightChunkGetter chunkProvider, M lightData) { +- super(3, 16, 256); ++ super(3, 256, 256); // Paper - bump expected size of level sets to improve collisions and reduce rehashing (seen a lot of it) + this.layer = lightType; + this.chunkSource = chunkProvider; +- this.updatingSectionData = lightData; ++ this.updatingSectionData = lightData; updating = lightData; // Paper + this.e_visible = lightData.copy(); // Paper - avoid copying light data + this.e_visible.disableCache(); // Paper - avoid copying light data + } + +- protected boolean storingLightForSection(long sectionPos) { +- return this.getDataLayer(sectionPos, true) != null; ++ protected final boolean storingLightForSection(long sectionPos) { // Paper - final to help inlining ++ return this.updating.getUpdatingOptimized(sectionPos) != null; // Paper - inline to avoid branching + } + + @Nullable + protected DataLayer getDataLayer(long sectionPos, boolean cached) { + // Paper start - avoid copying light data + if (cached) { +- return this.getDataLayer(this.updatingSectionData, sectionPos); ++ return this.updating.getUpdatingOptimized(sectionPos); + } else { + synchronized (this.visibleUpdateLock) { +- return this.getDataLayer(this.e_visible, sectionPos); ++ return this.e_visible.lookup.apply(sectionPos); + } + } + // Paper end - avoid copying light data + } + + @Nullable +- protected DataLayer getDataLayer(M storage, long sectionPos) { ++ protected final DataLayer getDataLayer(M storage, long sectionPos) { // Paper + return storage.getLayer(sectionPos); + } + +@@ -77,27 +77,57 @@ public abstract class LayerLightSectionStorage> + protected abstract int getLightValue(long blockPos); + + protected int getStoredLevel(long blockPos) { +- long j = SectionPos.blockToSection(blockPos); +- DataLayer nibblearray = this.getDataLayer(j, true); ++ // Paper start - reuse and inline math, use Optimized Updating path ++ final int x = (int) (blockPos >> 38); ++ final int y = (int) ((blockPos << 52) >> 52); ++ final int z = (int) ((blockPos << 26) >> 38); ++ long j = SectionPos.blockPosAsSectionLong(x, y, z); ++ DataLayer nibblearray = this.updating.getUpdatingOptimized(j); ++ // BUG: Sometimes returns null and crashes, try to recover, but to prevent crash just return no light. ++ if (nibblearray == null) { ++ nibblearray = this.e_visible.lookup.apply(j); ++ } ++ if (nibblearray == null) { ++ System.err.println("Null nibble, preventing crash " + BlockPos.of(blockPos)); ++ return 0; ++ } + +- return nibblearray.get(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos))); ++ return nibblearray.get(x & 15, y & 15, z & 15); // Paper - inline operations ++ // Paper end + } + + protected void setStoredLevel(long blockPos, int value) { +- long k = SectionPos.blockToSection(blockPos); ++ // Paper start - cache part of the math done in loop below ++ int x = (int) (blockPos >> 38); ++ int y = (int) ((blockPos << 52) >> 52); ++ int z = (int) ((blockPos << 26) >> 38); ++ long k = SectionPos.blockPosAsSectionLong(x, y, z); ++ // Paper end + + if (this.changedSections.add(k)) { + this.updatingSectionData.copyDataLayer(k); + } + + DataLayer nibblearray = this.getDataLayer(k, true); +- +- nibblearray.set(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos)), value); +- +- for (int l = -1; l <= 1; ++l) { +- for (int i1 = -1; i1 <= 1; ++i1) { +- for (int j1 = -1; j1 <= 1; ++j1) { +- this.sectionsAffectedByLightUpdates.add(SectionPos.blockToSection(BlockPos.offset(blockPos, i1, j1, l))); ++ nibblearray.set(x & 15, y & 15, z & 15, value); // Paper - use already calculated x/y/z ++ ++ // Paper start - credit to JellySquid for a major optimization here: ++ /* ++ * An extremely important optimization is made here in regards to adding items to the pending notification set. The ++ * original implementation attempts to add the coordinate of every chunk which contains a neighboring block position ++ * even though a huge number of loop iterations will simply map to block positions within the same updating chunk. ++ * ++ * Our implementation here avoids this by pre-calculating the min/max chunk coordinates so we can iterate over only ++ * the relevant chunk positions once. This reduces what would always be 27 iterations to just 1-8 iterations. ++ * ++ * @reason Use faster implementation ++ * @author JellySquid ++ */ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPos.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -129,17 +159,23 @@ public abstract class LayerLightSectionStorage> + } + + if (k >= 2 && level != 2) { +- if (this.toRemove.contains(id)) { +- this.toRemove.remove(id); +- } else { ++ if (!this.toRemove.remove(id)) { // Paper - remove useless contains - credit to JellySquid ++ //this.p.remove(i); // Paper ++ //} else { // Paper + this.updatingSectionData.setLayer(id, this.createDataLayer(id)); + this.changedSections.add(id); + this.onNodeAdded(id); + +- for (int l = -1; l <= 1; ++l) { +- for (int i1 = -1; i1 <= 1; ++i1) { +- for (int j1 = -1; j1 <= 1; ++j1) { +- this.sectionsAffectedByLightUpdates.add(SectionPos.blockToSection(BlockPos.offset(id, i1, j1, l))); ++ // Paper start - reuse x/y/z and only notify valid chunks - Credit to JellySquid (See above method for notes) ++ int x = (int) (id >> 38); ++ int y = (int) ((id << 52) >> 52); ++ int z = (int) ((id << 26) >> 38); ++ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPos.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -165,9 +201,9 @@ public abstract class LayerLightSectionStorage> + return SectionPos.blockToSection(j) == sectionPos; + }); + } else { +- int j = SectionPos.sectionToBlockCoord(SectionPos.x(sectionPos)); +- int k = SectionPos.sectionToBlockCoord(SectionPos.y(sectionPos)); +- int l = SectionPos.sectionToBlockCoord(SectionPos.z(sectionPos)); ++ int j = (int) (sectionPos >> 42) << 4; // Paper - inline ++ int k = (int) (sectionPos << 44 >> 44) << 4; // Paper - inline ++ int l = (int) (sectionPos << 22 >> 42) << 4; // Paper - inline + + for (int i1 = 0; i1 < 16; ++i1) { + for (int j1 = 0; j1 < 16; ++j1) { +@@ -194,7 +230,7 @@ public abstract class LayerLightSectionStorage> + DataLayer nibblearray; + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.clearQueuedSectionBlocks(lightProvider, i); + DataLayer nibblearray1 = (DataLayer) this.queuedSections.remove(i); + +@@ -212,7 +248,7 @@ public abstract class LayerLightSectionStorage> + longiterator = this.toRemove.iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.onNodeRemoved(i); + } + +@@ -223,12 +259,13 @@ public abstract class LayerLightSectionStorage> + Entry entry; + long j; + ++ DataLayer test = null; // Paper + while (objectiterator.hasNext()) { + entry = (Entry) objectiterator.next(); + j = entry.getLongKey(); +- if (this.storingLightForSection(j)) { ++ if ((test = this.updating.getUpdatingOptimized(j)) != null) { // Paper - dont look up nibble twice + nibblearray = (DataLayer) entry.getValue(); +- if (this.updatingSectionData.getLayer(j) != nibblearray) { ++ if (test != nibblearray) { // Paper + this.clearQueuedSectionBlocks(lightProvider, j); + this.updatingSectionData.setLayer(j, nibblearray); + this.changedSections.add(j); +@@ -241,14 +278,14 @@ public abstract class LayerLightSectionStorage> + longiterator = this.queuedSections.keySet().iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.checkEdgesForSection(lightProvider, i); + } + } else { + longiterator = this.untrustedSections.iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.checkEdgesForSection(lightProvider, i); + } + } +@@ -269,15 +306,20 @@ public abstract class LayerLightSectionStorage> + + private void checkEdgesForSection(LayerLightEngine lightProvider, long sectionPos) { + if (this.storingLightForSection(sectionPos)) { +- int j = SectionPos.sectionToBlockCoord(SectionPos.x(sectionPos)); +- int k = SectionPos.sectionToBlockCoord(SectionPos.y(sectionPos)); +- int l = SectionPos.sectionToBlockCoord(SectionPos.z(sectionPos)); ++ // Paper start ++ int secX = (int) (sectionPos >> 42); ++ int secY = (int) (sectionPos << 44 >> 44); ++ int secZ = (int) (sectionPos << 22 >> 42); ++ int j = secX << 4; // baseX ++ int k = secY << 4; // baseY ++ int l = secZ << 4; // baseZ ++ // Paper end + Direction[] aenumdirection = LayerLightSectionStorage.DIRECTIONS; + int i1 = aenumdirection.length; + + for (int j1 = 0; j1 < i1; ++j1) { + Direction enumdirection = aenumdirection[j1]; +- long k1 = SectionPos.offset(sectionPos, enumdirection); ++ long k1 = SectionPos.getAdjacentFromSectionPos(secX, secY, secZ, enumdirection); // Paper - avoid extra unpacking + + if (!this.queuedSections.containsKey(k1) && this.storingLightForSection(k1)) { + for (int l1 = 0; l1 < 16; ++l1) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +index ff1fbc46776b26ca56c3293e40ed55028230ec46..da4003aebc8d5ffce695071af9a27139568d773f 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +@@ -4,6 +4,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.SectionPos; + import net.minecraft.world.level.LightLayer; ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -38,21 +39,25 @@ public final class SkyLightEngine extends LayerLightEngine= 15) { ++ // Paper start - use x/y/z and optimized block lookup ++ int jx = (int) (targetId >> 38); ++ int jy = (int) ((targetId << 52) >> 52); ++ int jz = (int) ((targetId << 26) >> 38); ++ BlockState iblockdata = this.getBlockOptimized(jx, jy, jz, mutableint); ++ int blockedLight = mutableint.getValue(); ++ if (blockedLight >= 15) { ++ // Paper end + return 15; + } else { +- int l = BlockPos.getX(sourceId); +- int i1 = BlockPos.getY(sourceId); +- int j1 = BlockPos.getZ(sourceId); +- int k1 = BlockPos.getX(targetId); +- int l1 = BlockPos.getY(targetId); +- int i2 = BlockPos.getZ(targetId); +- boolean flag = l == k1 && j1 == i2; +- int j2 = Integer.signum(k1 - l); +- int k2 = Integer.signum(l1 - i1); +- int l2 = Integer.signum(i2 - j1); ++ // Paper start - inline math ++ int ix = (int) (sourceId >> 38); ++ int iy = (int) ((sourceId << 52) >> 52); ++ int iz = (int) ((sourceId << 26) >> 38); ++ boolean flag = ix == jx && iz == jz; ++ int j2 = Integer.signum(jx - ix); ++ int k2 = Integer.signum(jy - iy); ++ int l2 = Integer.signum(jz - iz); ++ // Paper end + Direction enumdirection; + + if (sourceId == Long.MAX_VALUE) { +@@ -61,7 +66,7 @@ public final class SkyLightEngine extends LayerLightEngine l1; ++ boolean flag1 = sourceId == Long.MAX_VALUE || flag && iy > jy; // Paper rename vars to iy > jy + +- return flag1 && level == 0 && mutableint.getValue() == 0 ? 0 : level + Math.max(1, mutableint.getValue()); ++ return flag1 && level == 0 && blockedLight == 0 ? 0 : level + Math.max(1, blockedLight); // Paper + } + } + } +@@ -101,10 +106,14 @@ public final class SkyLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long k = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ int i1 = baseY & 15; ++ int j1 = baseY >> 4; ++ // Paper end + int k1; + + if (i1 != 0) { +@@ -119,15 +128,16 @@ public final class SkyLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long j1 = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ DataLayer nibblearray = this.storage.updating.getUpdatingOptimized(j1); ++ // Paper end + Direction[] aenumdirection = SkyLightEngine.DIRECTIONS; + int k1 = aenumdirection.length; + + for (int l1 = 0; l1 < k1; ++l1) { + Direction enumdirection = aenumdirection[l1]; +- long i2 = BlockPos.offset(id, enumdirection); +- long j2 = SectionPos.blockToSection(i2); ++ // Paper start ++ int newX = baseX + enumdirection.getStepX(); ++ int newY = baseY + enumdirection.getStepY(); ++ int newZ = baseZ + enumdirection.getStepZ(); ++ long i2 = BlockPos.asLong(newX, newY, newZ); ++ long j2 = SectionPos.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + DataLayer nibblearray1; + + if (j1 == j2) { + nibblearray1 = nibblearray; + } else { +- nibblearray1 = ((SkyLightSectionStorage) this.storage).getDataLayer(j2, true); ++ nibblearray1 = ((SkyLightSectionStorage) this.storage).updating.getUpdatingOptimized(j2); // Paper + } + + if (nibblearray1 != null) { + if (i2 != excludedId) { +- int k2 = this.computeLevelFromNeighbor(i2, id, this.getLevel(nibblearray1, i2)); ++ int k2 = this.computeLevelFromNeighbor(i2, id, this.getNibbleLightInverse(nibblearray1, newX, newY, newZ)); // Paper + + if (l > k2) { + l = k2; +@@ -215,7 +235,7 @@ public final class SkyLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((blockPos << 52) >> 52); ++ int baseZ = (int) ((blockPos << 26) >> 38); ++ long j = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ // Paper end + int k = SectionPos.y(j); + synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + SkyLightSectionStorage.SkyDataLayerStorageMap lightenginestoragesky_a = (SkyLightSectionStorage.SkyDataLayerStorageMap) this.e_visible; // Paper - avoid copying light data - must be after lock acquire +@@ -49,7 +54,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 52) & 15, (int) baseZ & 15); // Paper - y changed above + } else { + return 15; + } +@@ -168,7 +173,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 42) << 4; // Paper ++ int baseY = (int) (i << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (i << 22 >> 42) << 4; // Paper + j = this.getLevel(i); + if (j != 2 && !this.sectionsToRemoveSourcesFrom.contains(i) && this.sectionsWithSources.add(i)) { + int l; +@@ -203,10 +211,10 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 42) << 4; // Paper ++ int baseY = (int) (i << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (i << 22 >> 42) << 4; // Paper + if (this.sectionsWithSources.remove(i) && this.storingLightForSection(i)) { + for (j = 0; j < 16; ++j) { + for (k = 0; k < 16; ++k) { +- long l3 = BlockPos.asLong(SectionPos.sectionToBlockCoord(SectionPos.x(i)) + j, SectionPos.sectionToBlockCoord(SectionPos.y(i)) + 16 - 1, SectionPos.sectionToBlockCoord(SectionPos.z(i)) + k); ++ long l3 = BlockPos.asLong(baseX + j, baseY + 16 - 1, baseZ + k); // Paper + + lightProvider.checkEdge(Long.MAX_VALUE, l3, 15, false); + } diff --git a/Remapped-Spigot-Server-Patches/0492-Delay-Chunk-Unloads-based-on-Player-Movement.patch b/Remapped-Spigot-Server-Patches/0492-Delay-Chunk-Unloads-based-on-Player-Movement.patch new file mode 100644 index 000000000..a8c890b13 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0492-Delay-Chunk-Unloads-based-on-Player-Movement.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Jun 2016 23:22:12 -0400 +Subject: [PATCH] Delay Chunk Unloads based on Player Movement + +When players are moving in the world, doing things such as building or exploring, +they will commonly go back and forth in a small area. This causes a ton of chunk load +and unload activity on the edge chunks of their view distance. + +A simple back and forth movement in 6 blocks could spam a chunk to thrash a +loading and unload cycle over and over again. + +This is very wasteful. This system introduces a delay of inactivity on a chunk +before it actually unloads, which will be handled by the ticket expiry process. + +This allows servers with smaller worlds who do less long distance exploring to stop +wasting cpu cycles on saving/unloading/reloading chunks repeatedly. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6463d3e4837d032a35654a035f42b8a805e0e286..1655bca0502e7b871de4addaa163536d86547a02 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -637,4 +637,13 @@ public class PaperWorldConfig { + private void viewDistance() { + this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); + } ++ ++ public long delayChunkUnloadsBy; ++ private void delayChunkUnloadsBy() { ++ delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s")); ++ if (delayChunkUnloadsBy > 0) { ++ log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds"); ++ delayChunkUnloadsBy *= 20; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index e41f388e8350010a471410436adf15a906f07e97..e0241b9d60cd2b72f8fb774f1ab4753dfd615184 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -185,6 +185,27 @@ public abstract class DistanceManager { + boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { + removed = true; // CraftBukkit ++ // Paper start - delay chunk unloads for player tickets ++ long delayChunkUnloadsBy = chunkMap.level.paperConfig.delayChunkUnloadsBy; ++ if (ticket.getType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) { ++ boolean hasPlayer = false; ++ for (Ticket ticket1 : arraysetsorted) { ++ if (ticket1.getType() == TicketType.PLAYER) { ++ hasPlayer = true; ++ break; ++ } ++ } ++ ChunkHolder playerChunk = chunkMap.getUpdatingChunkIfPresent(i); ++ if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) { ++ Ticket delayUnload = new Ticket(TicketType.DELAY_UNLOAD, 33, i); ++ delayUnload.delayUnloadBy = delayChunkUnloadsBy; ++ delayUnload.setCurrentTick(this.ticketTickCounter); ++ arraysetsorted.remove(delayUnload); ++ // refresh ticket ++ arraysetsorted.add(delayUnload); ++ } ++ } ++ // Paper end + } + + if (arraysetsorted.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index c0bfe136ccb9ad4fc0f8ccdd703254205213ec8e..f7898bd7806684d2c068898cecbf835d834df461 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -9,11 +9,13 @@ public final class Ticket implements Comparable> { + public final T key; public final T getObjectReason() { return this.key; } // Paper - OBFHELPER + private long createdTick; public final long getCreationTick() { return this.createdTick; } // Paper - OBFHELPER + public int priority = 0; // Paper ++ public long delayUnloadBy; // Paper + + protected Ticket(TicketType type, int level, T argument) { + this.type = type; + this.ticketLevel = level; + this.key = argument; ++ this.delayUnloadBy = type.timeout; // Paper + } + + public int compareTo(Ticket ticket) { +@@ -63,7 +65,7 @@ public final class Ticket implements Comparable> { + } + + protected boolean timedOut(long currentTick) { +- long j = this.type.timeout(); ++ long j = delayUnloadBy; // Paper + + return j != 0L && currentTick - this.createdTick > j; + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 2444f6f676db543509b14e8c882491dc3f41b264..531ebf1bafec2b295af9f6dfec8f4b6466688287 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -30,6 +30,7 @@ public class TicketType { + public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper + public static final TicketType PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper + public static final TicketType URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper ++ public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper + + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); diff --git a/Remapped-Spigot-Server-Patches/0493-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/Remapped-Spigot-Server-Patches/0493-Add-Plugin-Tickets-to-API-Chunk-Methods.patch new file mode 100644 index 000000000..2d4cc183b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0493-Add-Plugin-Tickets-to-API-Chunk-Methods.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 9 Jun 2020 03:33:03 -0400 +Subject: [PATCH] Add Plugin Tickets to API Chunk Methods + +Like previous versions, plugins loading chunks kept them loaded until +they garbage collected to avoid constant spamming of chunk loads + +This adds tickets to a few more places so that they can be unloaded. + +Additionally, this drops their ticket level to BORDER so they wont be ticking +so they will just sit inactive instead. + +Using .loadChunk to keep a chunk ticking was a horrible idea for upstream +when we have TWO methods that are able to do that already in the API. + +Also reduce their collection count down to a maximum of 1 second. Barely +anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and +since this wasn't spigot behavior, this is safe to mostly ignore (unless someone +wants it to collect even faster, they can restore that setting back to 1 instead of 20+) + +Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 92f1a6d32a96fee682342e86c3ffd3c65292150b..2ec41cb87cec97780f1fa8abfbb756fca4dba1bf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -342,7 +342,7 @@ public final class CraftServer implements Server { + ambientSpawn = configuration.getInt("spawn-limits.ambient"); + console.autosavePeriod = configuration.getInt("ticks-per.autosave"); + warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + minimumAPI = configuration.getString("settings.minimum-api"); + loadIcon(); + } +@@ -832,7 +832,7 @@ public final class CraftServer implements Server { + waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient"); + ambientSpawn = configuration.getInt("spawn-limits.ambient"); + warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + minimumAPI = configuration.getString("settings.minimum-api"); + printSaveWarning = false; + console.autosavePeriod = configuration.getInt("ticks-per.autosave"); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 7261e22a71d219efe0949a08c5d3f10747759469..4436b3d23dc8f33925da1ec539ea16307e0785b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -30,6 +30,7 @@ import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; + import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; + import net.minecraft.network.protocol.game.ClientboundSetTimePacket; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ChunkMap; + import net.minecraft.server.level.DistanceManager; +@@ -389,8 +390,21 @@ public class CraftWorld implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- return this.world.getChunkSource().getChunk(x, z, true).bukkitChunk; ++ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it ++ net.minecraft.world.level.chunk.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (chunk == null) { ++ addTicket(x, z); ++ chunk = this.world.getChunkSource().getChunk(x, z, true); ++ } ++ return chunk.bukkitChunk; ++ // Paper end ++ } ++ ++ // Paper start ++ private void addTicket(int x, int z) { ++ MCUtil.MAIN_EXECUTOR.execute(() -> world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper + } ++ // Paper end + + @Override + public Chunk getChunkAt(Block block) { +@@ -465,7 +479,7 @@ public class CraftWorld implements World { + public boolean unloadChunkRequest(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (isChunkLoaded(x, z)) { +- world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + } + + return true; +@@ -542,9 +556,12 @@ public class CraftWorld implements World { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + // Paper start - Optimize this method + ChunkPos chunkPos = new ChunkPos(x, z); ++ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); // Paper ++ if (immediate != null) return true; // Paper + + if (!generate) { +- ChunkAccess immediate = world.getChunkSource().getChunkAtImmediately(x, z); ++ ++ //IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); // Paper + if (immediate == null) { + immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); + } +@@ -552,7 +569,7 @@ public class CraftWorld implements World { + if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { + return false; // not full status + } +- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunk(x, z); // make sure we're at ticket level 32 or lower + return true; + } +@@ -579,7 +596,7 @@ public class CraftWorld implements World { + // we do this so we do not re-read the chunk data on disk + } + +- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); + return true; + // Paper end +@@ -2529,6 +2546,7 @@ public class CraftWorld implements World { + } + return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); ++ if (chunk != null) addTicket(x, z); // Paper + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, net.minecraft.server.MinecraftServer.getServer()); + } diff --git a/Remapped-Spigot-Server-Patches/0494-Fix-missing-chunks-due-to-integer-overflow.patch b/Remapped-Spigot-Server-Patches/0494-Fix-missing-chunks-due-to-integer-overflow.patch new file mode 100644 index 000000000..f86c4c1c6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0494-Fix-missing-chunks-due-to-integer-overflow.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Slovikosky +Date: Tue, 9 Jun 2020 00:10:03 -0700 +Subject: [PATCH] Fix missing chunks due to integer overflow + +This patch fixes a bug in the WorldChunkManagerTheEnd class where the distance +from 0,0 squared overflows the maximum size of an integer. The overflow leads +to hard chunk borders around 370,000 blocks from 0,0. After this cutoff there +is a few hundred thousand block gap before end land resuming to generate at +530,000 blocks from spawn. This is due to the integer flipping back and forth. + +The fix for the issue is quite simple, casting chunk coordinates to longs +allows the distance calculation to avoid overflow and work as intended. + +diff --git a/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java b/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java +index af006c2e45e0a14367a0bc850c319024c6b82024..063369d3a64b4afc9cc6e1d20360900595e1a05f 100644 +--- a/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java ++++ b/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java +@@ -75,7 +75,9 @@ public class TheEndBiomeSource extends BiomeSource { + int l = j / 2; + int i1 = i % 2; + int j1 = j % 2; +- float f = 100.0F - Mth.sqrt((float) (i * i + j * j)) * 8.0F; ++ // Paper start - cast ints to long to avoid integer overflow ++ float f = 100.0F - Mth.sqrt((long) i * (long) i + (long) j * (long) j) * 8.0F; ++ // Paper end + + f = Mth.clamp(f, -100.0F, 80.0F); + diff --git a/Remapped-Spigot-Server-Patches/0495-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/Remapped-Spigot-Server-Patches/0495-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch new file mode 100644 index 000000000..423af0ebf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0495-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ossi +Date: Fri, 12 Jun 2020 01:38:06 +0300 +Subject: [PATCH] Fix CraftScheduler#runTaskTimerAsynchronously(Plugin, + Consumer, long, long) scheduling a non-repeating task instead of + a repeating one. + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index ca90237a53c9a026919d28adaedf483ca3c7c2a8..13e461ffb2ee2e7d0440c0f60809ea99629b843c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -184,7 +184,7 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { +- runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); ++ runTaskTimerAsynchronously(plugin, (Object) task, delay, period); + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0496-Fix-piston-physics-inconsistency-MC-188840.patch b/Remapped-Spigot-Server-Patches/0496-Fix-piston-physics-inconsistency-MC-188840.patch new file mode 100644 index 000000000..1d54c7bad --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0496-Fix-piston-physics-inconsistency-MC-188840.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Jun 2020 17:29:42 -0700 +Subject: [PATCH] Fix piston physics inconsistency - MC-188840 + +Pistons invoke physics when they move blocks. The physics can cause +tnt blocks to ignite. However, pistons (when storing the blocks they "moved") +don't actually go back to the world state sometimes to check if something +like that happened. As a result they end up moving the tnt like it was +never ignited. This resulted in the ability to create machines +that can duplicate tnt, called "world eaters". +This patch makes the piston logic retrieve the block state from the world +prevent this from occuring. + +This patch also sets the moved pos to air immediately after creating +the moving piston TE. This prevents the block from being updated from +other physics calls by the piston. + +Tested against the following tnt duper design: +https://www.youtube.com/watch?v=mS7xxNGhjxs + +This patch also affects every type of machine that utilises +this mechanic. For example, dead coral is removed by a physics +update when being moved while it is attached to slimeblocks. + +Standard piston machines that don't destroy or modify the +blocks they move by physics updates should be entirely +unaffected. + +This patch fixes https://bugs.mojang.com/browse/MC-188840 + +This patch also fixes rail duping and carpet duping. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7f140333c2e62012fa572c1a061d84432426997f..b67ba8f75e4a3358d7c2462918b85b0bf9b5a922 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -434,4 +434,10 @@ public class PaperConfig { + consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); + } + ++ public static boolean allowPistonDuplication; ++ private static void allowPistonDuplication() { ++ config.set("settings.unsupported-settings.allow-piston-duplication-readme", "This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items"); ++ allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false)); ++ set("settings.unsupported-settings.allow-tnt-duplication", null); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 40a18302dd682e5ade4ec77ac7f316b6c0f8c112..44876557515eaa6bbe33344b3d3ba03aee58409f 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -398,12 +398,24 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + for (k = list.size() - 1; k >= 0; --k) { +- blockposition3 = (BlockPos) list.get(k); +- iblockdata1 = world.getBlockState(blockposition3); ++ // Paper start - fix a variety of piston desync dupes ++ boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication; ++ BlockPos oldPos = blockposition3 = (BlockPos) list.get(k); ++ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; ++ // Paper end - fix a variety of piston desync dupes + blockposition3 = blockposition3.relative(enumdirection1); + map.remove(blockposition3); + world.setBlock(blockposition3, (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir), 68); +- world.setBlockEntity(blockposition3, MovingPistonBlock.newMovingBlockEntity((BlockState) list1.get(k), dir, retract, false)); ++ // Paper start - fix a variety of piston desync dupes ++ if (!allowDesync) { ++ iblockdata1 = world.getBlockState(oldPos); ++ map.replace(oldPos, iblockdata1); ++ } ++ world.setBlockEntity(blockposition3, MovingPistonBlock.newMovingBlockEntity(allowDesync ? list1.get(k) : iblockdata1, dir, retract, false)); ++ if (!allowDesync) { ++ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), 2 | 4 | 16 | 1024); // set air to prevent later physics updates from seeing this block ++ } ++ // Paper end - fix a variety of piston desync dupes + aiblockdata[j++] = iblockdata1; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 73888713746e7ddd72ba9ac9d33d8e616eb3bd25..001e90da8b09e16b6df4849a5bac4f4821000c94 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -279,7 +279,7 @@ public class PistonMovingBlockEntity extends BlockEntity implements TickableBloc + BlockState iblockdata = Block.updateFromNeighbourShapes(this.movedState, (LevelAccessor) this.level, this.worldPosition); + + if (iblockdata.isAir()) { +- this.level.setBlock(this.worldPosition, this.movedState, 84); ++ this.level.setBlock(this.worldPosition, this.movedState, com.destroystokyo.paper.PaperConfig.allowPistonDuplication ? 84 : (84 | 2)); // Paper - force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air + Block.updateOrDestroy(this.movedState, iblockdata, this.level, this.worldPosition, 3); + } else { + if (iblockdata.hasProperty(BlockStateProperties.WATERLOGGED) && (Boolean) iblockdata.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/Remapped-Spigot-Server-Patches/0497-Fix-sand-duping.patch b/Remapped-Spigot-Server-Patches/0497-Fix-sand-duping.patch new file mode 100644 index 000000000..d06a66165 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0497-Fix-sand-duping.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 13:33:19 -0700 +Subject: [PATCH] Fix sand duping + +If the falling block dies during teleportation (entity#move), then we need +to detect that by placing a check after the move. + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index ff8f7e4569a889ead1512b7c9908f9c5cad9eed5..2ba81e7179c7f9e2e1add1ad6bd6b96ee12c5da1 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -101,6 +101,11 @@ public class FallingBlockEntity extends Entity { + + @Override + public void tick() { ++ // Paper start - fix sand duping ++ if (this.removed) { ++ return; ++ } ++ // Paper end - fix sand duping + if (this.blockState.isAir()) { + this.remove(); + } else { +@@ -123,6 +128,12 @@ public class FallingBlockEntity extends Entity { + + this.move(MoverType.SELF, this.getDeltaMovement()); + ++ // Paper start - fix sand duping ++ if (this.removed) { ++ return; ++ } ++ // Paper end - fix sand duping ++ + // Paper start - Configurable EntityFallingBlock height nerf + if (this.level.paperConfig.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig.fallingBlockHeightNerf) { + if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { diff --git a/Remapped-Spigot-Server-Patches/0498-Prevent-position-desync-in-playerconnection-causing-.patch b/Remapped-Spigot-Server-Patches/0498-Prevent-position-desync-in-playerconnection-causing-.patch new file mode 100644 index 000000000..9994f830f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0498-Prevent-position-desync-in-playerconnection-causing-.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 16:51:39 -0700 +Subject: [PATCH] Prevent position desync in playerconnection causing tp + exploit + +Caused the server to revert to the player's overworld coordinates +after teleporting into the end. + +Sidenote: The underlying issue is that the move call can teleport +entities and do other things like kill the entity. In the future, +to fix all exploits derieved from this usually unexpected +behaviour, we need to move all of this dangerous logic outside +of the move call and into an appropriate place in the tick method. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0625bc7ffd07b66b27176fe62ae3061aa7c67df2..fbafb89cc63744d942933546026e272122bd9fba 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1324,6 +1324,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); + this.player.setOnGround(packet.isOnGround()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.awaitingPositionFromClient != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d12 = d8; + + d7 = d4 - this.player.getX(); diff --git a/Remapped-Spigot-Server-Patches/0499-Fix-enderdragon-exp-dupe.patch b/Remapped-Spigot-Server-Patches/0499-Fix-enderdragon-exp-dupe.patch new file mode 100644 index 000000000..38690afc4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0499-Fix-enderdragon-exp-dupe.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 22:25:11 -0700 +Subject: [PATCH] Fix enderdragon exp dupe + +Properly track death stage when unloading/loading in the +dragon + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index ec9436005a3a6fdfb4783d1092bb361224eb6414..b224a630f8adb1fa357c838e6b32c784aed0b15b 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -879,6 +879,7 @@ public class EnderDragon extends Mob implements Enemy { + public void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId()); ++ tag.putInt("Paper.DeathTick", this.dragonDeathTime); // Paper + } + + @Override +@@ -887,6 +888,7 @@ public class EnderDragon extends Mob implements Enemy { + if (tag.contains("DragonPhase")) { + this.phaseManager.setPhase(EnderDragonPhase.getById(tag.getInt("DragonPhase"))); + } ++ this.dragonDeathTime = tag.getInt("Paper.DeathTick"); // Paper + + } + diff --git a/Remapped-Spigot-Server-Patches/0500-Inventory-getHolder-method-without-block-snapshot.patch b/Remapped-Spigot-Server-Patches/0500-Inventory-getHolder-method-without-block-snapshot.patch new file mode 100644 index 000000000..d25c9ffc5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0500-Inventory-getHolder-method-without-block-snapshot.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Wed, 10 Jun 2020 23:55:15 +0100 +Subject: [PATCH] Inventory getHolder method without block snapshot + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index ef2d18d19a86b3701855aa1ac126462e663f8fcd..7ccc085228f373e6eba55d809bed480d43d5c211 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -10,6 +10,7 @@ import net.minecraft.world.inventory.PlayerEnderChestContainer; + import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; + import net.minecraft.world.level.block.entity.BarrelBlockEntity; + import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; + import net.minecraft.world.level.block.entity.DispenserBlockEntity; + import net.minecraft.world.level.block.entity.DropperBlockEntity; +@@ -525,6 +526,13 @@ public class CraftInventory implements Inventory { + return inventory.getOwner(); + } + ++ // Paper start - getHolder without snapshot ++ @Override ++ public InventoryHolder getHolder(boolean useSnapshot) { ++ return inventory instanceof BlockEntity ? ((BlockEntity) inventory).getOwner(useSnapshot) : getHolder(); ++ } ++ // Paper end ++ + @Override + public int getMaxStackSize() { + return inventory.getMaxStackSize(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java +index 3245dfa94410d319e53543c862c990e80ed3c72d..23dcf6e9332bd2b42ebb851497483e41948355d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java +@@ -64,6 +64,13 @@ public class CraftInventoryDoubleChest extends CraftInventory implements DoubleC + return new DoubleChest(this); + } + ++ // Paper start - getHolder without snapshot ++ @Override ++ public DoubleChest getHolder(boolean useSnapshot) { ++ return getHolder(); ++ } ++ // Paper end ++ + @Override + public Location getLocation() { + return getLeftSide().getLocation().add(getRightSide().getLocation()).multiply(0.5); diff --git a/Remapped-Spigot-Server-Patches/0501-Expose-Arrow-getItemStack.patch b/Remapped-Spigot-Server-Patches/0501-Expose-Arrow-getItemStack.patch new file mode 100644 index 000000000..cafd641bc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0501-Expose-Arrow-getItemStack.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nesaak <52047222+Nesaak@users.noreply.github.com> +Date: Sat, 23 May 2020 10:31:11 -0400 +Subject: [PATCH] Expose Arrow getItemStack + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 3ce431c1fdf1f5bd62b49f26cca188e939e98efa..6225f390b51733217a809910182f58acea1055e2 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -556,6 +556,7 @@ public abstract class AbstractArrow extends Projectile { + } + } + ++ public final ItemStack getOriginalItemStack() { return getPickupItem(); } // Paper - OBFHELPER - exists purely due to overrides all as protected and dont want to change them all + protected abstract ItemStack getPickupItem(); + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index 5b898118a46007a85254931c7b5bd18d7cda99be..91d32aff07e81608a2f8ecb1301ef3c08533494b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -102,6 +102,13 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { + getHandle().pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.byOrdinal(status.ordinal()); + } + ++ // Paper start ++ @Override ++ public org.bukkit.craftbukkit.inventory.CraftItemStack getItemStack() { ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getOriginalItemStack()); ++ } ++ //Paper end ++ + @Override + public void setTicksLived(int value) { + super.setTicksLived(value); diff --git a/Remapped-Spigot-Server-Patches/0502-Add-and-implement-PlayerRecipeBookClickEvent.patch b/Remapped-Spigot-Server-Patches/0502-Add-and-implement-PlayerRecipeBookClickEvent.patch new file mode 100644 index 000000000..412fc1c8a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0502-Add-and-implement-PlayerRecipeBookClickEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Fri, 5 Jun 2020 18:24:06 -0400 +Subject: [PATCH] Add and implement PlayerRecipeBookClickEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index fbafb89cc63744d942933546026e272122bd9fba..a0b2fc3fe59d97b9282a9451f35542b39df774e7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2770,9 +2770,15 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu.isSynched(this.player) && this.player.containerMenu instanceof RecipeBookMenu) { +- this.server.getRecipeManager().byKey(packet.getRecipe()).ifPresent((irecipe) -> { +- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(packet.isShiftDown(), irecipe, this.player); +- }); ++ // Paper start - fire event for clicking recipes in the recipe book ++ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent event = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( ++ player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(packet.getRecipe()), packet.isShiftDown()); ++ if (event.callEvent()) { ++ this.server.getRecipeManager().byKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getRecipe())).ifPresent((irecipe) -> { ++ ((ContainerRecipeBook) this.player.activeContainer).a(event.isMakeAll(), irecipe, this.player); ++ }); ++ } ++ // Paper end + } + } + diff --git a/Remapped-Spigot-Server-Patches/0503-Hide-sync-chunk-writes-behind-flag.patch b/Remapped-Spigot-Server-Patches/0503-Hide-sync-chunk-writes-behind-flag.patch new file mode 100644 index 000000000..43a0839a4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0503-Hide-sync-chunk-writes-behind-flag.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 26 Jun 2020 22:35:08 -0700 +Subject: [PATCH] Hide sync chunk writes behind flag + +Syncing writes on each write call has terrible performance +on harddrives. + +-DPaper.enable-sync-chunk-writes=true to enable + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 545096d9ba403396b6aaa7bb6d912f2de08a967e..3450a58fc02a472eb710aa1a31f6fecefc982b6f 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -108,7 +108,7 @@ public class DedicatedServerProperties extends Settings { + return Mth.clamp(integer, 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true); ++ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); + this.entityBroadcastRangePercentage = this.get("entity-broadcast-range-percentage", (integer) -> { diff --git a/Remapped-Spigot-Server-Patches/0504-Limit-lightning-strike-effect-distance.patch b/Remapped-Spigot-Server-Patches/0504-Limit-lightning-strike-effect-distance.patch new file mode 100644 index 000000000..1f74c2ced --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0504-Limit-lightning-strike-effect-distance.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Fri, 14 Sep 2018 17:42:08 +0200 +Subject: [PATCH] Limit lightning strike effect distance + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1655bca0502e7b871de4addaa163536d86547a02..978062774c1db286bfb9b0ffdef19d880b1f249b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -646,4 +646,26 @@ public class PaperWorldConfig { + delayChunkUnloadsBy *= 20; + } + } ++ ++ public double sqrMaxThunderDistance; ++ public double sqrMaxLightningImpactSoundDistance; ++ public double maxLightningFlashDistance; ++ private void lightningStrikeDistanceLimit() { ++ sqrMaxThunderDistance = getInt("lightning-strike-distance-limit.sound", -1); ++ if (sqrMaxThunderDistance > 0) { ++ sqrMaxThunderDistance *= sqrMaxThunderDistance; ++ } ++ ++ sqrMaxLightningImpactSoundDistance = getInt("lightning-strike-distance-limit.impact-sound", -1); ++ if (sqrMaxLightningImpactSoundDistance < 0) { ++ sqrMaxLightningImpactSoundDistance = 32 * 32; //Vanilla value ++ } else { ++ sqrMaxLightningImpactSoundDistance *= sqrMaxLightningImpactSoundDistance; ++ } ++ ++ maxLightningFlashDistance = getInt("lightning-strike-distance-limit.flash", -1); ++ if (maxLightningFlashDistance < 0) { ++ maxLightningFlashDistance = 512; // Vanilla value ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index e030e7f3d8bd9fe6578df0b560a237d494ec8a01..4b0dbeded2b8a475d32f518957909d3495a4b6fc 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -15,7 +15,6 @@ import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.Difficulty; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; +@@ -74,6 +73,17 @@ public class LightningBolt extends Entity { + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ // Paper start - Limit lightning strike effect distance ++ if (distanceSquared <= this.level.paperConfig.sqrMaxLightningImpactSoundDistance) { ++ player.connection.send(new ClientboundSoundPacket(SoundEvents.LIGHTNING_BOLT_IMPACT, ++ SoundSource.WEATHER, this.getX(), this.getY(), this.getZ(), 2.0f, 0.5F + this.random.nextFloat() * 0.2F)); ++ } ++ ++ if (level.paperConfig.sqrMaxThunderDistance != -1 && distanceSquared >= level.paperConfig.sqrMaxThunderDistance) { ++ continue; ++ } ++ ++ // Paper end + if (distanceSquared > viewDistance * viewDistance) { + double deltaLength = Math.sqrt(distanceSquared); + double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; +@@ -84,7 +94,7 @@ public class LightningBolt extends Entity { + } + } + // CraftBukkit end +- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F); ++// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LIGHTNING_BOLT_IMPACT, SoundCategory.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F); // Paper - Limit lightning strike effect distance (the packet is now sent from inside the loop) + } + + --this.life; diff --git a/Remapped-Spigot-Server-Patches/0505-Add-permission-for-command-blocks.patch b/Remapped-Spigot-Server-Patches/0505-Add-permission-for-command-blocks.patch new file mode 100644 index 000000000..9e0fdeee1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0505-Add-permission-for-command-blocks.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:05:30 +0200 +Subject: [PATCH] Add permission for command blocks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index af048ab682612233c01f7087d7b8afbf7e58945b..79f3e4176145c42debb9adc1e68175cf063c1f22 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -386,7 +386,7 @@ public class ServerPlayerGameMode { + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + +- if ((block instanceof CommandBlock || block instanceof StructureBlock || block instanceof JigsawBlock) && !this.player.canUseGameMasterBlocks()) { ++ if ((block instanceof CommandBlock || block instanceof StructureBlock || block instanceof JigsawBlock) && !this.player.canUseGameMasterBlocks() && !(block instanceof CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + return false; + } else if (this.player.blockActionRestricted((Level) this.level, pos, this.gameModeForPlayer)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a0b2fc3fe59d97b9282a9451f35542b39df774e7..471370a9aafa3eda83beb4097c6233650bd155ee 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -796,7 +796,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendMessage(new TranslatableComponent("advMode.notEnabled"), Util.NIL_UUID); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && !this.player.isCreative() && !this.player.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + this.player.sendMessage(new TranslatableComponent("advMode.notAllowed"), Util.NIL_UUID); + } else { + BaseCommandBlock commandblocklistenerabstract = null; +@@ -859,7 +859,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendMessage(new TranslatableComponent("advMode.notEnabled"), Util.NIL_UUID); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && !this.player.isCreative() && !this.player.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + this.player.sendMessage(new TranslatableComponent("advMode.notAllowed"), Util.NIL_UUID); + } else { + BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level); +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index 00dc4cd436023b946d7005f17a7ba983a4bbdfb6..23500428abf5e7daec19f8fb3c24e6c5361f0819 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -192,7 +192,7 @@ public abstract class BaseCommandBlock implements CommandSource { + } + + public InteractionResult usedBy(Player player) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && !player.isCreative() && !player.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + return InteractionResult.PASS; + } else { + if (player.getCommandSenderWorld().isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +index 088d78905732cacf69beb7a4b713dac4bec82220..c5e81e7d67504b1bc2a87c0b577bb93be4a57e9c 100644 +--- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +@@ -128,7 +128,7 @@ public class CommandBlock extends BaseEntityBlock { + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { ++ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + player.openCommandBlock((CommandBlockEntity) tileentity); + return InteractionResult.sidedSuccess(world.isClientSide); + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +index 525ebf961e5da0687183a5e2ead23ed92cbd9d79..a4a809f302c5ff9c76cde5fc0add2ceec1bdf9b5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { + DefaultPermissions.registerPermission(ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); + DefaultPermissions.registerPermission(ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); + DefaultPermissions.registerPermission(ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE, parent); ++ DefaultPermissions.registerPermission(ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper + // Spigot end + parent.recalculatePermissibles(); + } diff --git a/Remapped-Spigot-Server-Patches/0506-Ensure-Entity-AABB-s-are-never-invalid.patch b/Remapped-Spigot-Server-Patches/0506-Ensure-Entity-AABB-s-are-never-invalid.patch new file mode 100644 index 000000000..f77fef5ea --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0506-Ensure-Entity-AABB-s-are-never-invalid.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:12:46 -0400 +Subject: [PATCH] Ensure Entity AABB's are never invalid + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d9bb00752ac81b2171d3ad25fd84904467a18e3b..728379292728cf58f5512feae3cdc74392980f68 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -66,6 +66,7 @@ import net.minecraft.world.Nameable; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.animal.AbstractFish; + import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.decoration.HangingEntity; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.vehicle.AbstractMinecart; +@@ -478,7 +479,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + public void setPos(double x, double y, double z) { + this.setPosRaw(x, y, z); +- this.setBoundingBox(this.dimensions.makeBoundingBox(x, y, z)); ++ //this.a(this.size.a(d0, d1, d2)); // Paper - move into setPositionRaw + if (valid) ((ServerLevel) level).updateChunkPos(this); // CraftBukkit + } + +@@ -2998,6 +2999,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return new AABB(vec3d, vec3d1); + } + ++ public final void setBoundingBox(AABB axisalignedbb) { setBoundingBox(axisalignedbb); } // Paper - OBFHELPER + public void setBoundingBox(AABB boundingBox) { + // CraftBukkit start - block invalid bounding boxes + double minX = boundingBox.minX, +@@ -3436,6 +3438,12 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public void setPosRaw(double x, double y, double z) { ++ // Paper start - never allow AABB to become desynced from position ++ // hanging has its own special logic ++ if (!(this instanceof HangingEntity) && (this.position.x != x || this.position.y != y || this.position.z != z)) { ++ this.setBoundingBox(this.dimensions.makeBoundingBox(x, y, z)); ++ } ++ // Paper end + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); diff --git a/Remapped-Spigot-Server-Patches/0507-Optimize-WorldBorder-collision-checks-and-air.patch b/Remapped-Spigot-Server-Patches/0507-Optimize-WorldBorder-collision-checks-and-air.patch new file mode 100644 index 000000000..bc82f5847 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0507-Optimize-WorldBorder-collision-checks-and-air.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 10 May 2020 22:49:05 -0400 +Subject: [PATCH] Optimize WorldBorder collision checks and air + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 728379292728cf58f5512feae3cdc74392980f68..e9a658b11e2b6683831dc3f5bd20be9a7840ed69 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -102,7 +102,6 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; +-import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; +@@ -908,7 +907,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + AABB axisalignedbb = this.getBoundingBox(); + CollisionContext voxelshapecollision = CollisionContext.of(this); + VoxelShape voxelshape = this.level.getWorldBorder().getCollisionShape(); +- Stream stream = Shapes.joinIsNotEmpty(voxelshape, Shapes.create(axisalignedbb.deflate(1.0E-7D)), BooleanOp.AND) ? Stream.empty() : Stream.of(voxelshape); ++ Stream stream = !this.level.getWorldBorder().isInBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper + Stream stream1 = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement), (entity) -> { + return true; + }); +diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +index feca9ff34936686c0665ae0dbc926869087df3a7..60f8585a736af5b654b8aaed89a39a8bf5e91301 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java ++++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +@@ -143,10 +143,10 @@ public class CollisionSpliterator extends AbstractSpliterator { + AABB axisalignedbb = this.source.getBoundingBox(); + + if (!isBoxFullyWithinWorldBorder(worldborder, axisalignedbb)) { +- VoxelShape voxelshape = worldborder.getCollisionShape(); +- +- if (!isOutsideBorder(voxelshape, axisalignedbb) && isCloseToBorder(voxelshape, axisalignedbb)) { +- consumer.accept(voxelshape); ++ // Paper start ++ if (worldborder.isInBounds(axisalignedbb.deflate(1.0E-7D)) && !worldborder.isInBounds(axisalignedbb.grow(1.0E-7D))) { ++ consumer.accept(worldborder.asVoxelShape()); ++ // Paper end + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 7a728ca96ee2eaf776c391ba8351196a526e18ec..aaa6251838483de5c46913534413151b5cb1d3fe 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -52,6 +52,7 @@ public class WorldBorder { + return (double) pos.getMaxBlockX() > this.getMinX() && (double) pos.getMinBlockX() < this.getMaxX() && (double) pos.getMaxBlockZ() > this.getMinZ() && (double) pos.getMinBlockZ() < this.getMaxZ(); + } + ++ public final boolean isInBounds(AABB aabb) { return this.isWithinBounds(aabb); } // Paper - OBFHELPER + public boolean isWithinBounds(AABB box) { + return box.maxX > this.getMinX() && box.minX < this.getMaxX() && box.maxZ > this.getMinZ() && box.minZ < this.getMaxZ(); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 1603eb3f7d90a4b3a028b20776566db77d09c123..f28d2126bc29fad3971a32cf85a7a7c4803b36ab 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -252,7 +252,7 @@ public final class Shapes { + BlockState iblockdata = world.getTypeIfLoaded(blockposition_mutableblockposition); // Paper + if (iblockdata == null) return 0.0D; // Paper + +- if ((k2 != 1 || iblockdata.hasLargeCollisionShape()) && (k2 != 2 || iblockdata.is(Blocks.MOVING_PISTON))) { ++ if (!iblockdata.isAir() && (k2 != 1 || iblockdata.hasLargeCollisionShape()) && (k2 != 2 || iblockdata.is(Blocks.MOVING_PISTON))) { // Paper + initial = iblockdata.getCollisionShape((BlockGetter) world, blockposition_mutableblockposition, context).collide(enumdirection_enumaxis2, box.move((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), initial); + if (Math.abs(initial) < 1.0E-7D) { + return 0.0D; diff --git a/Remapped-Spigot-Server-Patches/0508-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/Remapped-Spigot-Server-Patches/0508-Fix-Per-World-Difficulty-Remembering-Difficulty.patch new file mode 100644 index 000000000..568d1c34e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0508-Fix-Per-World-Difficulty-Remembering-Difficulty.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 03:59:10 -0400 +Subject: [PATCH] Fix Per World Difficulty / Remembering Difficulty + +Fixes per world difficulty with /difficulty command and also +makes it so that the server keeps the last difficulty used instead +of restoring the server.properties every single load. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f530c739b6aee3718eb5d0e0e6a09d882d817c68..19544b794b5a46c129016172798ff7294fcfed33 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1645,11 +1645,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 28 Jun 2020 19:27:20 -0400 +Subject: [PATCH] Paper dumpitem command + +Let's you quickly view the item in your hands NBT data + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 528c860fc0c04431e0ebb2ae6bc96bf9c2d04789..6fad9329213e4e8a3ef9ce7fb568ad22484a11f3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -12,6 +12,7 @@ import com.google.gson.JsonObject; + import com.google.gson.internal.Streams; + import com.google.gson.stream.JsonWriter; + import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; +@@ -36,7 +37,9 @@ import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.entity.Player; ++import org.bukkit.inventory.ItemStack; + + import java.io.File; + import java.io.FileOutputStream; +@@ -59,7 +62,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight", "dumpitem").build(); + + public PaperCommand(String name) { + super(name); +@@ -168,6 +171,9 @@ public class PaperCommand extends Command { + case "reload": + doReload(sender); + break; ++ case "dumpitem": ++ doDumpItem(sender); ++ break; + case "debug": + doDebug(sender, args); + break; +@@ -200,6 +206,19 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doDumpItem(CommandSender sender) { ++ ItemStack itemInHand = ((CraftPlayer) sender).getItemInHand(); ++ net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(itemInHand); ++ CompoundTag tag = itemStack.getTag(); ++ if (tag != null) { ++ String nbt = org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(tag.getNbtPrettyComponent()); ++ Bukkit.getConsoleSender().sendMessage(nbt); ++ sender.sendMessage(nbt); ++ } else { ++ sender.sendMessage("Item does not have NBT"); ++ } ++ } ++ + private void doFixLight(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only players can use this command"); +diff --git a/src/main/java/net/minecraft/nbt/Tag.java b/src/main/java/net/minecraft/nbt/Tag.java +index 85e9c5f4620fcf48cb3655fbb2db58b3fb31aa74..ba204cf135be333d4ac1c06ee6c2e961faadf8cb 100644 +--- a/src/main/java/net/minecraft/nbt/Tag.java ++++ b/src/main/java/net/minecraft/nbt/Tag.java +@@ -26,6 +26,7 @@ public interface Tag { + return this.toString(); + } + ++ default Component getNbtPrettyComponent() { return this.getPrettyDisplay(); } // Paper - OBFHELPER + default Component getPrettyDisplay() { + return this.getPrettyDisplay("", 0); + } diff --git a/Remapped-Spigot-Server-Patches/0510-Don-t-allow-null-UUID-s-for-chat.patch b/Remapped-Spigot-Server-Patches/0510-Don-t-allow-null-UUID-s-for-chat.patch new file mode 100644 index 000000000..dfc52a21c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0510-Don-t-allow-null-UUID-s-for-chat.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:36:55 -0400 +Subject: [PATCH] Don't allow null UUID's for chat + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +index 002a6c7933f64405707d7d34d3e5c17584539623..a983785bf3bc43f65bd0809870c14a9fd30a3fc1 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java +@@ -3,6 +3,7 @@ package net.minecraft.network.protocol.game; + + import java.io.IOException; + import java.util.UUID; ++import net.minecraft.Util; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; +@@ -21,7 +22,7 @@ public class ClientboundChatPacket implements Packet { + public ClientboundChatPacket(Component message, ChatType location, UUID senderUuid) { + this.message = message; + this.type = location; +- this.sender = senderUuid; ++ this.sender = senderUuid != null ? senderUuid : Util.getNullUUID(); // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0511-Improve-Legacy-Component-serialization-size.patch b/Remapped-Spigot-Server-Patches/0511-Improve-Legacy-Component-serialization-size.patch new file mode 100644 index 000000000..2f031e906 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0511-Improve-Legacy-Component-serialization-size.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:08:41 -0400 +Subject: [PATCH] Improve Legacy Component serialization size + +Don't constantly send format: false for all formatting options when parent already +has it false + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index b27af66795d902a2e95d692fa0ff18eccbef8a75..b89660244ffad484e3fbf69ccb9cdc1bc178d2ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -46,6 +46,7 @@ public final class CraftChatMessage { + // Separate pattern with no group 3, new lines are part of previous string + private static final Pattern INCREMENTAL_PATTERN_KEEP_NEWLINES = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-orx])|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " ]|$))))", Pattern.CASE_INSENSITIVE); + // ChatColor.b does not explicitly reset, its more of empty ++ private static final Style EMPTY = Style.EMPTY.withItalic(false); // Paper - OBFHELPER + private static final Style RESET = Style.EMPTY.withBold(false).withItalic(false).setUnderline(false).setStrikethrough(false).setRandom(false); + + private final List list = new ArrayList(); +@@ -67,6 +68,7 @@ public final class CraftChatMessage { + Matcher matcher = (keepNewlines ? INCREMENTAL_PATTERN_KEEP_NEWLINES : INCREMENTAL_PATTERN).matcher(message); + String match = null; + boolean needsAdd = false; ++ boolean hasReset = false; // Paper + while (matcher.find()) { + int groupId = 0; + while ((match = matcher.group(++groupId)) == null) { +@@ -112,7 +114,26 @@ public final class CraftChatMessage { + throw new AssertionError("Unexpected message format"); + } + } else { // Color resets formatting +- modifier = RESET.withColor(format); ++ // Paper start - improve legacy formatting ++ Style previous = modifier; ++ modifier = (!hasReset ? RESET : EMPTY).withColor(format); ++ hasReset = true; ++ if (previous.isBold()) { ++ modifier = modifier.withBold(false); ++ } ++ if (previous.isItalic()) { ++ modifier = modifier.withItalic(false); ++ } ++ if (previous.isObfuscated()) { ++ modifier = modifier.setRandom(false); ++ } ++ if (previous.isStrikethrough()) { ++ modifier = modifier.setStrikethrough(false); ++ } ++ if (previous.isUnderlined()) { ++ modifier = modifier.setUnderline(false); ++ } ++ // Paper end + } + needsAdd = true; + break; diff --git a/Remapped-Spigot-Server-Patches/0512-Support-old-UUID-format-for-NBT.patch b/Remapped-Spigot-Server-Patches/0512-Support-old-UUID-format-for-NBT.patch new file mode 100644 index 000000000..ffba727ac --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0512-Support-old-UUID-format-for-NBT.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 29 Jun 2020 03:26:17 -0400 +Subject: [PATCH] Support old UUID format for NBT + +We have stored UUID in plenty of places that did not get DFU'd + +So just look for old format and load it if it exists. + +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index c856ca720a9329a94bb07eaa3060c034f95718b3..0b739b5f040697049893d311c99456dbc5470e93 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -142,6 +142,12 @@ public class CompoundTag implements Tag { + + public void setUUID(String prefix, UUID uuid) { putUUID(prefix, uuid); } // Paper - OBFHELPER + public void putUUID(String key, UUID value) { ++ // Paper start - support old format ++ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ this.tags.remove(key + "Most"); ++ this.tags.remove(key + "Least"); ++ } ++ // Paper end + this.tags.put(key, NbtUtils.createUUID(value)); + } + +@@ -151,11 +157,21 @@ public class CompoundTag implements Tag { + */ + public UUID getUUID(String prefix) { return getUUID(prefix); } // Paper - OBFHELPER + public UUID getUUID(String key) { ++ // Paper start - support old format ++ if (!contains(key, 11) && this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ return new UUID(this.getLong(key + "Most"), this.getLong(key + "Least")); ++ } ++ // Paper end + return NbtUtils.loadUUID(this.get(key)); + } + + public final boolean hasUUID(String s) { return this.hasUUID(s); } // Paper - OBFHELPER + public boolean hasUUID(String key) { ++ // Paper start - support old format ++ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ return true; ++ } ++ // Paper end + Tag nbtbase = this.get(key); + + return nbtbase != null && nbtbase.getType() == IntArrayTag.TYPE && ((IntArrayTag) nbtbase).getAsIntArray().length == 4; +diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index bf0da04ec9db3ec8313bddb06c278c13073819d1..f57c5e441045a81072a2edfed0f199d90e6d7fde 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -40,6 +40,11 @@ public final class NbtUtils { + s = tag.getString("Name"); + } + ++ // Paper start - support string UUID's ++ if (tag.contains("Id", 8)) { ++ uuid = UUID.fromString(tag.getString("Id")); ++ } ++ // Paper end + if (tag.hasUUID("Id")) { + uuid = tag.getUUID("Id"); + } diff --git a/Remapped-Spigot-Server-Patches/0513-Clean-up-duplicated-GameProfile-Properties.patch b/Remapped-Spigot-Server-Patches/0513-Clean-up-duplicated-GameProfile-Properties.patch new file mode 100644 index 000000000..40c906021 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0513-Clean-up-duplicated-GameProfile-Properties.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jul 2020 03:12:06 -0400 +Subject: [PATCH] Clean up duplicated GameProfile Properties + +We had a bug where we accidently cloned properties resulting in skulls +growing to large sizes and preventing login. + +This now automatically cleans up the extra properties. + +diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index f57c5e441045a81072a2edfed0f199d90e6d7fde..3abfd21ea84c54aec6256008b3b9e6bbc7ae694c 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -59,8 +59,8 @@ public final class NbtUtils { + while (iterator.hasNext()) { + String s1 = (String) iterator.next(); + ListTag nbttaglist = nbttagcompound1.getList(s1, 10); +- +- for (int i = 0; i < nbttaglist.size(); ++i) { ++ if (nbttaglist.size() == 0) continue; // Paper - remove duplicate properties ++ for (int i = nbttaglist.size() - 1; i < nbttaglist.size(); ++i) { // Paper - remove duplicate properties + CompoundTag nbttagcompound2 = nbttaglist.getCompound(i); + String s2 = nbttagcompound2.getString("Value"); + +@@ -246,7 +246,7 @@ public final class NbtUtils { + Optional optional = property.getValue(propertiesTag.getString(key)); + + if (optional.isPresent()) { +- return (StateHolder) state.setValue(property, (Comparable) optional.get()); ++ return state.setValue(property, optional.get()); // Paper - decompile error + } else { + NbtUtils.LOGGER.warn("Unable to read property: {} with value: {} for blockstate: {}", key, propertiesTag.getString(key), mainTag.toString()); + return state; +@@ -276,8 +276,8 @@ public final class NbtUtils { + return nbttagcompound; + } + +- private static > String getName(net.minecraft.world.level.block.state.properties.Property property, Comparable value) { +- return property.value(value); ++ private static > String getName(net.minecraft.world.level.block.state.properties.Property property, Comparable value) {// Paper - decompile error ++ return property.getName((T) value);// Paper - decompile error + } + + public static CompoundTag update(DataFixer fixer, DataFixTypes fixTypes, CompoundTag tag, int oldVersion) { +diff --git a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java +index 1cb67832a849db96f1cce95c32b41574e990e5b7..d97be7a1dfa7ad413afb8ff7668189fd37baf264 100644 +--- a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java ++++ b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java +@@ -59,6 +59,18 @@ public class PlayerHeadItem extends StandingAndWallBlockItem { + return true; + } else { + // CraftBukkit start ++ // Paper start - clean up old duplicated properties ++ CompoundTag properties = tag.getCompound("SkullOwner").getCompound("Properties"); ++ for (String key : properties.getAllKeys()) { ++ net.minecraft.nbt.ListTag values = properties.getList(key, 10); ++ if (values.size() > 1) { ++ net.minecraft.nbt.Tag texture = values.get(values.size() - 1); ++ values = new net.minecraft.nbt.ListTag(); ++ values.add(texture); ++ properties.put(key, values); ++ } ++ } ++ // Paper end + net.minecraft.nbt.ListTag textures = tag.getCompound("SkullOwner").getCompound("Properties").getList("textures", 10); // Safe due to method contracts + for (int i = 0; i < textures.size(); i++) { + if (textures.get(i) instanceof CompoundTag && !((CompoundTag) textures.get(i)).contains("Signature", 8) && ((CompoundTag) textures.get(i)).getString("Value").trim().isEmpty()) { diff --git a/Remapped-Spigot-Server-Patches/0514-Convert-legacy-attributes-in-Item-Meta.patch b/Remapped-Spigot-Server-Patches/0514-Convert-legacy-attributes-in-Item-Meta.patch new file mode 100644 index 000000000..9306e47d2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0514-Convert-legacy-attributes-in-Item-Meta.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jul 2020 04:50:22 -0400 +Subject: [PATCH] Convert legacy attributes in Item Meta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index 2d547810125f00680ef7e60dd791d0bddd9ebd3e..320fd6780af2fa99e4e4f4193cbc9338d492dc6d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -11,6 +11,20 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + public class CraftAttributeMap implements Attributable { + + private final AttributeMap handle; ++ // Paper start - convert legacy attributes ++ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); ++ ++ public static String convertIfNeeded(String nms) { ++ if (nms == null) { ++ return null; ++ } ++ nms = legacyNMS.getOrDefault(nms, nms); ++ if (!nms.toLowerCase().equals(nms) || nms.indexOf(' ') != -1) { ++ return null; ++ } ++ return nms; ++ } ++ // Paper end + + public CraftAttributeMap(AttributeMap handle) { + this.handle = handle; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 45abfebf3f947dcbd2e7b1d95be8ba918f044e51..cb66998dbaa9d93e92ef4045b83efbb0fd486234 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -480,7 +480,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); + +- String attributeName = entry.getString(ATTRIBUTES_IDENTIFIER.NBT); ++ String attributeName = CraftAttributeMap.convertIfNeeded(entry.getString(ATTRIBUTES_IDENTIFIER.NBT)); // Paper + if (attributeName == null || attributeName.isEmpty()) { + continue; + } diff --git a/Remapped-Spigot-Server-Patches/0515-Remove-some-streams-from-structures.patch b/Remapped-Spigot-Server-Patches/0515-Remove-some-streams-from-structures.patch new file mode 100644 index 000000000..26f90fc29 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0515-Remove-some-streams-from-structures.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Mon, 29 Jun 2020 17:03:06 -0400 +Subject: [PATCH] Remove some streams from structures + +This showed up a lot in the spark profiler, should have a low-medium performance improvement. + +diff --git a/src/main/java/net/minecraft/world/level/StructureFeatureManager.java b/src/main/java/net/minecraft/world/level/StructureFeatureManager.java +index e842dbc586234799a05b6df213b686e17b8ed1ac..2f88e015708cadb43a348ba2b144c3dd92bb95a5 100644 +--- a/src/main/java/net/minecraft/world/level/StructureFeatureManager.java ++++ b/src/main/java/net/minecraft/world/level/StructureFeatureManager.java +@@ -2,21 +2,22 @@ + package net.minecraft.world.level; + + import com.mojang.datafixers.DataFixUtils; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Paper + import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.SectionPos; +-import net.minecraft.core.Vec3i; + import net.minecraft.server.level.WorldGenRegion; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.FeatureAccess; + import net.minecraft.world.level.levelgen.WorldGenSettings; + import net.minecraft.world.level.levelgen.feature.StructureFeature; ++import net.minecraft.world.level.levelgen.structure.StructurePiece; + import net.minecraft.world.level.levelgen.structure.StructureStart; + + public class StructureFeatureManager { + +- private final LevelAccessor level; ++ private final LevelAccessor level; public LevelAccessor getLevel() { return level; } // Paper - OBFHELPER + private final WorldGenSettings worldGenSettings; + + public StructureFeatureManager(LevelAccessor world, WorldGenSettings options) { +@@ -42,6 +43,20 @@ public class StructureFeatureManager { + }); + } + ++ // Paper start - remove structure streams ++ public java.util.List> getFeatureStarts(SectionPos sectionPosition, StructureFeature structureGenerator) { ++ java.util.List> list = new ObjectArrayList<>(); ++ for (Long curLong: getLevel().getChunk(sectionPosition.x(), sectionPosition.z(), ChunkStatus.STRUCTURE_REFERENCES).getReferencesForFeature(structureGenerator)) { ++ SectionPos sectionPosition1 = SectionPos.of(new ChunkPos(curLong), 0); ++ StructureStart structurestart = getStartForFeature(sectionPosition1, structureGenerator, getLevel().getChunk(sectionPosition1.x(), sectionPosition1.z(), ChunkStatus.STRUCTURE_STARTS)); ++ if (structurestart != null && structurestart.e()) { ++ list.add(structurestart); ++ } ++ } ++ return list; ++ } ++ // Paper end ++ + @Nullable + public StructureStart getStartForFeature(SectionPos pos, StructureFeature feature, FeatureAccess holder) { + return holder.getStartForFeature(feature); +@@ -60,13 +75,21 @@ public class StructureFeatureManager { + } + + public StructureStart getStructureAt(BlockPos pos, boolean matchChildren, StructureFeature feature) { +- return (StructureStart) DataFixUtils.orElse(this.startsForFeature(SectionPos.of(pos), feature).filter((structurestart) -> { +- return structurestart.c().b((Vec3i) pos); +- }).filter((structurestart) -> { +- return !matchChildren || structurestart.d().stream().anyMatch((structurepiece) -> { +- return structurepiece.g().b((BaseBlockPosition) blockposition); +- }); +- }).findFirst(), StructureStart.a); ++ // Paper start - remove structure streams ++ for (StructureStart structurestart : getFeatureStarts(SectionPos.of(pos), feature)) { ++ if (structurestart.c().b(pos)) { ++ if (!matchChildren) { ++ return structurestart; ++ } ++ for (StructurePiece structurepiece : structurestart.d()) { ++ if (structurepiece.g().b(pos)) { ++ return structurestart; ++ } ++ } ++ } ++ } ++ return StructureStart.a; ++ // Paper end + } + + // Spigot start +diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java +index ed83335175bb882741dfaef251ab30ce1590f74c..2422dbb8691b8c45401a68602a33d4d7f1718dfb 100644 +--- a/src/main/java/net/minecraft/world/level/biome/Biome.java ++++ b/src/main/java/net/minecraft/world/level/biome/Biome.java +@@ -39,6 +39,7 @@ import net.minecraft.world.level.levelgen.WorldgenRandom; + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; + import net.minecraft.world.level.levelgen.feature.StructureFeature; + import net.minecraft.world.level.levelgen.structure.BoundingBox; ++import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder; + import net.minecraft.world.level.levelgen.synth.PerlinSimplexNoise; + import net.minecraft.world.level.material.FluidState; +@@ -238,9 +239,11 @@ public final class Biome { + int l1 = j1 << 4; + + try { +- structureAccessor.startsForFeature(SectionPos.of(pos), structuregenerator).forEach((structurestart) -> { +- structurestart.a(region, structureAccessor, chunkGenerator, random, new BoundingBox(k1, l1, k1 + 15, l1 + 15), new ChunkPos(i1, j1)); +- }); ++ // Paper start - remove structure streams ++ for (StructureStart structureStart : structureAccessor.getFeatureStarts(SectionPos.of(pos), structuregenerator)) { ++ structureStart.a(region, structureAccessor, chunkGenerator, random, new BoundingBox(k1, l1, k1 + 15, l1 + 15), new ChunkPos(i1, j1)); ++ } ++ // Paper end + } catch (Exception exception) { + CrashReport crashreport = CrashReport.forThrowable(exception, "Feature placement"); + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 04adec255e4650ead8d80bee32a681c98686fb95..20f3899b7e39033ebc0f833e75fbdba29777a168 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -41,6 +41,7 @@ import net.minecraft.world.level.levelgen.feature.structures.StructureTemplatePo + import net.minecraft.world.level.levelgen.structure.BoundingBox; + import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece; + import net.minecraft.world.level.levelgen.structure.StructurePiece; ++import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.synth.ImprovedNoise; + import net.minecraft.world.level.levelgen.synth.PerlinNoise; + import net.minecraft.world.level.levelgen.synth.PerlinSimplexNoise; +@@ -455,7 +456,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + while (iterator.hasNext()) { + StructureFeature structuregenerator = (StructureFeature) iterator.next(); + +- accessor.startsForFeature(SectionPos.of(chunkcoordintpair, 0), structuregenerator).forEach((structurestart) -> { ++ for (StructureStart structurestart : accessor.getFeatureStarts(SectionPos.of(chunkcoordintpair, 0), structuregenerator)) { // Paper - remove structure streams + Iterator iterator1 = structurestart.d().iterator(); + + while (iterator1.hasNext()) { +@@ -487,7 +488,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + } + } + +- }); ++ } // Paper - remove structure streams + } + + double[][][] adouble = new double[2][this.chunkCountZ + 1][this.chunkCountY + 1]; diff --git a/Remapped-Spigot-Server-Patches/0516-Remove-streams-from-classes-related-villager-gossip.patch b/Remapped-Spigot-Server-Patches/0516-Remove-streams-from-classes-related-villager-gossip.patch new file mode 100644 index 000000000..a3582192b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0516-Remove-streams-from-classes-related-villager-gossip.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 1 Jul 2020 18:01:49 -0400 +Subject: [PATCH] Remove streams from classes related villager gossip + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index 0204f05d989d45c0848f810d1953adf0992ce3c2..57832c392910d22aa81ac2b4816d043dd7ac867a 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -9,6 +9,7 @@ import com.mojang.serialization.DynamicOps; + import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Paper + import it.unimi.dsi.fastutil.objects.ObjectIterator; + import java.util.Arrays; + import java.util.Collection; +@@ -51,8 +52,21 @@ public class GossipContainer { + }); + } + ++ // Paper start - Remove streams from reputation ++ private List decompress() { ++ List list = new ObjectArrayList<>(); ++ for (Map.Entry entry : getReputations().entrySet()) { ++ for (GossipContainer.GossipEntry cur : entry.getValue().decompress(entry.getKey())) { ++ if (cur.weightedValue() != 0) ++ list.add(cur); ++ } ++ } ++ return list; ++ } ++ // Paper end ++ + private Collection selectGossipsForTransfer(Random random, int count) { +- List list = (List) this.unpack().collect(Collectors.toList()); ++ List list = decompress(); // Paper - Remove streams from reputation + + if (list.isEmpty()) { + return Collections.emptyList(); +@@ -119,7 +133,7 @@ public class GossipContainer { + } + + public Dynamic store(DynamicOps dynamicops) { +- return new Dynamic(dynamicops, dynamicops.createList(this.unpack().map((reputation_b) -> { ++ return new Dynamic(dynamicops, dynamicops.createList(this.decompress().stream().map((reputation_b) -> { + return reputation_b.store(dynamicops); + }).map(Dynamic::getValue))); + } +@@ -144,18 +158,30 @@ public class GossipContainer { + + public static class EntityGossips { // Paper - make public + +- private final Object2IntMap entries; ++ private final Object2IntMap entries; private Object2IntMap getEntries() { return entries; } // Paper - OBFHELPER + + public EntityGossips() { // Paper - make public - update CraftVillager setReputation on change + this.entries = new Object2IntOpenHashMap(); + } + + public int weightedValue(Predicate gossipTypeFilter) { +- return this.entries.object2IntEntrySet().stream().filter((entry) -> { +- return gossipTypeFilter.test(entry.getKey()); +- }).mapToInt((entry) -> { +- return entry.getIntValue() * ((GossipType) entry.getKey()).weight; +- }).sum(); ++ // Paper start - Remove streams from reputation ++ int weight = 0; ++ for (Object2IntMap.Entry entry : getEntries().object2IntEntrySet()) { ++ if (gossipTypeFilter.test(entry.getKey())) { ++ weight += entry.getIntValue() * entry.getKey().getWeight(); ++ } ++ } ++ return weight; ++ } ++ ++ public List decompress(UUID uuid) { ++ List list = new ObjectArrayList<>(); ++ for (Object2IntMap.Entry entry : getEntries().object2IntEntrySet()) { ++ list.add(new GossipContainer.GossipEntry(uuid, entry.getKey(), entry.getIntValue())); ++ } ++ return list; ++ // Paper - end + } + + public Stream unpack(UUID target) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipType.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipType.java +index 808eaaae5d534427d197c90c8e53494f4c3bfd82..c775d0df2a8f8a0fd32a8ffc26d6ea6978cbb595 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipType.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipType.java +@@ -11,7 +11,7 @@ public enum GossipType { + MAJOR_NEGATIVE("major_negative", -5, 100, 10, 10), MINOR_NEGATIVE("minor_negative", -1, 200, 20, 20), MINOR_POSITIVE("minor_positive", 1, 200, 1, 5), MAJOR_POSITIVE("major_positive", 5, 100, 0, 100), TRADING("trading", 1, 25, 2, 20); + + public final String id; +- public final int weight; ++ public final int weight; public int getWeight() { return weight; } // Paper - OBFHELPER + public final int max; + public final int decayPerDay; + public final int decayPerTransfer; diff --git a/Remapped-Spigot-Server-Patches/0517-Support-components-in-ItemMeta.patch b/Remapped-Spigot-Server-Patches/0517-Support-components-in-ItemMeta.patch new file mode 100644 index 000000000..66bda208c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0517-Support-components-in-ItemMeta.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Sat, 6 Jun 2020 18:13:42 +0200 +Subject: [PATCH] Support components in ItemMeta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index cb66998dbaa9d93e92ef4045b83efbb0fd486234..20e008277d1188fc7b31bfb2522ef9f6429cc3fb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -874,11 +874,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return CraftChatMessage.fromJSONComponent(displayName); + } + ++ // Paper start ++ @Override ++ public net.md_5.bungee.api.chat.BaseComponent[] getDisplayNameComponent() { ++ return displayName == null ? new net.md_5.bungee.api.chat.BaseComponent[0] : net.md_5.bungee.chat.ComponentSerializer.parse(displayName); ++ } ++ // Paper end + @Override + public final void setDisplayName(String name) { + this.displayName = CraftChatMessage.fromStringOrNullToJSON(name); + } + ++ // Paper start ++ @Override ++ public void setDisplayNameComponent(net.md_5.bungee.api.chat.BaseComponent[] component) { ++ this.displayName = net.md_5.bungee.chat.ComponentSerializer.toString(component); ++ } ++ // Paper end + @Override + public boolean hasDisplayName() { + return displayName != null; +@@ -1021,6 +1033,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return this.lore == null ? null : new ArrayList(Lists.transform(this.lore, CraftChatMessage::fromJSONComponent)); + } + ++ // Paper start ++ @Override ++ public List getLoreComponents() { ++ return this.lore == null ? null : new ArrayList<>(this.lore.stream().map(entry -> ++ net.md_5.bungee.chat.ComponentSerializer.parse(entry) ++ ).collect(java.util.stream.Collectors.toList())); ++ } ++ // Paper end + @Override + public void setLore(List lore) { + if (lore == null || lore.isEmpty()) { +@@ -1035,6 +1055,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ @Override ++ public void setLoreComponents(List lore) { ++ if (lore == null) { ++ this.lore = null; ++ } else { ++ if (this.lore == null) { ++ safelyAdd(lore, this.lore = new ArrayList<>(lore.size()), false); ++ } else { ++ this.lore.clear(); ++ safelyAdd(lore, this.lore, false); ++ } ++ } ++ } ++ // Paper end + @Override + public boolean hasCustomModelData() { + return customModelData != null; +@@ -1496,6 +1531,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + for (Object object : addFrom) { ++ // Paper start - support components ++ if(object instanceof net.md_5.bungee.api.chat.BaseComponent[]) { ++ addTo.add(net.md_5.bungee.chat.ComponentSerializer.toString((net.md_5.bungee.api.chat.BaseComponent[]) object)); ++ } else ++ // Paper end + if (!(object instanceof String)) { + if (object != null) { + throw new IllegalArgumentException(addFrom + " cannot contain non-string " + object.getClass().getName()); diff --git a/Remapped-Spigot-Server-Patches/0518-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch b/Remapped-Spigot-Server-Patches/0518-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch new file mode 100644 index 000000000..52cd27053 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0518-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 3 Jul 2020 15:03:33 -0700 +Subject: [PATCH] Improve EntityTargetLivingEntityEvent for 1.16 mobs + +CraftBukkit has a bug in their implementation and is incorrectly handling forget +Also adds more target reasons for why it forgot target. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +index 738162a55eb186f66df4d31e017c9b9a7cc604c2..1b6f34c2a185368aac973e8a5316a03950e4314b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +@@ -33,15 +33,15 @@ public class StopAttackingIfTargetInvalid extends Behavior { + + protected void start(ServerLevel world, E entity, long time) { + if (isTiredOfTryingToReachTarget((LivingEntity) entity)) { +- this.clearAttackTarget(entity); ++ this.d(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET); // Paper + } else if (this.isCurrentTargetDeadOrRemoved(entity)) { +- this.clearAttackTarget(entity); ++ this.d(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED); // Paper + } else if (this.isCurrentTargetInDifferentLevel(entity)) { +- this.clearAttackTarget(entity); ++ this.d(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL); // Paper + } else if (!EntitySelector.ATTACK_ALLOWED.test(this.getAttackTarget(entity))) { +- this.clearAttackTarget(entity); ++ this.d(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } else if (this.stopAttackingWhen.test(this.getAttackTarget(entity))) { +- this.clearAttackTarget(entity); ++ this.d(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } + } + +@@ -65,18 +65,21 @@ public class StopAttackingIfTargetInvalid extends Behavior { + return optional.isPresent() && !((LivingEntity) optional.get()).isAlive(); + } + +- private void clearAttackTarget(E entity) { ++ private void d(E e0, EntityTargetEvent.TargetReason reason) { + // CraftBukkit start +- LivingEntity old = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); +- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); ++ // Paper start - fix this event ++ //EntityLiving old = e0.getBehaviorController().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(e0, null, reason); + if (event.isCancelled()) { + return; + } +- if (event.getTarget() != null) { +- entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); ++ // comment out, bad logic - bad ++ /*if (event.getTarget() != null) { ++ e0.getBehaviorController().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); + return; +- } ++ }*/ ++ // Paper end + // CraftBukkit end +- entity.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET); ++ e0.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET); + } + } diff --git a/Remapped-Spigot-Server-Patches/0519-Add-entity-liquid-API.patch b/Remapped-Spigot-Server-Patches/0519-Add-entity-liquid-API.patch new file mode 100644 index 000000000..87da8f1dc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0519-Add-entity-liquid-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jul 2020 18:11:43 -0500 +Subject: [PATCH] Add entity liquid API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e9a658b11e2b6683831dc3f5bd20be9a7840ed69..aea2457510c75214bbb925307155611e981f115f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1164,12 +1164,13 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.wasTouchingWater; + } + +- private boolean isInRain() { ++ public boolean isInRain() { // Paper - private -> public + BlockPos blockposition = this.blockPosition(); + + return this.level.isRainingAt(blockposition) || this.level.isRainingAt(new BlockPos((double) blockposition.getX(), this.getBoundingBox().maxY, (double) blockposition.getZ())); + } + ++ public final boolean isInBubbleColumn() { return isInBubbleColumn(); } // Paper - OBFHELPER + private boolean isInBubbleColumn() { + return this.level.getBlockState(this.blockPosition()).is(Blocks.BUBBLE_COLUMN); + } +@@ -1183,6 +1184,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.isInWater() || this.isInRain() || this.isInBubbleColumn(); + } + ++ public final boolean isInWaterOrBubbleColumn() { return isInWaterOrBubble(); } // Paper - OBFHELPER + public boolean isInWaterOrBubble() { + return this.isInWater() || this.isInBubbleColumn(); + } +@@ -1325,6 +1327,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + return this.fluidOnEyes == tag; + } + ++ public final boolean isInLava() { return isInLava(); } // Paper - OBFHELPER + public boolean isInLava() { + return !this.firstTick && this.fluidHeight.getDouble(FluidTags.LAVA) > 0.0D; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 76d652386806fd11961611486a1d0a12fe9616a4..deeae62e9926f9435907c68e7d35e7420f5e79dd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1135,5 +1135,29 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { + return getHandle().spawnReason; + } ++ ++ public boolean isInRain() { ++ return getHandle().isInRain(); ++ } ++ ++ public boolean isInBubbleColumn() { ++ return getHandle().isInBubbleColumn(); ++ } ++ ++ public boolean isInWaterOrRain() { ++ return getHandle().isInWaterOrRain(); ++ } ++ ++ public boolean isInWaterOrBubbleColumn() { ++ return getHandle().isInWaterOrBubbleColumn(); ++ } ++ ++ public boolean isInWaterOrRainOrBubbleColumn() { ++ return getHandle().isInWaterOrRainOrBubble(); ++ } ++ ++ public boolean isInLava() { ++ return getHandle().isInLava(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0520-Update-itemstack-legacy-name-and-lore.patch b/Remapped-Spigot-Server-Patches/0520-Update-itemstack-legacy-name-and-lore.patch new file mode 100644 index 000000000..1b999bcc3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0520-Update-itemstack-legacy-name-and-lore.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 1 Jul 2020 11:57:40 -0500 +Subject: [PATCH] Update itemstack legacy name and lore + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 458cdfbeac9d757c9721acd4557a548affa0ede1..04b717326524f400da3562655c25db59e72814ec 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -49,6 +49,7 @@ import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.StringTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.ComponentUtils; +@@ -135,6 +136,44 @@ public final class ItemStack { + list.sort((Comparator) enchantSorter); // Paper + } catch (Exception ignored) {} + } ++ ++ private void processText() { ++ CompoundTag display = getSubTag("display"); ++ if (display != null) { ++ if (display.contains("Name", 8)) { ++ String json = display.getString("Name"); ++ if (json != null && json.contains("\u00A7")) { ++ try { ++ display.put("Name", convert(json)); ++ } catch (JsonParseException jsonparseexception) { ++ display.remove("Name"); ++ } ++ } ++ } ++ if (display.contains("Lore", 9)) { ++ ListTag list = display.getList("Lore", 8); ++ for (int index = 0; index < list.size(); index++) { ++ String json = list.getString(index); ++ if (json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json ++ try { ++ list.set(index, convert(json)); ++ } catch (JsonParseException e) { ++ list.set(index, StringTag.create(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(new TextComponent("")))); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ private StringTag convert(String json) { ++ Component component = Component.Serializer.jsonToComponent(json); ++ if (component instanceof TextComponent && component.getContents().contains("\u00A7") && component.getSiblings().isEmpty()) { ++ // Only convert if the root component is a single comp with legacy in it, don't convert already normal components ++ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(component.getContents())[0]; ++ } ++ return StringTag.create(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); ++ } + // Paper end + + public ItemStack(ItemLike item) { +@@ -180,6 +219,7 @@ public final class ItemStack { + // CraftBukkit start - make defensive copy as this data may be coming from the save thread + this.tag = (CompoundTag) nbttagcompound.getCompound("tag").copy(); + processEnchantOrder(this.tag); // Paper ++ processText(); // Paper + this.getItem().verifyTagAfterLoad(this.tag); + // CraftBukkit end + } +@@ -663,6 +703,7 @@ public final class ItemStack { + } + } + ++ @Nullable public CompoundTag getSubTag(String s) { return getTagElement(s); } // Paper - OBFHELPER + @Nullable + public CompoundTag getTagElement(String key) { + return this.tag != null && this.tag.contains(key, 10) ? this.tag.getCompound(key) : null; diff --git a/Remapped-Spigot-Server-Patches/0521-Spawn-player-in-correct-world-on-login.patch b/Remapped-Spigot-Server-Patches/0521-Spawn-player-in-correct-world-on-login.patch new file mode 100644 index 000000000..5ec61b9da --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0521-Spawn-player-in-correct-world-on-login.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Wyatt Childers +Date: Fri, 3 Jul 2020 14:57:05 -0400 +Subject: [PATCH] Spawn player in correct world on login + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 713cc88dd067c0d918f253b1845f42c0d9eb920f..f36c92e42300c2056075610caf63f8bef0e7edda 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -192,7 +192,18 @@ public abstract class PlayerList { + }String lastKnownName = s; // Paper + // CraftBukkit end + +- if (nbttagcompound != null) { ++ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID. ++ if (nbttagcompound != null && nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) { ++ UUID uid = new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")); ++ org.bukkit.World bWorld = org.bukkit.Bukkit.getServer().getWorld(uid); ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); ++ } else { ++ resourcekey = Level.OVERWORLD; ++ } ++ } else if (nbttagcompound != null) { ++ // Vanilla migration support ++ // Paper end + DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); + Logger logger = PlayerList.LOGGER; + diff --git a/Remapped-Spigot-Server-Patches/0522-Add-PrepareResultEvent.patch b/Remapped-Spigot-Server-Patches/0522-Add-PrepareResultEvent.patch new file mode 100644 index 000000000..be0c76ca0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0522-Add-PrepareResultEvent.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 Jul 2020 11:58:56 -0500 +Subject: [PATCH] Add PrepareResultEvent + +Adds a new event for all crafting stations that generate a result slot item + +Anvil, Grindstone and Smithing now extend this event + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 72b0cfcc5aab03e14e63440c734436e9c1432111..bc39e7464646d712b085251dc0277a5b1ec0a393 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -141,6 +141,7 @@ public abstract class AbstractContainerMenu { + return nonnulllist; + } + ++ public final void notifyListeners() { this.broadcastChanges(); } // Paper - OBFHELPER + public void broadcastChanges() { + int i; + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index 3d53edae7e3d5bb00913384ad0eb67551a65750e..492a42ad5dc460717de8179d522d042cee11db60 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -307,6 +307,7 @@ public class AnvilMenu extends ItemCombinerMenu { + } + + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index 6183e33237a231be388a8ace0ca3b56720db13ee..464f27d3f0cc694257a550cf873a0ee4534e2189 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -146,6 +146,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + this.setupResultSlot(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index e9e830117fe3e4e02a51eef8671a3d3b48c2858e..329a6d70d53c13cd554c64996f2ddc489bdc1e94 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -156,6 +156,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.repairSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index d944cbcdf4d886d3b8b171edd8e2ac8a54dc19b9..8704c4dbead1ff661d84b751479babac5ebc5839 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -71,6 +71,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.inputSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index b33daf92752841e46f2fd9fa20dc1cfa79aa423a..3460fb2bb1451b8456a7fe42449ec4dbce641f40 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -188,7 +188,8 @@ public class LoomMenu extends AbstractContainerMenu { + } + + this.setupResultSlot(); +- this.broadcastChanges(); ++ //this.c(); // Paper - done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +index befb2e6294c3d0a16ae4766c3804d04fd4e1bba5..44aa1f4f91ae9f84fc7ed38cc6b3c11f07d55ba1 100644 +--- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +@@ -78,6 +78,7 @@ public class SmithingMenu extends ItemCombinerMenu { + // CraftBukkit end + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index febf7fa112c470888af171e585ab6a052abb46ca..072bac443e7c54ac2b92e1d93b757bdacf230fbb 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -155,6 +155,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.setupRecipeList(inventory, itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 1); // Paper + } + + private void setupRecipeList(Container input, ItemStack stack) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 87fe7f4f5ed70bf1b3dc1e2a392ba42a1f8f568b..64cfa14aa4e32430a6970fd4f3654a56146ba807 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1522,19 +1522,44 @@ public class CraftEventFactory { + return event; + } + +- public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) { +- PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); +- event.getView().getPlayer().getServer().getPluginManager().callEvent(event); ++ // Paper start - disable this method, handled below ++ public static void callPrepareAnvilEvent(InventoryView view, ItemStack item) { // Paper - verify nothing uses return - handled below in PrepareResult ++ PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item)); // Paper - remove clone ++ //event.getView().getPlayer().getServer().getPluginManager().callEvent(event); // disable event + event.getInventory().setItem(2, event.getResult()); +- return event; ++ //return event; // Paper + } ++ // Paper end + +- public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { +- PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); +- event.getView().getPlayer().getServer().getPluginManager().callEvent(event); ++ // Paper start - disable this method, handled in callPrepareResultEvent ++ public static void callPrepareSmithingEvent(InventoryView view, ItemStack item) { // Paper - verify nothing uses return - handled below in PrepareResult ++ PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item)); // Paper - remove clone ++ //event.getView().getPlayer().getServer().getPluginManager().callEvent(event); // Paper - disable event + event.getInventory().setItem(2, event.getResult()); +- return event; ++ //return event; // Paper + } ++ // Paper end ++ ++ // Paper start - support specific overrides for prepare result ++ public static void callPrepareResultEvent(AbstractContainerMenu container, int resultSlot) { ++ com.destroystokyo.paper.event.inventory.PrepareResultEvent event; ++ InventoryView view = container.getBukkitView(); ++ org.bukkit.inventory.ItemStack origItem = view.getTopInventory().getItem(resultSlot); ++ CraftItemStack result = origItem != null ? CraftItemStack.asCraftCopy(origItem) : null; ++ if (view.getTopInventory() instanceof org.bukkit.inventory.AnvilInventory) { ++ event = new PrepareAnvilEvent(view, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.GrindstoneInventory) { ++ event = new com.destroystokyo.paper.event.inventory.PrepareGrindstoneEvent(view, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.SmithingInventory) { ++ event = new PrepareSmithingEvent(view, result); ++ } else { ++ event = new com.destroystokyo.paper.event.inventory.PrepareResultEvent(view, result); ++ } ++ event.callEvent(); ++ event.getInventory().setItem(resultSlot, event.getResult()); ++ container.notifyListeners(); ++ } ++ // Paper end + + /** + * Mob spawner event. diff --git a/Remapped-Spigot-Server-Patches/0523-Allow-delegation-to-vanilla-chunk-gen.patch b/Remapped-Spigot-Server-Patches/0523-Allow-delegation-to-vanilla-chunk-gen.patch new file mode 100644 index 000000000..183e23a96 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0523-Allow-delegation-to-vanilla-chunk-gen.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Wed, 29 Apr 2020 02:10:32 +0200 +Subject: [PATCH] Allow delegation to vanilla chunk gen + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +index aaa8d78b131c4095b36c6db6078f57f927c15374..54e560348836498f16dde017c6e7e3fcc263eeea 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +@@ -36,7 +36,7 @@ import org.apache.logging.log4j.Logger; + public class UpgradeData { + + private static final Logger LOGGER = LogManager.getLogger(); +- public static final UpgradeData EMPTY = new UpgradeData(); ++ public static final UpgradeData EMPTY = new UpgradeData(); public static UpgradeData getEmptyConverter() { return EMPTY; } // Paper - obfhelper + private static final Direction8[] DIRECTIONS = Direction8.values(); + private final EnumSet sides; + private final int[][] index; +@@ -322,7 +322,7 @@ public class UpgradeData { + if ((Integer) iblockdata.getValue(BlockStateProperties.DISTANCE) >= j) { + world.setBlock(blockposition, (BlockState) iblockdata.setValue(BlockStateProperties.DISTANCE, j), 18); + if (i != 7) { +- Direction[] aenumdirection = null.f; ++ Direction[] aenumdirection = DIRECTIONS; // Paper - decomp fix + int k = aenumdirection.length; + + for (int l = 0; l < k; ++l) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2ec41cb87cec97780f1fa8abfbb756fca4dba1bf..e301aee53b19fc3f93a36d0ed03a649741123bfa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2032,6 +2032,32 @@ public final class CraftServer implements Server { + return new CraftChunkData(world); + } + ++ // Paper start ++ @Override ++ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { ++ // get empty object ++ CraftChunkData data = (CraftChunkData) createChunkData(world); ++ // do bunch of vanilla shit ++ net.minecraft.server.level.ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); ++ net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk(new net.minecraft.world.level.ChunkPos(x, z), net.minecraft.world.level.chunk.UpgradeData.getEmptyConverter(), nmsWorld); ++ List list = new ArrayList<>(); ++ list.add(protoChunk); ++ net.minecraft.server.level.WorldGenRegion genRegion = new net.minecraft.server.level.WorldGenRegion(nmsWorld, list); ++ // call vanilla generator, one feature after another. Order here is important! ++ net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator = nmsWorld.getChunkSource().generator; ++ if (chunkGenerator instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator) { ++ chunkGenerator = ((org.bukkit.craftbukkit.generator.CustomChunkGenerator) chunkGenerator).delegate; ++ } ++ chunkGenerator.createBiomes(nmsWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), protoChunk); ++ chunkGenerator.fillFromNoise(genRegion, nmsWorld.structureFeatureManager(), protoChunk); ++ chunkGenerator.buildSurfaceAndBedrock(genRegion, protoChunk); ++ // copy over generated sections ++ data.setRawChunkData(protoChunk.getSections()); ++ // hooray! ++ return data; ++ } ++ // Paper end ++ + @Override + public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { + return new CraftBossBar(title, color, style, flags); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index 8d72cd6a44cf462cfe3adac9bf99a16883a587df..fd2cb2a584fea360fcf8180338708f35c4e3dc1f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -19,7 +19,7 @@ import org.bukkit.material.MaterialData; + */ + public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; +- private final LevelChunkSection[] sections; ++ private LevelChunkSection[] sections; // Paper - remove final + private Set tiles; + private World world; // Paper - Anti-Xray - Add world + +@@ -168,6 +168,12 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + return sections; + } + ++ // Paper start ++ public void setRawChunkData(LevelChunkSection[] sections) { ++ this.sections = sections; ++ } ++ // Paper end ++ + Set getTiles() { + return tiles; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +index eba3a6a2467116d93945ab2d5dc0a6f41d76f547..e25dc1c87752fcf73181cb02ddaf84b258ac4e9d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +@@ -32,7 +32,7 @@ import org.bukkit.generator.ChunkGenerator.ChunkData; + + public class CustomChunkGenerator extends InternalChunkGenerator { + +- private final net.minecraft.world.level.chunk.ChunkGenerator delegate; ++ public final net.minecraft.world.level.chunk.ChunkGenerator delegate; // Paper - public + private final ChunkGenerator generator; + private final ServerLevel world; + private final Random random = new Random(); diff --git a/Remapped-Spigot-Server-Patches/0524-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/Remapped-Spigot-Server-Patches/0524-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch new file mode 100644 index 000000000..253148a7e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0524-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Jul 2020 14:59:31 -0400 +Subject: [PATCH] Don't check chunk for portal on world gen entity add + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index cec1e6105b8c2ac3d1482c00482d53d6be0d38d1..9724d4222311345a44aa101ec47523a1909fbe8f 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3038,7 +3038,7 @@ public abstract class LivingEntity extends Entity { + Entity entity = this.getVehicle(); + + super.stopRiding(suppressCancellation); // Paper - suppress +- if (entity != null && entity != this.getVehicle() && !this.level.isClientSide) { ++ if (entity != null && entity != this.getVehicle() && !this.level.isClientSide && entity.valid) { // Paper - don't process on world gen + this.dismountVehicle(entity); + } + diff --git a/Remapped-Spigot-Server-Patches/0525-Optimize-NetworkManager-Exception-Handling.patch b/Remapped-Spigot-Server-Patches/0525-Optimize-NetworkManager-Exception-Handling.patch new file mode 100644 index 000000000..dc51ae715 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0525-Optimize-NetworkManager-Exception-Handling.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sun, 5 Jul 2020 22:38:18 -0400 +Subject: [PATCH] Optimize NetworkManager Exception Handling + + +diff --git a/src/main/java/net/minecraft/network/ConnectionProtocol.java b/src/main/java/net/minecraft/network/ConnectionProtocol.java +index fca778d131aa10e88d5f7ed8d57eda6803318184..47a5ee9db64184f173af5984765e9b6d1a8ec367 100644 +--- a/src/main/java/net/minecraft/network/ConnectionProtocol.java ++++ b/src/main/java/net/minecraft/network/ConnectionProtocol.java +@@ -151,6 +151,7 @@ public enum ConnectionProtocol { + + @Nullable + public Packet createPacket(int id) { ++ if (id < 0 || id >= this.idToConstructor.size()) return null; // Paper + Supplier> supplier = (Supplier) this.idToConstructor.get(id); + + return supplier != null ? (Packet) supplier.get() : null; +diff --git a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java +index 8363b63f8dbd16948eeba3f912dc43605ad8db6a..fbebef6525a0872fecc081c7f63f27c1a04e11dc 100644 +--- a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java ++++ b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java +@@ -9,11 +9,21 @@ import java.util.List; + + public class Varint21FrameDecoder extends ByteToMessageDecoder { + ++ private final byte[] lenBuf = new byte[3]; // Paper + public Varint21FrameDecoder() {} + + protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { ++ // Paper start - if channel is not active just discard the packet ++ if (!channelhandlercontext.channel().isActive()) { ++ bytebuf.skipBytes(bytebuf.readableBytes()); ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); +- byte[] abyte = new byte[3]; ++ // Paper start - reuse temporary length buffer ++ byte[] abyte = lenBuf; ++ java.util.Arrays.fill(abyte, (byte) 0); ++ // Paper end + + for (int i = 0; i < abyte.length; ++i) { + if (!bytebuf.isReadable()) { +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index 22db5d0d2cc33498ca40162c66aa3b5fbf2f569f..3163502cb9bbbb5a00e7f06cf0032b1ad93c354d 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -2,8 +2,10 @@ package net.minecraft.network.protocol; + + import io.netty.channel.ChannelFuture; // Paper + import java.io.IOException; ++import net.minecraft.network.Connection; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.PacketListener; ++import net.minecraft.server.level.ServerPlayer; + + public interface Packet { + +@@ -18,17 +20,17 @@ public interface Packet { + /** + * @param player Null if not at PLAY stage yet + */ +- default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {} ++ default void onPacketDispatch(@javax.annotation.Nullable ServerPlayer player) {} + + /** + * @param player Null if not at PLAY stage yet + * @param future Can be null if packet was cancelled + */ +- default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable ChannelFuture future) {} ++ default void onPacketDispatchFinish(@javax.annotation.Nullable ServerPlayer player, @javax.annotation.Nullable ChannelFuture future) {} + default boolean hasFinishListener() { return false; } + default boolean isReady() { return true; } + default java.util.List getExtraPackets() { return null; } +- default boolean packetTooLarge(NetworkManager manager) { ++ default boolean packetTooLarge(Connection manager) { + return false; + } + // Paper end +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index 4ae8201d7dcffeb3298a4e593f978e15ffc5ac15..5812e518222e419da141ab0f70d1e7a3939a0df0 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -1,6 +1,9 @@ + package net.minecraft.network.protocol; + ++import net.minecraft.network.Connection; + import net.minecraft.network.PacketListener; ++import net.minecraft.network.chat.TextComponent; ++import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import co.aikar.timings.MinecraftTimings; // Paper +@@ -30,6 +33,21 @@ public class PacketUtils { + try (Timing ignored = timing.startTiming()) { // Paper - timings + packet.handle(listener); + } // Paper - timings ++ // Paper start ++ catch (Exception e) { ++ Connection networkmanager = listener.a(); ++ if (networkmanager.getPlayer() != null) { ++ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), e); ++ } else { ++ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), e); ++ } ++ TextComponent error = new TextComponent("Packet processing error"); ++ networkmanager.send(new ClientboundDisconnectPacket(error), (future) -> { ++ networkmanager.disconnect(error); ++ }); ++ networkmanager.setReadOnly(); ++ } ++ // Paper end + } else { + PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); + } diff --git a/Remapped-Spigot-Server-Patches/0526-Fix-Concurrency-issue-in-WeightedList.patch b/Remapped-Spigot-Server-Patches/0526-Fix-Concurrency-issue-in-WeightedList.patch new file mode 100644 index 000000000..7c0af04ac --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0526-Fix-Concurrency-issue-in-WeightedList.patch @@ -0,0 +1,153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 6 Jul 2020 18:36:41 -0400 +Subject: [PATCH] Fix Concurrency issue in WeightedList + +if multiple threads from worldgen sort at same time, it will crash. +So make a copy of the list for sorting purposes. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +index 0a65e442ddc31c06f3bb0ff5aa152daee7a210af..a81ad258b39b7472312ab1bedeeacaf26ffae4f7 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -17,7 +17,7 @@ public class GateBehavior extends Behavior { + private final Set> exitErasedMemories; + private final GateBehavior.OrderPolicy orderPolicy; + private final GateBehavior.RunningPolicy runningPolicy; +- private final WeightedList> behaviors = new WeightedList<>(); ++ private final WeightedList> behaviors = new WeightedList<>(false); // Paper - don't use a clone + + public GateBehavior(Map, MemoryStatus> requiredMemoryState, Set> memoriesToForgetWhenStopped, GateBehavior.OrderPolicy order, GateBehavior.RunningPolicy runMode, List, Integer>> tasks) { + super(requiredMemoryState); +@@ -65,10 +65,9 @@ public class GateBehavior extends Behavior { + }).forEach((behavior) -> { + behavior.g(world, entity, time); + }); +- Set set = this.exitErasedMemories; + Brain behaviorcontroller = entity.getBrain(); + +- set.forEach(behaviorcontroller::removeMemory); ++ this.exitErasedMemories.forEach(behaviorcontroller::eraseMemory); // Paper - decomp fix + } + + @Override +@@ -111,11 +110,11 @@ public class GateBehavior extends Behavior { + static enum OrderPolicy { + + ORDERED((weightedlist) -> { +- }), SHUFFLED(WeightedList::a); ++ }), SHUFFLED(WeightedList::shuffle); + + private final Consumer> consumer; + +- private OrderPolicy(Consumer consumer) { ++ private OrderPolicy(Consumer> consumer) { // Paper - decomp fix + this.consumer = consumer; + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +index e4c1c58e9a9c744c7ebb9948a27766b84a081b9e..85df30ef7c03c2f8ae741a8cac8bf601490d2539 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +@@ -6,7 +6,7 @@ import com.mojang.serialization.Codec; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.DynamicOps; +-import com.mojang.serialization.OptionalDynamic; ++ + import java.util.Comparator; + import java.util.List; + import java.util.Random; +@@ -14,26 +14,32 @@ import java.util.stream.Stream; + + public class WeightedList { + +- protected final List> entries; ++ protected final List> list; // Paper - decompile conflict + private final Random random; ++ private final boolean isUnsafe; // Paper + +- public WeightedList() { +- this(Lists.newArrayList()); ++ // Paper start - add useClone option ++ public WeightedList() { this(true); } ++ public WeightedList(boolean isUnsafe) { ++ this(Lists.newArrayList(), isUnsafe); + } + +- private WeightedList(List> entries) { ++ private WeightedList(List> entries) { this(entries, true); } ++ private WeightedList(List> list, boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end + this.random = new Random(); +- this.entries = Lists.newArrayList(entries); ++ this.list = Lists.newArrayList(list); // Paper - decompile conflict + } + + public static Codec> codec(Codec codec) { +- return WeightedList.entries.a(codec).listOf().xmap(WeightedList::new, (weightedlist) -> { +- return weightedlist.a; ++ return WeightedList.WeightedEntry.codec(codec).listOf().xmap(WeightedList::new, (weightedlist) -> { // Paper - decompile conflict ++ return weightedlist.list; // Paper - decompile conflict + }); + } + + public WeightedList add(U item, int weight) { +- this.entries.add(new WeightedList.WeightedEntry<>(item, weight)); ++ this.list.add(new WeightedList.WeightedEntry<>(item, weight)); // Paper - decompile conflict + return this; + } + +@@ -42,21 +48,20 @@ public class WeightedList { + } + + public WeightedList shuffle(Random random) { +- this.entries.forEach((weightedlist_a) -> { +- weightedlist_a.setRandom(random.nextFloat()); +- }); +- this.entries.sort(Comparator.comparingDouble((object) -> { +- return ((WeightedList.WeightedEntry) object).getRandWeight(); +- })); +- return this; ++ // Paper start - make concurrent safe, work off a clone of the list ++ List> list = isUnsafe ? new java.util.ArrayList>(this.list) : this.list; ++ list.forEach((weightedlist_a) -> weightedlist_a.setRandom(random.nextFloat())); ++ list.sort(Comparator.comparingDouble(WeightedEntry::getRandWeight)); ++ return isUnsafe ? new WeightedList<>(list, isUnsafe) : this; ++ // Paper end + } + + public boolean isEmpty() { +- return this.entries.isEmpty(); ++ return this.list.isEmpty(); // Paper - decompile conflict + } + + public Stream stream() { +- return this.entries.stream().map(WeightedList.entries::a); ++ return this.list.stream().map(WeightedList.WeightedEntry::getData); // Paper - decompile conflict + } + + public U getOne(Random random) { +@@ -64,7 +69,7 @@ public class WeightedList { + } + + public String toString() { +- return "WeightedList[" + this.entries + "]"; ++ return "WeightedList[" + this.list + "]"; // Paper - decompile conflict + } + + public static class WeightedEntry { +@@ -98,11 +103,7 @@ public class WeightedList { + return new Codec>() { + public DataResult, T>> decode(DynamicOps dynamicops, T t0) { + Dynamic dynamic = new Dynamic(dynamicops, t0); +- OptionalDynamic optionaldynamic = dynamic.get("data"); +- Codec codec1 = codec; +- +- codec.getClass(); +- return optionaldynamic.flatMap(codec1::parse).map((object) -> { ++ return dynamic.get("data").flatMap(codec::parse).map((object) -> { // Paper - decompile error + return new WeightedList.WeightedEntry<>(object, dynamic.get("weight").asInt(1)); + }).map((weightedlist_a) -> { + return Pair.of(weightedlist_a, dynamicops.empty()); diff --git a/Remapped-Spigot-Server-Patches/0527-Optimize-the-advancement-data-player-iteration-to-be.patch b/Remapped-Spigot-Server-Patches/0527-Optimize-the-advancement-data-player-iteration-to-be.patch new file mode 100644 index 000000000..a23f3a28c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0527-Optimize-the-advancement-data-player-iteration-to-be.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Wyatt Childers +Date: Sat, 4 Jul 2020 23:07:43 -0400 +Subject: [PATCH] Optimize the advancement data player iteration to be O(N) + rather than O(N^2) + + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 5ab62fc74085bbbb0c81b2f4d16a35c9345cd1f1..af8553f1b22e24fbeb732937fbbffc95cb9dfe90 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -457,6 +457,16 @@ public class PlayerAdvancements { + } + + private void ensureVisibility(Advancement advancement) { ++ // Paper start ++ e(advancement, IterationEntryPoint.ROOT); ++ } ++ private enum IterationEntryPoint { ++ ROOT, ++ ITERATOR, ++ PARENT_OF_ITERATOR ++ } ++ private void e(Advancement advancement, IterationEntryPoint entryPoint) { ++ // Paper end + boolean flag = this.shouldBeVisible(advancement); + boolean flag1 = this.visible.contains(advancement); + +@@ -472,15 +482,23 @@ public class PlayerAdvancements { + } + + if (flag != flag1 && advancement.getParent() != null) { +- this.ensureVisibility(advancement.getParent()); ++ // Paper start - If we're not coming from an iterator consider this to be a root entry, otherwise ++ // market that we're entering from the parent of an iterator. ++ this.e(advancement.getParent(), entryPoint == IterationEntryPoint.ITERATOR ? IterationEntryPoint.PARENT_OF_ITERATOR : IterationEntryPoint.ROOT); + } + ++ // If this is true, we've went through a child iteration, entered the parent, processed the parent ++ // and are about to reprocess the children. Stop processing here to prevent O(N^2) processing. ++ if (entryPoint == IterationEntryPoint.PARENT_OF_ITERATOR) { ++ return; ++ } // Paper end ++ + Iterator iterator = advancement.getChildren().iterator(); + + while (iterator.hasNext()) { + Advancement advancement1 = (Advancement) iterator.next(); + +- this.ensureVisibility(advancement1); ++ this.e(advancement1, IterationEntryPoint.ITERATOR); // Paper - Mark this call as being from iteration + } + + } diff --git a/Remapped-Spigot-Server-Patches/0528-Fix-arrows-never-despawning-MC-125757.patch b/Remapped-Spigot-Server-Patches/0528-Fix-arrows-never-despawning-MC-125757.patch new file mode 100644 index 000000000..677615b01 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0528-Fix-arrows-never-despawning-MC-125757.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 8 Jul 2020 11:24:30 -0500 +Subject: [PATCH] Fix arrows never despawning MC-125757 + +This forces the despawn counter to start ticking regardless of +state after the arrow has been alive for 200 ticks (10 seconds) +instead of getting stuck in a never despawn state (bubble columns, +etc). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 6225f390b51733217a809910182f58acea1055e2..73df844610530bbfb133bd59d00015117b59b215 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -169,6 +169,7 @@ public abstract class AbstractArrow extends Projectile { + + ++this.inGroundTime; + } else { ++ if (tickCount > 200) this.tickDespawnCounter(); // Paper - tick despawnCounter regardless after 10 seconds + this.inGroundTime = 0; + Vec3 vec3d2 = this.position(); + +@@ -290,6 +291,7 @@ public abstract class AbstractArrow extends Projectile { + + } + ++ protected final void tickDespawnCounter() { this.tickDespawn(); } // Paper - OBFHELPER + protected void tickDespawn() { + ++this.life; + if (this.life >= (pickup == Pickup.CREATIVE_ONLY ? level.paperConfig.creativeArrowDespawnRate : (pickup == Pickup.DISALLOWED ? level.paperConfig.nonPlayerArrowDespawnRate : ((this instanceof ThrownTrident) ? level.spigotConfig.tridentDespawnRate : level.spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - TODO: Extract this to init? diff --git a/Remapped-Spigot-Server-Patches/0529-Thread-Safe-Vanilla-Command-permission-checking.patch b/Remapped-Spigot-Server-Patches/0529-Thread-Safe-Vanilla-Command-permission-checking.patch new file mode 100644 index 000000000..41cf79026 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0529-Thread-Safe-Vanilla-Command-permission-checking.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Jul 2020 03:54:28 -0400 +Subject: [PATCH] Thread Safe Vanilla Command permission checking + +Datapacks check this on load and are built concurrently. This was breaking them badly due +to race conditions. + +Plus, .canUse we want to be safe for async anyways. + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index 5c35cef42af4053332c02b4960c227fe95d4c197..757ed7a0887f4bdb187ca7c757db5c188362f1a0 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -74,10 +74,10 @@ public abstract class CommandNode implements Comparable> { + public synchronized boolean canUse(final S source) { + if (source instanceof CommandSourceStack) { + try { +- ((CommandSourceStack) source).currentCommand = this; ++ ((CommandSourceStack) source).currentCommand.set(this); // Paper + return requirement.test(source); + } finally { +- ((CommandSourceStack) source).currentCommand = null; ++ ((CommandSourceStack) source).currentCommand.set(null); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index e1f4ffaa36bfffb7741c74b7a094e26a03a9a1e6..2c024f8f8b949dc8cebd29a10415eeac6d50902e 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -54,7 +54,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + private final ResultConsumer consumer; + private final EntityAnchorArgument.Anchor anchor; + private final Vec2 rotation; +- public volatile CommandNode currentCommand; // CraftBukkit ++ public ThreadLocal currentCommand = new ThreadLocal<>(); // CraftBukkit // Paper + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String simpleName, Component name, MinecraftServer server, @Nullable Entity entity) { + this(output, pos, rot, world, level, simpleName, name, server, entity, false, (commandcontext, flag, j) -> { +@@ -171,9 +171,11 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + @Override + public boolean hasPermission(int level) { + // CraftBukkit start +- CommandNode currentCommand = this.currentCommand; ++ // Paper start - fix concurrency issue ++ CommandNode currentCommand = this.currentCommand.get(); + if (currentCommand != null) { + return hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand)); ++ // Paper end + } + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0530-Move-range-check-for-block-placing-up.patch b/Remapped-Spigot-Server-Patches/0530-Move-range-check-for-block-placing-up.patch new file mode 100644 index 000000000..7fba76509 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0530-Move-range-check-for-block-placing-up.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Jul 2020 19:34:11 -0700 +Subject: [PATCH] Move range check for block placing up + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 773f2589c14e16d2f5b01a6dbd48e09d17d19c7e..d264fca2737f83a0860394f7bb6b269ffe669594 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1661,15 +1661,19 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + BlockPos blockposition = movingobjectpositionblock.getBlockPos(); + Direction enumdirection = movingobjectpositionblock.getDirection(); + ++ // Paper start - move check up ++ Location eyeLoc = this.getPlayer().getEyeLocation(); ++ double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ()); ++ if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) { ++ return; ++ } ++ // Paper end - move check up ++ + this.player.resetLastActionTime(); + if (blockposition.getY() < this.server.getMaxBuildHeight()) { + if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract((net.minecraft.world.entity.player.Player) this.player, blockposition)) { + // CraftBukkit start - Check if we can actually do something over this large a distance +- Location eyeLoc = this.getPlayer().getEyeLocation(); +- double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ()); +- if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) { +- return; +- } ++ // Paper - move check up + this.player.stopUsingItem(); // SPIGOT-4706 + // CraftBukkit end + InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); diff --git a/Remapped-Spigot-Server-Patches/0531-Fix-SPIGOT-5989.patch b/Remapped-Spigot-Server-Patches/0531-Fix-SPIGOT-5989.patch new file mode 100644 index 000000000..84a6a9da9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0531-Fix-SPIGOT-5989.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 15 Jul 2020 21:42:52 -0400 +Subject: [PATCH] Fix SPIGOT-5989 + +Before this fix, if a player was respawning to a respawn anchor and +the respawn location was modified away from the anchor with the +PlayerRespawnEvent, the anchor would still lose some charge. +This fixes that by checking if the modified spawn location is +still at a respawn anchor. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f36c92e42300c2056075610caf63f8bef0e7edda..882cd25c9610f0b995c27291aa62846922ab531f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -84,6 +84,7 @@ import net.minecraft.world.level.GameType; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.RespawnAnchorBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.border.BorderChangeListener; + import net.minecraft.world.level.border.WorldBorder; +@@ -846,6 +847,7 @@ public abstract class PlayerList { + // Paper start + boolean isBedSpawn = false; + boolean isRespawn = false; ++ boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 + // Paper end + + // CraftBukkit start - fire PlayerRespawnEvent +@@ -856,7 +858,7 @@ public abstract class PlayerList { + Optional optional; + + if (blockposition != null) { +- optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, flag); ++ optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, true); // Paper - Fix SPIGOT-5989 + } else { + optional = Optional.empty(); + } +@@ -899,7 +901,12 @@ public abstract class PlayerList { + } + // Spigot End + +- location = respawnEvent.getRespawnLocation(); ++ // Paper start - Fix SPIGOT-5989 ++ if (!location.equals(respawnEvent.getRespawnLocation()) ) { ++ location = respawnEvent.getRespawnLocation(); ++ isLocAltered = true; ++ } ++ // Paper end + if (!flag) entityplayer.reset(); // SPIGOT-4785 + isRespawn = true; // Paper + } else { +@@ -937,8 +944,12 @@ public abstract class PlayerList { + } + // entityplayer1.syncInventory(); + entityplayer1.setHealth(entityplayer1.getHealth()); +- if (flag2) { +- entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F)); ++ // Paper start - Fix SPIGOT-5989 ++ if (flag2 && !isLocAltered) { ++ BlockState data = worldserver1.getBlockState(blockposition); ++ worldserver1.setBlock(blockposition, data.setValue(RespawnAnchorBlock.CHARGE, data.getValue(RespawnAnchorBlock.CHARGE) - 1), 3); ++ entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) location.getX(), (double) location.getY(), (double) location.getZ(), 1.0F, 1.0F)); ++ // Paper end + } + // Added from changeDimension + sendAllPlayerInfo(entityplayer); // Update health, etc... diff --git a/Remapped-Spigot-Server-Patches/0532-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/Remapped-Spigot-Server-Patches/0532-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch new file mode 100644 index 000000000..52bb1345a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0532-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 13:12:33 -0500 +Subject: [PATCH] Fix SPIGOT-5824 Bukkit world-container is not used + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 855b3b4c90d84d4efa8395a76010b4b194591cbc..4e353432281a6dbbb49eaa4a6cb4eb051d1a08c5 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -124,11 +124,20 @@ public class Main { + return; + } + +- File file = (File) optionset.valueOf("universe"); // CraftBukkit ++ // Paper start - fix SPIGOT-5824 ++ File file; ++ File userCacheFile = new File("usercache.json"); ++ if (optionset.has("universe")) { ++ file = (File) optionset.valueOf("universe"); // CraftBukkit ++ userCacheFile = new File(file, "usercache.json"); ++ } else { ++ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); ++ } ++ // Paper end - fix SPIGOT-5824 + YggdrasilAuthenticationService yggdrasilauthenticationservice = new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY); // Paper + MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService(); + GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); +- GameProfileCache usercache = new GameProfileCache(gameprofilerepository, new File(file, MinecraftServer.USERID_CACHE_FILE.getName())); ++ GameProfileCache usercache = new GameProfileCache(gameprofilerepository, userCacheFile); // Paper - only move usercache.json into folder if --universe is used, not world-container + // CraftBukkit start + String s = (String) Optional.ofNullable(optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); diff --git a/Remapped-Spigot-Server-Patches/0533-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/Remapped-Spigot-Server-Patches/0533-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch new file mode 100644 index 000000000..fb2e14a9d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0533-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 12:38:12 -0500 +Subject: [PATCH] Fix SPIGOT-5885 Unable to disable advancements + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 4e353432281a6dbbb49eaa4a6cb4eb051d1a08c5..9dc9a5e6ad7f23c8bf3553c765ceeecd67a49ac1 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -124,6 +124,7 @@ public class Main { + return; + } + ++ org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init + // Paper start - fix SPIGOT-5824 + File file; + File userCacheFile = new File("usercache.json"); diff --git a/Remapped-Spigot-Server-Patches/0534-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/Remapped-Spigot-Server-Patches/0534-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch new file mode 100644 index 000000000..8ec38751c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0534-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 13 Jul 2020 06:22:54 -0700 +Subject: [PATCH] Fix AdvancementDataPlayer leak due from quitting early in + login + +Move the criterion storage to the AdvancementDataPlayer object +itself, so the criterion object stores no references - and thus +needs no cleanup. + +diff --git a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +index 31650e41b37e322d2e8a4d4a3deec95851e72675..a645c79af856d2484a4ce7aa2885a32358b9a480 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -16,25 +16,25 @@ import net.minecraft.world.level.storage.loot.LootContext; + + public abstract class SimpleCriterionTrigger implements CriterionTrigger { + +- private final Map>> a = Maps.newIdentityHashMap(); ++ //private final Map>> a = Maps.newIdentityHashMap(); // Paper - moved into AdvancementDataPlayer to fix memory leak + + public SimpleCriterionTrigger() {} + + @Override + public final void a(PlayerAdvancements advancementdataplayer, CriterionTrigger.Listener criteriontrigger_a) { +- ((Set) this.a.computeIfAbsent(advancementdataplayer, (advancementdataplayer1) -> { ++ (advancementdataplayer.criterionData.computeIfAbsent(this, (advancementdataplayer1) -> { // Paper - fix AdvancementDataPlayer leak + return Sets.newHashSet(); + })).add(criteriontrigger_a); + } + + @Override + public final void b(PlayerAdvancements advancementdataplayer, CriterionTrigger.Listener criteriontrigger_a) { +- Set> set = (Set) this.a.get(advancementdataplayer); ++ Set> set = (Set) advancementdataplayer.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + + if (set != null) { + set.remove(criteriontrigger_a); + if (set.isEmpty()) { +- this.a.remove(advancementdataplayer); ++ advancementdataplayer.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + } + +@@ -42,7 +42,7 @@ public abstract class SimpleCriterionTrigger tester) { + PlayerAdvancements advancementdataplayer = player.getAdvancements(); +- Set> set = (Set) this.a.get(advancementdataplayer); ++ Set> set = (Set) advancementdataplayer.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + + if (set != null && !set.isEmpty()) { + LootContext loottableinfo = EntityPredicate.createContext(player, player); +@@ -67,7 +67,7 @@ public abstract class SimpleCriterionTrigger> criterionData = Maps.newIdentityHashMap(); ++ // Paper end - fix advancement data player leakage ++ + public PlayerAdvancements(DataFixer datafixer, PlayerList playerlist, ServerAdvancementManager advancementdataworld, File file, ServerPlayer entityplayer) { + this.dataFixer = datafixer; + this.playerList = playerlist; diff --git a/Remapped-Spigot-Server-Patches/0535-Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/Remapped-Spigot-Server-Patches/0535-Add-missing-strikeLighting-call-to-World-spigot-stri.patch new file mode 100644 index 000000000..468888ab9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0535-Add-missing-strikeLighting-call-to-World-spigot-stri.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 26 Jul 2020 12:11:39 +0100 +Subject: [PATCH] Add missing strikeLighting call to + World#spigot()#strikeLightningEffect + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 4436b3d23dc8f33925da1ec539ea16307e0785b9..793b1309528671ce822d5a484ff9e40d6eba4e9d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2609,6 +2609,7 @@ public class CraftWorld implements World { + lightning.moveTo( loc.getX(), loc.getY(), loc.getZ() ); + lightning.visualOnly = true; + lightning.isSilent = isSilent; ++ world.strikeLightning( lightning ); + return (LightningStrike) lightning.getBukkitEntity(); + } + }; diff --git a/Remapped-Spigot-Server-Patches/0536-Fix-some-rails-connecting-improperly.patch b/Remapped-Spigot-Server-Patches/0536-Fix-some-rails-connecting-improperly.patch new file mode 100644 index 000000000..a1593c3e2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0536-Fix-some-rails-connecting-improperly.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 24 Jul 2020 15:56:05 -0700 +Subject: [PATCH] Fix some rails connecting improperly + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +index 1a44c8b41928a83a22b53d1b6f45ce39b4caf2b2..7cef6d1fc2045c62d4e96a0fd0a311d089cb1406 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +@@ -62,6 +62,7 @@ public abstract class BaseRailBlock extends Block { + state = this.updateDir(world, pos, state, true); + if (this.isStraight) { + state.neighborChanged(world, pos, this, pos, notify); ++ state = world.getBlockState(pos); // Paper - don't desync, update again + } + + return state; +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 0ab1e15d8575c3e90a10b80b94030e15a01faac9..1854809e045300e84a713dc7c3a8264f53ec6c0f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -70,6 +70,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + private void checkPressed(Level world, BlockPos pos, BlockState state) { + if (this.canSurvive(state, world, pos)) { ++ if (state.getBlock() != this) { return; } // Paper - not our block, don't do anything + boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); + boolean flag1 = false; + List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (Predicate) null); +diff --git a/src/main/java/net/minecraft/world/level/block/RailState.java b/src/main/java/net/minecraft/world/level/block/RailState.java +index 0d824ab98dcdd6ea9dac025c37970fb4ec464131..4c17bec369fb19f47760e30b391b2128cee6b276 100644 +--- a/src/main/java/net/minecraft/world/level/block/RailState.java ++++ b/src/main/java/net/minecraft/world/level/block/RailState.java +@@ -12,13 +12,19 @@ import net.minecraft.world.level.block.state.properties.RailShape; + + public class RailState { + +- private final Level level; +- private final BlockPos pos; ++ private final Level level; public final Level getWorld() { return this.level; } // Paper - OBFHELPER ++ private final BlockPos pos; public final BlockPos getPos() { return this.pos; } // Paper - OBFHELPER + private final BaseRailBlock block; +- private BlockState state; ++ private BlockState state; public final BlockState getRailState() { return this.state; } // Paper - OBFHELPER + private final boolean isStraight; + private final List connections = Lists.newArrayList(); + ++ // Paper start - prevent desync ++ public boolean isValid() { ++ return this.getWorld().getBlockState(this.getPos()).getBlock() == this.getRailState().getBlock(); ++ } ++ // Paper end - prevent desync ++ + public RailState(Level world, BlockPos pos, BlockState state) { + this.level = world; + this.pos = pos; +@@ -153,6 +159,11 @@ public class RailState { + } + + private void connectTo(RailState placementHelper) { ++ // Paper start - prevent desync ++ if (!this.isValid() || !placementHelper.isValid()) { ++ return; ++ } ++ // Paper end - prevent desync + this.connections.add(placementHelper.pos); + BlockPos blockposition = this.pos.north(); + BlockPos blockposition1 = this.pos.south(); +@@ -347,11 +358,16 @@ public class RailState { + this.state = (BlockState) this.state.setValue(this.block.getShapeProperty(), blockpropertytrackposition1); + if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { + this.level.setBlock(this.pos, this.state, 3); ++ // Paper start - prevent desync ++ if (!this.isValid()) { ++ return this; ++ } ++ // Paper end - prevent desync + + for (int i = 0; i < this.connections.size(); ++i) { + RailState minecarttracklogic = this.getRail((BlockPos) this.connections.get(i)); + +- if (minecarttracklogic != null) { ++ if (minecarttracklogic != null && minecarttracklogic.isValid()) { // Paper - prevent desync + minecarttracklogic.removeSoftConnections(); + if (minecarttracklogic.canConnectTo(this)) { + minecarttracklogic.connectTo(this); +@@ -364,6 +380,6 @@ public class RailState { + } + + public BlockState getState() { +- return this.state; ++ return this.getWorld().getBlockState(this.getPos()); // Paper - prevent desync + } + } diff --git a/Remapped-Spigot-Server-Patches/0537-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/Remapped-Spigot-Server-Patches/0537-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch new file mode 100644 index 000000000..7f15a7705 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0537-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 4 Aug 2020 22:24:15 +0200 +Subject: [PATCH] Optimize Pathfinder - Remove Streams / Optimized collections + +I utilized the IDE to convert streams to non streams code, so shouldn't +be any risk of behavior change. Only did minor optimization of the +generated code set to remove unnecessary things. + +I expect us to just drop this patch on next major update and re-apply +it with the IDE again and re-apply the collections optimization. + +Optimize collection by creating a list instead of a set of the key and value. + +This lets us get faster foreach iteration, as well as avoids map lookups on +the values when needed. + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +index ba8ee93032aabe7ec4ecf52d452e1a580d6ebc20..2ef0e04af771e14f8d71aef4ccb81d3b81db7df5 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -33,28 +33,31 @@ public class PathFinder { + this.openSet.a(); + this.nodeEvaluator.prepare(world, mob); + Node pathpoint = this.nodeEvaluator.getStart(); +- Map map = (Map) positions.stream().collect(Collectors.toMap((blockposition) -> { +- return this.nodeEvaluator.getGoal((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); +- }, Function.identity())); +- Path pathentity = this.findPath(pathpoint, map, followRange, distance, rangeMultiplier); ++ // Paper start - remove streams - and optimize collection ++ List> map = Lists.newArrayList(); ++ for (BlockPos blockposition : positions) { ++ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getGoal((double) blockposition.getX(), blockposition.getY(), blockposition.getZ()), blockposition)); ++ } ++ // Paper end ++ Path pathentity = this.a(pathpoint, map, followRange, distance, rangeMultiplier); + + this.nodeEvaluator.done(); + return pathentity; + } + + @Nullable +- private Path findPath(Node startNode, Map positions, float followRange, int distance, float rangeMultiplier) { +- Set set = positions.keySet(); ++ private Path a(Node pathpoint, List> list, float f, int i, float f1) { // Paper - optimize collection ++ //Set set = map.keySet(); // Paper + +- startNode.g = 0.0F; +- startNode.h = this.getBestH(startNode, set); +- startNode.f = startNode.h; ++ pathpoint.g = 0.0F; ++ pathpoint.h = this.a(pathpoint, list); // Paper - optimize collection ++ pathpoint.f = pathpoint.h; + this.openSet.a(); +- this.openSet.a(startNode); ++ this.openSet.a(pathpoint); + Set set1 = ImmutableSet.of(); + int j = 0; +- Set set2 = Sets.newHashSetWithExpectedSize(set.size()); +- int k = (int) ((float) this.maxVisitedNodes * rangeMultiplier); ++ List> set2 = Lists.newArrayListWithExpectedSize(list.size()); // Paper - optimize collection ++ int k = (int) ((float) this.maxVisitedNodes * f1); + + while (!this.openSet.e()) { + ++j; +@@ -65,14 +68,15 @@ public class PathFinder { + Node pathpoint1 = this.openSet.c(); + + pathpoint1.closed = true; +- Iterator iterator = set.iterator(); +- +- while (iterator.hasNext()) { +- Target pathdestination = (Target) iterator.next(); ++ // Paper start - optimize collection ++ for (int i1 = 0; i1 < list.size(); i1++) { ++ Map.Entry entry = list.get(i1); ++ Target pathdestination = entry.getKey(); + +- if (pathpoint1.distanceManhattan((Node) pathdestination) <= (float) distance) { ++ if (pathpoint1.distanceManhattan((Node) pathdestination) <= (float) i) { + pathdestination.setReached(); +- set2.add(pathdestination); ++ set2.add(entry); ++ // Paper end + } + } + +@@ -80,7 +84,7 @@ public class PathFinder { + break; + } + +- if (pathpoint1.distanceTo(startNode) < followRange) { ++ if (pathpoint1.distanceTo(pathpoint) < f) { + int l = this.nodeEvaluator.getNeighbors(this.neighbors, pathpoint1); + + for (int i1 = 0; i1 < l; ++i1) { +@@ -90,10 +94,10 @@ public class PathFinder { + pathpoint2.walkedDistance = pathpoint1.walkedDistance + f2; + float f3 = pathpoint1.g + f2 + pathpoint2.costMalus; + +- if (pathpoint2.walkedDistance < followRange && (!pathpoint2.inOpenSet() || f3 < pathpoint2.g)) { ++ if (pathpoint2.walkedDistance < f && (!pathpoint2.inOpenSet() || f3 < pathpoint2.g)) { + pathpoint2.cameFrom = pathpoint1; + pathpoint2.g = f3; +- pathpoint2.h = this.getBestH(pathpoint2, set) * 1.5F; ++ pathpoint2.h = this.a(pathpoint2, list) * 1.5F; // Paper - list instead of set + if (pathpoint2.inOpenSet()) { + this.openSet.a(pathpoint2, pathpoint2.g + pathpoint2.h); + } else { +@@ -105,31 +109,32 @@ public class PathFinder { + } + } + +- Optional optional = !set2.isEmpty() ? set2.stream().map((pathdestination1) -> { +- return this.reconstructPath(pathdestination1.getBestNode(), (BlockPos) positions.get(pathdestination1), true); +- }).min(Comparator.comparingInt(Path::getNodeCount)) : set.stream().map((pathdestination1) -> { +- return this.reconstructPath(pathdestination1.getBestNode(), (BlockPos) positions.get(pathdestination1), false); +- }).min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount)); +- +- if (!optional.isPresent()) { +- return null; +- } else { +- Path pathentity = (Path) optional.get(); +- +- return pathentity; ++ // Paper start - remove streams - and optimize collection ++ Path best = null; ++ boolean useSet1 = set2.isEmpty(); ++ Comparator comparator = useSet1 ? Comparator.comparingInt(Path::getNodeCount) ++ : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount); ++ for (Map.Entry entry : useSet1 ? list : set2) { ++ Path pathEntity = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !useSet1); ++ if (best == null || comparator.compare(pathEntity, best) < 0) ++ best = pathEntity; + } ++ return best; ++ // Paper end + } + +- private float getBestH(Node node, Set targets) { ++ private float a(Node pathpoint, List> list) { // Paper - optimize collection + float f = Float.MAX_VALUE; + + float f1; + +- for (Iterator iterator = targets.iterator(); iterator.hasNext(); f = Math.min(f1, f)) { +- Target pathdestination = (Target) iterator.next(); ++ // Paper start - optimize collection ++ for (int i = 0, listSize = list.size(); i < listSize; f = Math.min(f1, f), i++) { // Paper ++ Target pathdestination = list.get(i).getKey(); // Paper ++ // Paper end + +- f1 = node.distanceTo(pathdestination); +- pathdestination.updateBest(f1, node); ++ f1 = pathpoint.distanceTo(pathdestination); ++ pathdestination.updateBest(f1, pathpoint); + } + + return f; diff --git a/Remapped-Spigot-Server-Patches/0538-Incremental-player-saving.patch b/Remapped-Spigot-Server-Patches/0538-Incremental-player-saving.patch new file mode 100644 index 000000000..cd7713a51 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0538-Incremental-player-saving.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 9 Aug 2020 08:59:25 +0300 +Subject: [PATCH] Incremental player saving + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index b67ba8f75e4a3358d7c2462918b85b0bf9b5a922..fdbd8b89bb8bf3b61f60b812b90483c98a3d5ccb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -440,4 +440,15 @@ public class PaperConfig { + allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false)); + set("settings.unsupported-settings.allow-tnt-duplication", null); + } ++ ++ public static int playerAutoSaveRate = -1; ++ public static int maxPlayerAutoSavePerTick = 10; ++ private static void playerAutoSaveRate() { ++ playerAutoSaveRate = getInt("settings.player-auto-save-rate", -1); ++ maxPlayerAutoSavePerTick = getInt("settings.max-player-auto-save-per-tick", -1); ++ if (maxPlayerAutoSavePerTick == -1) { // -1 Automatic / "Recommended" ++ // 10 should be safe for everyone unless you mass spamming player auto save ++ maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 19544b794b5a46c129016172798ff7294fcfed33..735c3c983e96e4e6f36de0975909fc48cb042081 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1347,9 +1347,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down + //MinecraftServer.LOGGER.debug("Autosave started"); // Paper + serverAutoSave = (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0); // Paper ++ // Paper start ++ int playerSaveInterval = com.destroystokyo.paper.PaperConfig.playerAutoSaveRate; ++ if (playerSaveInterval < 0) { ++ playerSaveInterval = autosavePeriod; ++ } ++ // Paper end + this.profiler.push("save"); +- if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // Paper +- this.playerList.saveAll(); ++ if (playerSaveInterval > 0) { // Paper ++ this.playerList.savePlayers(playerSaveInterval); // Paper + }// Paper + // Paper start + for (ServerLevel world : getAllLevels()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c5717f45a0110492aad41f21cc06fb8cbeb1f791..bd4d4ace35e966e819aa461d3962fe06ff402be7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -173,6 +173,7 @@ import org.bukkit.inventory.MainHand; + public class ServerPlayer extends Player implements ContainerListener { + + private static final Logger LOGGER = LogManager.getLogger(); ++ public long lastSave = MinecraftServer.currentTick; // Paper + public ServerGamePacketListenerImpl connection; + public Connection networkManager; // Paper + public final MinecraftServer server; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 882cd25c9610f0b995c27291aa62846922ab531f..b76735531ef96f9d4c870a5107feea01524a7670 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -559,6 +559,7 @@ public abstract class PlayerList { + protected void save(ServerPlayer player) { + if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit + if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug) ++ player.lastSave = MinecraftServer.currentTick; // Paper + this.playerIo.save(player); + ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + +@@ -1218,10 +1219,21 @@ public abstract class PlayerList { + } + + public void saveAll() { ++ // Paper start - incremental player saving ++ savePlayers(null); ++ } ++ public void savePlayers(Integer interval) { + MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper ++ int numSaved = 0; ++ long now = MinecraftServer.currentTick; + for (int i = 0; i < this.players.size(); ++i) { +- this.save((ServerPlayer) this.players.get(i)); ++ ServerPlayer entityplayer = this.players.get(i); ++ if (interval == null || now - entityplayer.lastSave >= interval) { ++ this.save(entityplayer); ++ if (interval != null && ++numSaved <= com.destroystokyo.paper.PaperConfig.maxPlayerAutoSavePerTick) { break; } ++ } ++ // Paper end + } + MinecraftTimings.savePlayers.stopTiming(); // Paper + return null; }); // Paper - ensure main diff --git a/Remapped-Spigot-Server-Patches/0539-Import-fastutil-classes.patch b/Remapped-Spigot-Server-Patches/0539-Import-fastutil-classes.patch new file mode 100644 index 000000000..706c8236e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0539-Import-fastutil-classes.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 12 Aug 2020 11:33:04 +0200 +Subject: [PATCH] Import fastutil classes + + +diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +index 95e166aa63f42c675df645a56e313bdffc2e8663..05f7d4a3835536f26f741d54a0884bd43fc82967 100644 +--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +@@ -16,6 +16,7 @@ import net.minecraft.CrashReport; + import net.minecraft.ReportedException; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.world.entity.Entity; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; // Paper + import org.apache.commons.lang3.ObjectUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -25,7 +26,7 @@ public class SynchedEntityData { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Map, Integer> ENTITY_ID_POOL = Maps.newHashMap(); + private final Entity entity; +- private final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap> entries = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL ++ private final Int2ObjectOpenHashMap> entries = new Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL + // private final ReadWriteLock lock = new ReentrantReadWriteLock(); // Spigot - not required + private boolean isEmpty = true; + private boolean isDirty; diff --git a/Remapped-Spigot-Server-Patches/0540-Don-t-mark-null-chunk-sections-for-block-updates.patch b/Remapped-Spigot-Server-Patches/0540-Don-t-mark-null-chunk-sections-for-block-updates.patch new file mode 100644 index 000000000..3a19fd93b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0540-Don-t-mark-null-chunk-sections-for-block-updates.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 14 Aug 2020 23:41:19 +0200 +Subject: [PATCH] Don't mark null chunk sections for block updates + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 9e3629884709126574a52ad44fe7523f01dbcce9..82205ad13ef0e987bd83979d06331545efe0a60a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -449,6 +449,7 @@ public class ChunkHolder { + this.broadcastBlockEntityIfNeeded(world, blockposition, iblockdata); + } else { + LevelChunkSection chunksection = chunk.getSections()[sectionposition.getY()]; ++ if (chunksection == null) chunksection = new LevelChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found + ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection, this.resendLight); + + this.broadcast(packetplayoutmultiblockchange, false); diff --git a/Remapped-Spigot-Server-Patches/0541-Remove-armour-stand-double-add-to-world.patch b/Remapped-Spigot-Server-Patches/0541-Remove-armour-stand-double-add-to-world.patch new file mode 100644 index 000000000..8e11c0592 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0541-Remove-armour-stand-double-add-to-world.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 14 Aug 2020 23:59:26 +0200 +Subject: [PATCH] Remove armour stand double add to world + + +diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java +index a2dfcaac8a2a4a69e703de43be76d4fe369fd647..bed063497bb593683ea384605ae1a71a68f4fc1b 100644 +--- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java ++++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java +@@ -53,7 +53,7 @@ public class ArmorStandItem extends Item { + return InteractionResult.FAIL; + } + +- worldserver.addFreshEntityWithPassengers(entityarmorstand); ++ // Paper - moved down + float f = (float) Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F; + + entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F); +@@ -63,7 +63,7 @@ public class ArmorStandItem extends Item { + return InteractionResult.FAIL; + } + // CraftBukkit end +- world.addFreshEntity(entityarmorstand); ++ worldserver.addFreshEntityWithPassengers(entityarmorstand); // Paper - moved down + world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); + } + diff --git a/Remapped-Spigot-Server-Patches/0542-Fix-MC-187716-Use-configured-height.patch b/Remapped-Spigot-Server-Patches/0542-Fix-MC-187716-Use-configured-height.patch new file mode 100644 index 000000000..1e2cd52d5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0542-Fix-MC-187716-Use-configured-height.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Aug 2020 08:04:49 -0500 +Subject: [PATCH] Fix MC-187716 Use configured height + + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java +index 3f297ef7bde4159c77681574966446a0eba03f25..ff17c76f341028dd6d17f4c1f13f442c2e404532 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java +@@ -44,7 +44,7 @@ public abstract class NetherCappedSurfaceBuilder extends SurfaceBuilder= 0; --k2) { ++ for (int k2 = height; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.set(k1, k2, l1); + BlockState iblockdata5 = chunk.getBlockState(blockposition_mutableblockposition); + int l2; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java +index 4d6c03048022442dea467e1d9d018f150adc62c7..f64671b7359fb71e8af578d48d0a3c211e315057 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java +@@ -34,7 +34,7 @@ public class NetherForestSurfaceBuilder extends SurfaceBuilder= 0; --k2) { ++ for (int k2 = height; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.set(k1, k2, l1); + BlockState iblockdata3 = surfaceBlocks.getTopMaterial(); + BlockState iblockdata4 = chunk.getBlockState(blockposition_mutableblockposition); +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java +index c1e0fd7813786bf1cc03b08b204007711575f144..b222890e7bdff2c1470841677a99d4423f9c9d7f 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java +@@ -36,7 +36,7 @@ public class NetherSurfaceBuilder extends SurfaceBuilder= 0; --k2) { ++ for (int k2 = height; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.set(k1, k2, l1); + BlockState iblockdata4 = chunk.getBlockState(blockposition_mutableblockposition); + diff --git a/Remapped-Spigot-Server-Patches/0543-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/Remapped-Spigot-Server-Patches/0543-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch new file mode 100644 index 000000000..c7a761474 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0543-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mbax +Date: Mon, 17 Aug 2020 12:17:37 -0400 +Subject: [PATCH] Fix regex mistake in CB NBT int deserialization + +The existing regex is too open and allows for the absence of any actual +number data, detecting an NBT entry of just the letter "i" in upper or +lower case. This causes a single-character NBT entry to be processed as +an integer ending in "i", passing an empty String to to Integer.parseInt, +triggering an exception in loading the item. + +This commit forces numbers to be present prior to the ending "i" +letter. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java +index 94d46bc56b3bc4c4750fcfb1732eea0e49a04195..8ec09ff3b5aae4267b753bd715f0a9d4ef0381bd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java +@@ -19,7 +19,7 @@ import net.minecraft.nbt.TagParser; + public class CraftNBTTagConfigSerializer { + + private static final Pattern ARRAY = Pattern.compile("^\\[.*]"); +- private static final Pattern INTEGER = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)?i", Pattern.CASE_INSENSITIVE); ++ private static final Pattern INTEGER = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)i", Pattern.CASE_INSENSITIVE); // Paper - fix regex + private static final Pattern DOUBLE = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?d", Pattern.CASE_INSENSITIVE); + private static final TagParser MOJANGSON_PARSER = new TagParser(new StringReader("")); + diff --git a/Remapped-Spigot-Server-Patches/0544-Do-not-let-the-server-load-chunks-from-newer-version.patch b/Remapped-Spigot-Server-Patches/0544-Do-not-let-the-server-load-chunks-from-newer-version.patch new file mode 100644 index 000000000..d47ebc8c6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0544-Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 23 Jul 2019 20:44:47 -0500 +Subject: [PATCH] Do not let the server load chunks from newer versions + +If the server attempts to load a chunk generated by a newer version of +the game, immediately stop the server to prevent data corruption. + +You can override this functionality at your own peril. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 0adf14af9841cd3a20a8b2c0c320eb06794ef261..f6a814f9305813eaafa56baa0327e0111cd4e38c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -95,10 +95,24 @@ public class ChunkSerializer { + return holder.protoChunk; + } + ++ // Paper start ++ private static final int CURRENT_DATA_VERSION = SharedConstants.getCurrentVersion().getWorldVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end ++ + public static InProgressChunkHolder loadChunk(ServerLevel worldserver, StructureManager definedstructuremanager, PoiManager villageplace, ChunkPos chunkcoordintpair, CompoundTag nbttagcompound, boolean distinguish) { + ArrayDeque tasksToExecuteOnMain = new ArrayDeque<>(); + // Paper end + ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator(); ++ // Paper start - Do NOT attempt to load chunks saved with newer versions ++ if (nbttagcompound.contains("DataVersion", 99)) { ++ int dataVersion = nbttagcompound.getInt("DataVersion"); ++ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { ++ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); ++ System.exit(1); ++ } ++ } ++ // Paper end + BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource(); + CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Level"); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate + ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate diff --git a/Remapped-Spigot-Server-Patches/0545-Brand-support.patch b/Remapped-Spigot-Server-Patches/0545-Brand-support.patch new file mode 100644 index 000000000..e134b2718 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0545-Brand-support.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DigitalRegent +Date: Sat, 11 Apr 2020 13:10:58 +0200 +Subject: [PATCH] Brand support + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index d264fca2737f83a0860394f7bb6b269ffe669594..ab6494f5a872bba5398bef0367b4d9257786f61e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -5,6 +5,7 @@ import com.google.common.primitives.Doubles; + import com.google.common.primitives.Floats; + import com.mojang.brigadier.ParseResults; + import com.mojang.brigadier.StringReader; ++import io.netty.buffer.Unpooled; + import io.netty.util.concurrent.Future; + import io.netty.util.concurrent.GenericFutureListener; + import it.unimi.dsi.fastutil.ints.Int2ShortMap; +@@ -37,6 +38,7 @@ import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.StringTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.Connection; ++import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.MutableComponent; +@@ -258,6 +260,8 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + ++ private String clientBrandName = null; // Paper - Brand name ++ + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { + this.server = server; + this.connection = connection; +@@ -2998,6 +3002,8 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private static final ResourceLocation CUSTOM_REGISTER = new ResourceLocation("register"); + private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister"); + ++ private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support ++ + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +@@ -3025,6 +3031,16 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + try { + byte[] data = new byte[packet.data.readableBytes()]; + packet.data.readBytes(data); ++ ++ // Paper start - Brand support ++ if (packet.identifier.equals(MINECRAFT_BRAND)) { ++ try { ++ this.clientBrandName = new FriendlyByteBuf(Unpooled.copiedBuffer(data)).readUTF(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end + craftServer.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packet.identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +@@ -3034,6 +3050,12 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + } + ++ // Paper start - brand support ++ public String getClientBrandName() { ++ return clientBrandName; ++ } ++ // Paper end ++ + public final boolean isDisconnected() { + return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 610eabd2e93f9efccee810c3b5a314bc3cc649d8..7aae63d22167dc1b3ec7e8bc8672855c2038007e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2385,6 +2385,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + }; + ++ // Paper start - brand support ++ @Override ++ public String getClientBrandName() { ++ return getHandle().connection != null ? getHandle().connection.getClientBrandName() : null; ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return spigot; diff --git a/Remapped-Spigot-Server-Patches/0546-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch b/Remapped-Spigot-Server-Patches/0546-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch new file mode 100644 index 000000000..d3257fe8b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0546-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 20 Aug 2020 19:24:13 -0700 +Subject: [PATCH] Fix MC-99259 Wither Boss Bar doesn't update until + invulnerability period is over + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index edd231568b75330d0cffbecb03a7e9dbc55d5f94..1f330d852eb9b3a36570542e10a88ae065798714 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -391,8 +391,9 @@ public class WitherBoss extends Monster implements RangedAttackMob { + this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + } + +- this.bossEvent.setPercent(this.getHealth() / this.getMaxHealth()); ++ //this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); // Paper - Moved down + } ++ this.bossEvent.setPercent(this.getHealth() / this.getMaxHealth()); // Paper - Fix MC-99259 (Boss bar does not update until Wither invulnerability period ends) + } + + public static boolean canDestroy(BlockState block) { diff --git a/Remapped-Spigot-Server-Patches/0547-Fix-MC-197271.patch b/Remapped-Spigot-Server-Patches/0547-Fix-MC-197271.patch new file mode 100644 index 000000000..14ad3b04d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0547-Fix-MC-197271.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sun, 23 Aug 2020 10:57:44 +0200 +Subject: [PATCH] Fix MC-197271 + +This patch only fixes an issue for servers running OpenJ9. + +diff --git a/src/main/java/net/minecraft/data/BuiltinRegistries.java b/src/main/java/net/minecraft/data/BuiltinRegistries.java +index d64cebb4431664762a14670c7d9d782dd7894ed5..0c403ea85f7ea20f2f978e06313f8675abf204b6 100644 +--- a/src/main/java/net/minecraft/data/BuiltinRegistries.java ++++ b/src/main/java/net/minecraft/data/BuiltinRegistries.java +@@ -48,11 +48,11 @@ public class BuiltinRegistries { + public static final Registry PROCESSOR_LIST = registerSimple(Registry.PROCESSOR_LIST_REGISTRY, () -> { + return ProcessorLists.b; + }); +- public static final Registry TEMPLATE_POOL = registerSimple(Registry.TEMPLATE_POOL_REGISTRY, Pools::bootstrap); ++ public static final Registry TEMPLATE_POOL = registerSimple(Registry.TEMPLATE_POOL_REGISTRY, () -> Pools.bootstrap()); // Paper - MC-197271 + public static final Registry BIOME = registerSimple(Registry.BIOME_REGISTRY, () -> { + return Biomes.PLAINS; + }); +- public static final Registry NOISE_GENERATOR_SETTINGS = registerSimple(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY, NoiseGeneratorSettings::bootstrap); ++ public static final Registry NOISE_GENERATOR_SETTINGS = registerSimple(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY, () -> NoiseGeneratorSettings.bootstrap()); // Paper - MC-197271 + + private static Registry registerSimple(ResourceKey> registryRef, Supplier defaultValueSupplier) { + return registerSimple(registryRef, Lifecycle.stable(), defaultValueSupplier); +@@ -66,9 +66,9 @@ public class BuiltinRegistries { + ResourceLocation minecraftkey = registryRef.location(); + + BuiltinRegistries.LOADERS.put(minecraftkey, defaultValueSupplier); +- WritableRegistry iregistrywritable = BuiltinRegistries.WRITABLE_REGISTRY; ++ WritableRegistry iregistrywritable = (WritableRegistry) BuiltinRegistries.WRITABLE_REGISTRY; // Paper - decompile fix + +- return (WritableRegistry) iregistrywritable.register(registryRef, (Object) registry, lifecycle); ++ return (R) iregistrywritable.register((ResourceKey) registryRef, registry, lifecycle); // Paper - decompile fix + } + + public static T register(Registry registry, String id, T object) { +@@ -76,11 +76,11 @@ public class BuiltinRegistries { + } + + public static T register(Registry registry, ResourceLocation id, T object) { +- return ((WritableRegistry) registry).register(ResourceKey.create(registry.key(), id), object, Lifecycle.stable()); ++ return (T) ((WritableRegistry) registry).register(ResourceKey.create(registry.key(), id), object, Lifecycle.stable()); // Paper - decompile fix + } + + public static T registerMapping(Registry iregistry, int rawId, ResourceKey resourcekey, T object) { +- return ((WritableRegistry) iregistry).registerMapping(rawId, resourcekey, object, Lifecycle.stable()); ++ return (T) ((WritableRegistry) iregistry).registerMapping(rawId, resourcekey, object, Lifecycle.stable()); // Paper - decompile fix + } + + public static void bootstrap() {} diff --git a/Remapped-Spigot-Server-Patches/0548-MC-197883-Bandaid-decode-issue.patch b/Remapped-Spigot-Server-Patches/0548-MC-197883-Bandaid-decode-issue.patch new file mode 100644 index 000000000..fd35ea34e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0548-MC-197883-Bandaid-decode-issue.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 21 Aug 2020 21:05:28 -0400 +Subject: [PATCH] MC-197883: Bandaid decode issue + +Mojang has a mix of type and name in the data sets, but you can only +use one. + +This will retry as name if type is asked for and not found. + +diff --git a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +index de7d1e5e0319c65775d932144c268c2d55bb7dc7..bd6a0e1b5454e880a4f2a16be7dc8da64b73e11d 100644 +--- a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java ++++ b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +@@ -48,7 +48,12 @@ public class KeyDispatchCodec extends MapCodec { + + @Override + public DataResult decode(final DynamicOps ops, final MapLike input) { +- final T elementName = input.get(typeKey); ++ // Paper start - bandaid MC-197883 ++ T elementName = input.get(typeKey); ++ if (elementName == null && "type".equals(typeKey)) { ++ elementName = input.get("name"); ++ } ++ // Paper end + if (elementName == null) { + return DataResult.error("Input does not contain a key [" + typeKey + "]: " + input); + } diff --git a/Remapped-Spigot-Server-Patches/0549-Add-setMaxPlayers-API.patch b/Remapped-Spigot-Server-Patches/0549-Add-setMaxPlayers-API.patch new file mode 100644 index 000000000..bfd99ce60 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0549-Add-setMaxPlayers-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 22 Aug 2020 23:59:30 +0200 +Subject: [PATCH] Add #setMaxPlayers API + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index b76735531ef96f9d4c870a5107feea01524a7670..7e44c911f4abc5c7d0e89513bf2cfc3516f13492 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -141,7 +141,7 @@ public abstract class PlayerList { + public final PlayerDataStorage playerIo; + private boolean doWhiteList; + private final RegistryAccess.RegistryHolder registryHolder; +- protected final int maxPlayers; ++ protected int maxPlayers; public final void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } // Paper - remove final and add setter + private int viewDistance; + private GameType overrideGameMode; + private boolean allowCheatsForAllPlayers; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e301aee53b19fc3f93a36d0ed03a649741123bfa..e599be15af17e5e45d2b694c30140cc4a787a7f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -613,6 +613,13 @@ public final class CraftServer implements Server { + return playerList.getMaxPlayers(); + } + ++ // Paper start ++ @Override ++ public void setMaxPlayers(int maxPlayers) { ++ this.playerList.setMaxPlayers(maxPlayers); ++ } ++ // Paper end ++ + // NOTE: These are dependent on the corresponding call in MinecraftServer + // so if that changes this will need to as well + @Override diff --git a/Remapped-Spigot-Server-Patches/0550-Add-playPickupItemAnimation-to-LivingEntity.patch b/Remapped-Spigot-Server-Patches/0550-Add-playPickupItemAnimation-to-LivingEntity.patch new file mode 100644 index 000000000..e3089f12c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0550-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 19:36:22 +0200 +Subject: [PATCH] Add playPickupItemAnimation to LivingEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 067eaf1e05ced344eb168431403f3fe786eafddf..eb136af0f99f5d7520ceabb98cefd5a01122872c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -806,5 +806,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + ((Mob) getHandle()).getJumpControl().jump(); + } + } ++ ++ @Override ++ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { ++ getHandle().take(((CraftItem) item).getHandle(), quantity); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0551-Don-t-require-FACING-data.patch b/Remapped-Spigot-Server-Patches/0551-Don-t-require-FACING-data.patch new file mode 100644 index 000000000..5ecdef506 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0551-Don-t-require-FACING-data.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 23 Aug 2020 19:01:04 +0200 +Subject: [PATCH] Don't require FACING data + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index f7e60cdfa0b3f5970a897b5d52aaa72210f2fa57..ab8e69f9fc38012844ce01bd0cc5be8de2fcf4ab 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -14,20 +14,22 @@ import org.bukkit.event.block.BlockDispenseEvent; + // CraftBukkit end + + public class DefaultDispenseItemBehavior implements DispenseItemBehavior { ++ private Direction enumdirection; // Paper + + public DefaultDispenseItemBehavior() {} + + @Override + public final ItemStack dispense(BlockSource pointer, ItemStack stack) { ++ enumdirection = pointer.getBlockState().getValue(DispenserBlock.FACING); // Paper - cache facing direction + ItemStack itemstack1 = this.execute(pointer, stack); + + this.playSound(pointer); +- this.playAnimation(pointer, (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING)); ++ this.playAnimation(pointer, enumdirection); // Paper - cache facing direction + return itemstack1; + } + + protected ItemStack execute(BlockSource pointer, ItemStack stack) { +- Direction enumdirection = (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING); ++ // Paper - cached enum direction + Position iposition = DispenserBlock.getDispensePosition(pointer); + ItemStack itemstack1 = stack.split(1); + diff --git a/Remapped-Spigot-Server-Patches/0552-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/Remapped-Spigot-Server-Patches/0552-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch new file mode 100644 index 000000000..7947c7314 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0552-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Aug 2020 23:36:21 +0200 +Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 61aee2c109614a014149ae5a15ad2a28c796cb9d..22266fda4de9b5fbace3b8e55ce390b8d7e75a65 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1981,12 +1981,14 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + // Paper end + ++ public final void setSpawn(BlockPos blockposition, float f) { this.setDefaultSpawnPos(blockposition, f); } // Paper - OBFHELPER + public void setDefaultSpawnPos(BlockPos pos, float angle) { + // Paper - configurable spawn radius + BlockPos prevSpawn = this.getSpawn(); + //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.levelData.setSpawn(pos, angle); ++ new org.bukkit.event.world.SpawnChangeEvent(getWorld(), MCUtil.toLocation(this, prevSpawn)).callEvent(); // Paper + if (this.keepSpawnInMemory) { + // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add + this.removeTicketsForSpawn(this.paperConfig.keepLoadedRange, prevSpawn); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 793b1309528671ce822d5a484ff9e40d6eba4e9d..37513a1774f5a6611338c1b90018b974238ddbf6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -371,11 +371,13 @@ public class CraftWorld implements World { + public boolean setSpawnLocation(int x, int y, int z, float angle) { + try { + Location previousLocation = getSpawnLocation(); +- world.levelData.setSpawn(new BlockPos(x, y, z), angle); ++ world.setSpawn(new BlockPos(x, y, z), angle); // Paper - use WorldServer#setSpawn + ++ // Paper start - move to nms.World + // Notify anyone who's listening. +- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); +- server.getPluginManager().callEvent(event); ++ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); ++ // server.getPluginManager().callEvent(event); ++ // Paper end + + return true; + } catch (Exception e) { diff --git a/Remapped-Spigot-Server-Patches/0553-Add-moon-phase-API.patch b/Remapped-Spigot-Server-Patches/0553-Add-moon-phase-API.patch new file mode 100644 index 000000000..bd3ee0c1e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0553-Add-moon-phase-API.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 16:32:11 +0200 +Subject: [PATCH] Add moon phase API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 37513a1774f5a6611338c1b90018b974238ddbf6..28bf53bc9fca21f57cd4851adf508d833ecdd33b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -310,6 +310,11 @@ public class CraftWorld implements World { + public int getPlayerCount() { + return world.players.size(); + } ++ ++ @Override ++ public io.papermc.paper.world.MoonPhase getMoonPhase() { ++ return io.papermc.paper.world.MoonPhase.getPhase(getFullTime() / 24000L); ++ } + // Paper end + + private static final Random rand = new Random(); diff --git a/Remapped-Spigot-Server-Patches/0554-Prevent-headless-pistons-from-being-created.patch b/Remapped-Spigot-Server-Patches/0554-Prevent-headless-pistons-from-being-created.patch new file mode 100644 index 000000000..15edeb8af --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0554-Prevent-headless-pistons-from-being-created.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: commandblockguy +Date: Fri, 14 Aug 2020 14:44:14 -0500 +Subject: [PATCH] Prevent headless pistons from being created + +Prevent headless pistons from being created by explosions or tree/mushroom growth. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index fdbd8b89bb8bf3b61f60b812b90483c98a3d5ccb..faa1b775e45563b93ac1d5b904938b1f5ad8d80c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -441,6 +441,12 @@ public class PaperConfig { + set("settings.unsupported-settings.allow-tnt-duplication", null); + } + ++ public static boolean allowHeadlessPistons; ++ private static void allowHeadlessPistons() { ++ config.set("settings.unsupported-settings.allow-headless-pistons-readme", "This setting controls if players should be able to create headless pistons."); ++ allowHeadlessPistons = getBoolean("settings.unsupported-settings.allow-headless-pistons", false); ++ } ++ + public static int playerAutoSaveRate = -1; + public static int maxPlayerAutoSavePerTick = 10; + private static void playerAutoSaveRate() { +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 0f0a5fa2be5a7c69291b593a04cad83e069ba5b1..f7ca5294fe571770e3b0036e92563c5a099f76b1 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -15,6 +15,7 @@ import java.util.Random; + import java.util.Set; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; + import net.minecraft.core.Vec3i; + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.server.level.ServerLevel; +@@ -35,6 +36,8 @@ import net.minecraft.world.level.block.BaseFireBlock; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.piston.PistonHeadBlock; ++import net.minecraft.world.level.block.piston.PistonMovingBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.storage.loot.LootContext; +@@ -161,6 +164,15 @@ public class Explosion { + + if (f > 0.0F && this.damageCalculator.a(this, this.level, blockposition, iblockdata, f) && blockposition.getY() < 256 && blockposition.getY() >= 0) { // CraftBukkit - don't wrap explosions + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!com.destroystokyo.paper.PaperConfig.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ BlockEntity extension = this.level.getBlockEntity(blockposition); ++ if (extension instanceof PistonMovingBlockEntity && ((PistonMovingBlockEntity) extension).isHead()) { ++ Direction direction = iblockdata.getValue(PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); ++ } ++ } ++ // Paper end + } + + d4 += d0 * 0.30000001192092896D; +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 001e90da8b09e16b6df4849a5bac4f4821000c94..81f3c6fb7bab9de364537d3f3b2ea0f32795e5f1 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -65,6 +65,8 @@ public class PistonMovingBlockEntity extends BlockEntity implements TickableBloc + return this.direction; + } + ++ public final boolean isHead() { return this.isSourcePiston(); } // Paper - OBFHELPER ++ + public boolean isSourcePiston() { + return this.isSourcePiston; + } diff --git a/Remapped-Spigot-Server-Patches/0555-Add-BellRingEvent.patch b/Remapped-Spigot-Server-Patches/0555-Add-BellRingEvent.patch new file mode 100644 index 000000000..05dfb6c0b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0555-Add-BellRingEvent.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Eearslya Sleiarion +Date: Sun, 23 Aug 2020 13:04:02 +0200 +Subject: [PATCH] Add BellRingEvent + +Add a new event, BellRingEvent, to trigger whenever a player rings a +village bell. Passes along the bell block and the player who rang it. + +diff --git a/src/main/java/net/minecraft/world/level/block/BellBlock.java b/src/main/java/net/minecraft/world/level/block/BellBlock.java +index affae471e50354bfa9594e188e6dcea183b9b5c9..dc5dc9e533c71908b7a9a3cc9e614bd4a0dcde98 100644 +--- a/src/main/java/net/minecraft/world/level/block/BellBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BellBlock.java +@@ -1,8 +1,11 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.BellRingEvent; ++ + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.stats.Stats; +@@ -89,7 +92,7 @@ public class BellBlock extends BaseEntityBlock { + boolean flag1 = !flag || this.isProperHit(state, enumdirection, movingobjectpositionblock.getLocation().y - (double) blockposition.getY()); + + if (flag1) { +- boolean flag2 = this.attemptToRing(world, blockposition, enumdirection); ++ boolean flag2 = this.handleBellRing(world, blockposition, enumdirection, entityhuman); // Paper + + if (flag2 && entityhuman != null) { + entityhuman.awardStat(Stats.BELL_RING); +@@ -123,15 +126,21 @@ public class BellBlock extends BaseEntityBlock { + } + + public boolean attemptToRing(Level world, BlockPos pos, @Nullable Direction enumdirection) { +- BlockEntity tileentity = world.getBlockEntity(pos); ++ // Paper start - add ringer param ++ return this.handleBellRing(world, pos, enumdirection, null); ++ } ++ public boolean handleBellRing(Level world, BlockPos blockposition, @Nullable Direction enumdirection, @Nullable Entity ringer) { ++ // Paper end ++ BlockEntity tileentity = world.getBlockEntity(blockposition); + + if (!world.isClientSide && tileentity instanceof BellBlockEntity) { + if (enumdirection == null) { +- enumdirection = (Direction) world.getBlockState(pos).getValue(BellBlock.FACING); ++ enumdirection = (Direction) world.getBlockState(blockposition).getValue(BellBlock.FACING); + } + ++ if (!new BellRingEvent(world.getWorld().getBlockAt(MCUtil.toLocation(world, blockposition)), ringer == null ? null : ringer.getBukkitEntity()).callEvent()) return false; // Paper - BellRingEvent + ((BellBlockEntity) tileentity).onHit(enumdirection); +- world.playSound((Player) null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F); ++ world.playSound((Player) null, blockposition, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F); + return true; + } else { + return false; diff --git a/Remapped-Spigot-Server-Patches/0556-Add-zombie-targets-turtle-egg-config.patch b/Remapped-Spigot-Server-Patches/0556-Add-zombie-targets-turtle-egg-config.patch new file mode 100644 index 000000000..d46a54fd3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0556-Add-zombie-targets-turtle-egg-config.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 15:47:34 +0200 +Subject: [PATCH] Add zombie targets turtle egg config + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 978062774c1db286bfb9b0ffdef19d880b1f249b..36ecdfce84141ac731b827e469ac842f5c666259 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -668,4 +668,9 @@ public class PaperWorldConfig { + maxLightningFlashDistance = 512; // Vanilla value + } + } ++ ++ public boolean zombiesTargetTurtleEggs = true; ++ private void zombiesTargetTurtleEggs() { ++ zombiesTargetTurtleEggs = getBoolean("zombies-target-turtle-eggs", zombiesTargetTurtleEggs); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 1e7c2c603b967c8c606efd94ce95a17c856f78d7..4105c1763d25824aac35d305a793823c1604eee8 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -104,7 +104,7 @@ public class Zombie extends Monster { + + @Override + protected void registerGoals() { +- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); ++ if (level.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.addBehaviourGoals(); diff --git a/Remapped-Spigot-Server-Patches/0557-Buffer-joins-to-world.patch b/Remapped-Spigot-Server-Patches/0557-Buffer-joins-to-world.patch new file mode 100644 index 000000000..b9f930aa4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0557-Buffer-joins-to-world.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 19 Aug 2020 05:05:54 +0100 +Subject: [PATCH] Buffer joins to world + +This patch buffers the number of logins which will attempt to join +the world per tick, this attempts to reduce the impact that join floods +has on the server + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index faa1b775e45563b93ac1d5b904938b1f5ad8d80c..545948f20efd6c8dd42140b565af94cd6b52b661 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -457,4 +457,9 @@ public class PaperConfig { + maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20; + } + } ++ ++ public static int maxJoinsPerTick; ++ private static void maxJoinsPerTick() { ++ maxJoinsPerTick = getInt("settings.max-joins-per-tick", 3); ++ } + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index fc63df21aecd4721efdb45d4744666ed0b562c1b..6f7cbce5a049d87d4a0ed7cc4517cb4e8694efb5 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -32,6 +32,7 @@ import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; + import net.minecraft.network.protocol.game.ClientboundKeepAlivePacket; + import net.minecraft.network.protocol.game.ClientboundSetTitlesPacket; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.RunningOnDifferentThreadException; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.ServerGamePacketListenerImpl; +@@ -382,10 +383,22 @@ public class Connection extends SimpleChannelInboundHandler> { + } + // Paper end + ++ private static final int MAX_PER_TICK = com.destroystokyo.paper.PaperConfig.maxJoinsPerTick; // Paper ++ private static int joinAttemptsThisTick; // Paper ++ private static int currTick; // Paper + public void tick() { + this.p(); ++ // Paper start ++ if (currTick != MinecraftServer.currentTick) { ++ currTick = MinecraftServer.currentTick; ++ joinAttemptsThisTick = 0; ++ } ++ // Paper end + if (this.packetListener instanceof ServerLoginPacketListenerImpl) { ++ if ( ((ServerLoginPacketListenerImpl) this.packetListener).getLoginState() != ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT // Paper ++ || (joinAttemptsThisTick++ < MAX_PER_TICK)) { // Paper - limit the number of joins which can be processed each tick + ((ServerLoginPacketListenerImpl) this.packetListener).tick(); ++ } // Paper + } + + if (this.packetListener instanceof ServerGamePacketListenerImpl) { +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index e229c7735ba88be3d8721440104958408a2a075e..659bf14cf3c949b896d0333f893a3d5e16ab9c92 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -420,7 +420,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + return new GameProfile(uuid, profile.getName()); + } + +- static enum State { ++ public enum State { // Paper - package private -> public + + HELLO, KEY, AUTHENTICATING, NEGOTIATING, READY_TO_ACCEPT, DELAY_ACCEPT, ACCEPTED; + diff --git a/Remapped-Spigot-Server-Patches/0558-Optimize-redstone-algorithm.patch b/Remapped-Spigot-Server-Patches/0558-Optimize-redstone-algorithm.patch new file mode 100644 index 000000000..002eed1ee --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0558-Optimize-redstone-algorithm.patch @@ -0,0 +1,1157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: theosib +Date: Thu, 27 Sep 2018 01:43:35 -0600 +Subject: [PATCH] Optimize redstone algorithm + +Author: theosib +Co-authored-by: egg82 + +Original license: MIT + +This patch implements theosib's redstone algorithms to completely overhaul the way redstone works. +The new algorithms should be many times faster than current vanilla ones. +From the original author's comments, it looks like it shouldn't interfere with any redstone save for very extreme edge-cases. + +Surprisingly, not a lot was touched aside from a few obfuscation helpers and BlockRedstoneWire. +A lot of this code is self-contained in a helper class. + +Aside from making the obvious class/function renames and obfhelpers I didn't need to modify much. +Just added Bukkit's event system and took a few liberties with dead code and comment misspellings. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 36ecdfce84141ac731b827e469ac842f5c666259..02bb85364560784adea47c877c13291c3d016b86 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -673,4 +673,14 @@ public class PaperWorldConfig { + private void zombiesTargetTurtleEggs() { + zombiesTargetTurtleEggs = getBoolean("zombies-target-turtle-eggs", zombiesTargetTurtleEggs); + } ++ ++ public boolean useEigencraftRedstone = false; ++ private void useEigencraftRedstone() { ++ useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); ++ if (useEigencraftRedstone) { ++ log("Using Eigencraft redstone algorithm by theosib."); ++ } else { ++ log("Using vanilla redstone algorithm."); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..19604f4d2d0cdf65cb9f164258c4435a5a3450bc +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +@@ -0,0 +1,913 @@ ++package com.destroystokyo.paper.util; ++ ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ThreadLocalRandom; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.RedStoneWireBlock; ++import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.event.block.BlockRedstoneEvent; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++ ++/** ++ * Used for the faster redstone algorithm. ++ * Original author: theosib ++ * Original license: MIT ++ * ++ * Ported to Paper and updated to 1.13 by egg82 ++ */ ++public class RedstoneWireTurbo { ++ /* ++ * This is Helper class for BlockRedstoneWire. It implements a minimally-invasive ++ * bolt-on accelerator that performs a breadth-first search through redstone wire blocks ++ * in order to more efficiently and deterministically compute new redstone wire power levels ++ * and determine the order in which other blocks should be updated. ++ * ++ * Features: ++ * - Changes to BlockRedstoneWire are very limited, no other classes are affected, and the ++ * choice between old and new redstone wire update algorithms is switchable on-line. ++ * - The vanilla implementation relied on World.notifyNeighborsOfStateChange for redstone ++ * wire blocks to communicate power level changes to each other, generating 36 block ++ * updates per call. This improved implementation propagates power level changes directly ++ * between redstone wire blocks. Redstone wire power levels are therefore computed more quickly, ++ * and block updates are sent only to non-redstone blocks, many of which may perform an ++ * action when informed of a change in redstone power level. (Note: Block updates are not ++ * the same as state changes to redstone wire. Wire block states are updated as soon ++ * as they are computed.) ++ * - Of the 36 block updates generated by a call to World.notifyNeighborsOfStateChange, ++ * 12 of them are obviously redundant (e.g. the west neighbor of the east neighbor). ++ * These are eliminated. ++ * - Updates to redstone wire and other connected blocks are propagated in a breath-first ++ * manner, radiating out from the initial trigger (a block update to a redstone wire ++ * from something other than redstone wire). ++ * - Updates are scheduled both deterministically and in an intuitive order, addressing bug ++ * MC-11193. ++ * - All redstone behavior that used to be locational now works the same in all locations. ++ * - All behaviors of redstone wire that used to be orientational now work the same in all ++ * orientations, as long as orientation can be determined; random otherwise. Some other ++ * redstone components still update directionally (e.g. switches), and this code can't ++ * compensate for that. ++ * - Information that is otherwise computed over and over again or which is expensive to ++ * to compute is cached for faster lookup. This includes coordinates of block position ++ * neighbors and block states that won't change behind our backs during the execution of ++ * this search algorithm. ++ * - Redundant block updates (both to redstone wire and to other blocks) are heavily ++ * consolidated. For worst-case scenarios (depowering of redstone wire) this results ++ * in a reduction of block updates by as much as 95% (factor of 1/21). Due to overheads, ++ * empirical testing shows a speedup better than 10x. This addresses bug MC-81098. ++ * ++ * Extensive testing has been performed to ensure that existing redstone contraptions still ++ * behave as expected. Results of early testing that identified undesirable behavior changes ++ * were addressed. Additionally, real-time performance testing revealed compute inefficiencies ++ * With earlier implementations of this accelerator. Some compatibility adjustments and ++ * performance optimizations resulted in harmless increases in block updates above the ++ * theoretical minimum. ++ * ++ * Only a single redstone machine was found to break: An instant dropper line hack that ++ * relies on powered rails and quasi-connectivity but doesn't work in all directions. The ++ * replacement is to lay redstone wire directly on top of the dropper line, which now works ++ * reliably in any direction. ++ * ++ * There are numerous other optimization that can be made, but those will be provided later in ++ * separate updates. This version is designed to be minimalistic. ++ * ++ * Many thanks to the following individuals for their help in testing this functionality: ++ * - pokechu22, _MethodZz_, WARBEN, NarcolepticFrog, CommandHelper (nessie), ilmango, ++ * OreoLamp, Xcom6000, tryashtar, RedCMD, Smokey95Dog, EDDxample, Rays Works, ++ * Nodnam, BlockyPlays, Grumm, NeunEinser, HelVince. ++ */ ++ ++ /* Reference to BlockRedstoneWire object, which uses this accelerator */ ++ private final RedStoneWireBlock wire; ++ ++ /* ++ * Implementation: ++ * ++ * RedstoneWire Blocks are updated in concentric rings or "layers" radiating out from the ++ * initial block update that came from a call to BlockRedstoneWire.neighborChanged(). ++ * All nodes put in Layer N are those with Manhattan distance N from the trigger ++ * position, reachable through connected redstone wire blocks. ++ * ++ * Layer 0 represents the trigger block position that was input to neighborChanged. ++ * Layer 1 contains the immediate neighbors of that position. ++ * Layer N contains the neighbors of blocks in layer N-1, not including ++ * those in previous layers. ++ * ++ * Layers enforce an update order that is a function of Manhattan distance ++ * from the initial coordinates input to neighborChanged. The same ++ * coordinates may appear in multiple layers, but redundant updates are minimized. ++ * Block updates are sent layer-by-layer. If multiple of a block's neighbors experience ++ * redstone wire changes before its layer is processed, then those updates will be merged. ++ * If a block's update has been sent, but its neighboring redstone changes ++ * after that, then another update will be sent. This preserves compatibility with ++ * machines that rely on zero-tick behavior, except that the new functionality is non- ++ * locational. ++ * ++ * Within each layer, updates are ordered left-to-right relative to the direction of ++ * information flow. This makes the implementation non-orientational. Only when ++ * this direction is ambiguous is randomness applied (intentionally). ++ */ ++ private List updateQueue0 = Lists.newArrayList(); ++ private List updateQueue1 = Lists.newArrayList(); ++ private List updateQueue2 = Lists.newArrayList(); ++ ++ public RedstoneWireTurbo(RedStoneWireBlock wire) { ++ this.wire = wire; ++ } ++ ++ /* ++ * Compute neighbors of a block. When a redstone wire value changes, previously it called ++ * World.notifyNeighborsOfStateChange. That lists immediately neighboring blocks in ++ * west, east, down, up, north, south order. For each of those neighbors, their own ++ * neighbors are updated in the same order. This generates 36 updates, but 12 of them are ++ * redundant; for instance the west neighbor of a block's east neighbor. ++ * ++ * Note that this ordering is only used to create the initial list of neighbors. Once ++ * the direction of signal flow is identified, the ordering of updates is completely ++ * reorganized. ++ */ ++ public static BlockPos[] computeAllNeighbors(final BlockPos pos) { ++ final int x = pos.getX(); ++ final int y = pos.getY(); ++ final int z = pos.getZ(); ++ final BlockPos[] n = new BlockPos[24]; ++ ++ // Immediate neighbors, in the same order as ++ // World.notifyNeighborsOfStateChange, etc.: ++ // west, east, down, up, north, south ++ n[0] = new BlockPos(x - 1, y, z); ++ n[1] = new BlockPos(x + 1, y, z); ++ n[2] = new BlockPos(x, y - 1, z); ++ n[3] = new BlockPos(x, y + 1, z); ++ n[4] = new BlockPos(x, y, z - 1); ++ n[5] = new BlockPos(x, y, z + 1); ++ ++ // Neighbors of neighbors, in the same order, ++ // except that duplicates are not included ++ n[6] = new BlockPos(x - 2, y, z); ++ n[7] = new BlockPos(x - 1, y - 1, z); ++ n[8] = new BlockPos(x - 1, y + 1, z); ++ n[9] = new BlockPos(x - 1, y, z - 1); ++ n[10] = new BlockPos(x - 1, y, z + 1); ++ n[11] = new BlockPos(x + 2, y, z); ++ n[12] = new BlockPos(x + 1, y - 1, z); ++ n[13] = new BlockPos(x + 1, y + 1, z); ++ n[14] = new BlockPos(x + 1, y, z - 1); ++ n[15] = new BlockPos(x + 1, y, z + 1); ++ n[16] = new BlockPos(x, y - 2, z); ++ n[17] = new BlockPos(x, y - 1, z - 1); ++ n[18] = new BlockPos(x, y - 1, z + 1); ++ n[19] = new BlockPos(x, y + 2, z); ++ n[20] = new BlockPos(x, y + 1, z - 1); ++ n[21] = new BlockPos(x, y + 1, z + 1); ++ n[22] = new BlockPos(x, y, z - 2); ++ n[23] = new BlockPos(x, y, z + 2); ++ return n; ++ } ++ ++ /* ++ * We only want redstone wires to update redstone wires that are ++ * immediately adjacent. Some more distant updates can result ++ * in cross-talk that (a) wastes time and (b) can make the update ++ * order unintuitive. Therefore (relative to the neighbor order ++ * computed by computeAllNeighbors), updates are not scheduled ++ * for redstone wire in those non-connecting positions. On the ++ * other hand, updates will always be sent to *other* types of blocks ++ * in any of the 24 neighboring positions. ++ */ ++ private static final boolean[] update_redstone = { ++ true, true, false, false, true, true, // 0 to 5 ++ false, true, true, false, false, false, // 6 to 11 ++ true, true, false, false, false, true, // 12 to 17 ++ true, false, true, true, false, false // 18 to 23 ++ }; ++ ++ // Internal numbering for cardinal directions ++ private static final int North = 0; ++ private static final int East = 1; ++ private static final int South = 2; ++ private static final int West = 3; ++ ++ /* ++ * These lookup tables completely remap neighbor positions into a left-to-right ++ * ordering, based on the cardinal direction that is determined to be forward. ++ * See below for more explanation. ++ */ ++ private static final int[] forward_is_north = {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}; ++ private static final int[] forward_is_east = {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}; ++ private static final int[] forward_is_south = {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}; ++ private static final int[] forward_is_west = {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}; ++ ++ /* For any orientation, we end up with the update order defined below. This order is relative to any redstone wire block ++ * that is itself having an update computed, and this center position is marked with C. ++ * - The update position marked 0 is computed first, and the one marked 23 is last. ++ * - Forward is determined by the local direction of information flow into position C from prior updates. ++ * - The first updates are scheduled for the four positions below and above C. ++ * - Then updates are scheduled for the four horizontal neighbors of C, followed by the positions below and above those neighbors. ++ * - Finally, updates are scheduled for the remaining positions with Manhattan distance 2 from C (at the same Y coordinate). ++ * - For a given horizontal distance from C, updates are scheduled starting from directly left and stepping clockwise to directly ++ * right. The remaining positions behind C are scheduled counterclockwise so as to maintain the left-to-right ordering. ++ * - If C is in layer N of the update schedule, then all 24 positions may be scheduled for layer N+1. For redstone wire, no ++ * updates are scheduled for positions that cannot directly connect. Additionally, the four positions above and below C ++ * are ALSO scheduled for layer N+2. ++ * - This update order was selected after experimenting with a number of alternative schedules, based on its compatibility ++ * with existing redstone designs and behaviors that were considered to be intuitive by various testers. WARBEN in particular ++ * made some of the most challenging test cases, but the 3-tick clocks (made by RedCMD) were also challenging to fix, ++ * along with the rail-based instant dropper line built by ilmango. Numerous others made test cases as well, including ++ * NarcolepticFrog, nessie, and Pokechu22. ++ * ++ * - The forward direction is determined locally. So when there are branches in the redstone wire, the left one will get updated ++ * before the right one. Each branch can have its own relative forward direction, resulting in the left side of a left branch ++ * having priority over the right branch of a left branch, which has priority over the left branch of a right branch, followed ++ * by the right branch of a right branch. And so forth. Since redstone power reduces to zero after a path distance of 15, ++ * that imposes a practical limit on the branching. Note that the branching is not tracked explicitly -- relative forward ++ * directions dictate relative sort order, which maintains the proper global ordering. This also makes it unnecessary to be ++ * concerned about branches meeting up with each other. ++ * ++ * ^ ++ * | ++ * Forward ++ * <-- Left Right --> ++ * ++ * 18 ++ * 10 17 5 19 11 ++ * 2 8 0 12 16 4 C 6 20 9 1 13 3 ++ * 14 21 7 23 15 ++ * Further 22 Further ++ * Down Down Up Up ++ * ++ * Backward ++ * | ++ * V ++ */ ++ ++ // This allows the above remapping tables to be looked up by cardial direction index ++ private static final int[][] reordering = { forward_is_north, forward_is_east, forward_is_south, forward_is_west }; ++ ++ /* ++ * Input: Array of UpdateNode objects in an order corresponding to the positions ++ * computed by computeAllNeighbors above. ++ * Output: Array of UpdateNode objects oriented using the above remapping tables ++ * corresponding to the identified heading (direction of information flow). ++ */ ++ private static void orientNeighbors(final UpdateNode[] src, final UpdateNode[] dst, final int heading) { ++ final int[] re = reordering[heading]; ++ for (int i = 0; i < 24; i++) { ++ dst[i] = src[re[i]]; ++ } ++ } ++ ++ /* ++ * Structure to keep track of redstone wire blocks and ++ * neighbors that will receive updates. ++ */ ++ private static class UpdateNode { ++ public static enum Type { ++ UNKNOWN, REDSTONE, OTHER ++ } ++ ++ BlockState currentState; // Keep track of redstone wire value ++ UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) ++ BlockPos self; // UpdateNode's own position ++ BlockPos parent; // Which block pos spawned/updated this node ++ Type type = Type.UNKNOWN; // unknown, redstone wire, other type of block ++ int layer; // Highest layer this node is scheduled in ++ boolean visited; // To keep track of information flow direction, visited restone wire is marked ++ int xbias, zbias; // Remembers directionality of ancestor nodes; helps eliminate directional ambiguities. ++ } ++ ++ /* ++ * Keep track of all block positions discovered during search and their current states. ++ * We want to remember one entry for each position. ++ */ ++ private final Map nodeCache = Maps.newHashMap(); ++ ++ /* ++ * For a newly created UpdateNode object, determine what type of block it is. ++ */ ++ private void identifyNode(final Level worldIn, final UpdateNode upd1) { ++ final BlockPos pos = upd1.self; ++ final BlockState oldState = worldIn.getBlockState(pos); ++ upd1.currentState = oldState; ++ ++ // Some neighbors of redstone wire are other kinds of blocks. ++ // These need to receive block updates to inform them that ++ // redstone wire values have changed. ++ final Block block = oldState.getBlock(); ++ if (block != wire) { ++ // Mark this block as not redstone wire and therefore ++ // requiring updates ++ upd1.type = UpdateNode.Type.OTHER; ++ ++ // Non-redstone blocks may propagate updates, but those updates ++ // are not handled by this accelerator. Therefore, we do not ++ // expand this position's neighbors. ++ return; ++ } ++ ++ // One job of BlockRedstoneWire.neighborChanged is to convert ++ // redstone wires to items if the block beneath was removed. ++ // With this accelerator, BlockRedstoneWire.neighborChanged ++ // is only typically called for a single wire block, while ++ // others are processed internally by the breadth first search ++ // algorithm. To preserve this game behavior, this check must ++ // be replicated here. ++ if (!wire.canSurvive(null, worldIn, pos)) { ++ // Pop off the redstone dust ++ Block.popResource(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO ++ worldIn.setAir(pos); ++ ++ // Mark this position as not being redstone wire ++ upd1.type = UpdateNode.Type.OTHER; ++ ++ // Note: Sending updates to air blocks leads to an empty method. ++ // Testing shows this to be faster than explicitly avoiding updates to ++ // air blocks. ++ return; ++ } ++ ++ // If the above conditions fail, then this is a redstone wire block. ++ upd1.type = UpdateNode.Type.REDSTONE; ++ } ++ ++ /* ++ * Given which redstone wire blocks have been visited and not visited ++ * around the position currently being updated, compute the cardinal ++ * direction that is "forward." ++ * ++ * rx is the forward direction along the West/East axis ++ * rz is the forward direction along the North/South axis ++ */ ++ static private int computeHeading(final int rx, final int rz) { ++ // rx and rz can only take on values -1, 0, and 1, so we can ++ // compute a code number that allows us to use a single switch ++ // to determine the heading. ++ final int code = (rx + 1) + 3 * (rz + 1); ++ switch (code) { ++ case 0: { ++ // Both rx and rz are -1 (northwest) ++ // Randomly choose one to be forward. ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? North : West; ++ } ++ case 1: { ++ // rx=0, rz=-1 ++ // Definitively North ++ return North; ++ } ++ case 2: { ++ // rx=1, rz=-1 (northeast) ++ // Choose randomly between north and east ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? North : East; ++ } ++ case 3: { ++ // rx=-1, rz=0 ++ // Definitively West ++ return West; ++ } ++ case 4: { ++ // rx=0, rz=0 ++ // Heading is completely ambiguous. Choose ++ // randomly among the four cardinal directions. ++ return ThreadLocalRandom.current().nextInt(0, 4); ++ } ++ case 5: { ++ // rx=1, rz=0 ++ // Definitively East ++ return East; ++ } ++ case 6: { ++ // rx=-1, rz=1 (southwest) ++ // Choose randomly between south and west ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? South : West; ++ } ++ case 7: { ++ // rx=0, rz=1 ++ // Definitively South ++ return South; ++ } ++ case 8: { ++ // rx=1, rz=1 (southeast) ++ // Choose randomly between south and east ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? South : East; ++ } ++ } ++ ++ // We should never get here ++ return ThreadLocalRandom.current().nextInt(0, 4); ++ } ++ ++ // Select whether to use updateSurroundingRedstone from BlockRedstoneWire (old) ++ // or this helper class (new) ++ private static final boolean old_current_change = false; ++ ++ /* ++ * Process a node whose neighboring redstone wire has experienced value changes. ++ */ ++ private void updateNode(final Level worldIn, final UpdateNode upd1, final int layer) { ++ final BlockPos pos = upd1.self; ++ ++ // Mark this redstone wire as having been visited so that it can be used ++ // to calculate direction of information flow. ++ upd1.visited = true; ++ ++ // Look up the last known state. ++ // Due to the way other redstone components are updated, we do not ++ // have to worry about a state changing behind our backs. The rare ++ // exception is handled by scheduleReentrantNeighborChanged. ++ final BlockState oldState = upd1.currentState; ++ ++ // Ask the wire block to compute its power level from its neighbors. ++ // This will also update the wire's power level and return a new ++ // state if it has changed. When a wire power level is changed, ++ // calculateCurrentChanges will immediately update the block state in the world ++ // and return the same value here to be cached in the corresponding ++ // UpdateNode object. ++ BlockState newState; ++ if (old_current_change) { ++ newState = wire.calculateCurrentChanges(worldIn, pos, pos, oldState); ++ } else { ++ // Looking up block state is slow. This accelerator includes a version of ++ // calculateCurrentChanges that uses cahed wire values for a ++ // significant performance boost. ++ newState = this.calculateCurrentChanges(worldIn, upd1); ++ } ++ ++ // Only inform neighbors if the state has changed ++ if (newState != oldState) { ++ // Store the new state ++ upd1.currentState = newState; ++ ++ // Inform neighbors of the change ++ propagateChanges(worldIn, upd1, layer); ++ } ++ } ++ ++ /* ++ * This identifies the neighboring positions of a new UpdateNode object, ++ * determines their types, and links those to into the graph. Then based on ++ * what nodes in the redstone wire graph have been visited, the neighbors ++ * are reordered left-to-right relative to the direction of information flow. ++ */ ++ private void findNeighbors(final Level worldIn, final UpdateNode upd1) { ++ final BlockPos pos = upd1.self; ++ ++ // Get the list of neighbor coordinates ++ final BlockPos[] neighbors = computeAllNeighbors(pos); ++ ++ // Temporary array of neighbors in cardinal ordering ++ final UpdateNode[] neighbor_nodes = new UpdateNode[24]; ++ ++ // Target array of neighbors sorted left-to-right ++ upd1.neighbor_nodes = new UpdateNode[24]; ++ ++ for (int i=0; i<24; i++) { ++ // Look up each neighbor in the node cache ++ final BlockPos pos2 = neighbors[i]; ++ UpdateNode upd2 = nodeCache.get(pos2); ++ if (upd2 == null) { ++ // If this is a previously unreached position, create ++ // a new update node, add it to the cache, and identify what it is. ++ upd2 = new UpdateNode(); ++ upd2.self = pos2; ++ upd2.parent = pos; ++ nodeCache.put(pos2, upd2); ++ identifyNode(worldIn, upd2); ++ } ++ ++ // For non-redstone blocks, any of the 24 neighboring positions ++ // should receive a block update. However, some block coordinates ++ // may contain a redstone wire that does not directly connect to the ++ // one being expanded. To avoid redundant calculations and confusing ++ // cross-talk, those neighboring positions are not included. ++ if (update_redstone[i] || upd2.type != UpdateNode.Type.REDSTONE) { ++ neighbor_nodes[i] = upd2; ++ } ++ } ++ ++ // Determine the directions from which the redstone signal may have come from. This ++ // checks for redstone wire at the same Y level and also Y+1 and Y-1, relative to the ++ // block being expanded. ++ final boolean fromWest = (neighbor_nodes[0].visited || neighbor_nodes[7].visited || neighbor_nodes[8].visited); ++ final boolean fromEast = (neighbor_nodes[1].visited || neighbor_nodes[12].visited || neighbor_nodes[13].visited); ++ final boolean fromNorth = (neighbor_nodes[4].visited || neighbor_nodes[17].visited || neighbor_nodes[20].visited); ++ final boolean fromSouth = (neighbor_nodes[5].visited || neighbor_nodes[18].visited || neighbor_nodes[21].visited); ++ ++ int cx = 0, cz = 0; ++ if (fromWest) cx += 1; ++ if (fromEast) cx -= 1; ++ if (fromNorth) cz += 1; ++ if (fromSouth) cz -= 1; ++ ++ int heading; ++ if (cx==0 && cz==0) { ++ // If there is no clear direction, try to inherit the heading from ancestor nodes. ++ heading = computeHeading(upd1.xbias, upd1.zbias); ++ ++ // Propagate that heading to descendant nodes. ++ for (int i=0; i<24; i++) { ++ final UpdateNode nn = neighbor_nodes[i]; ++ if (nn != null) { ++ nn.xbias = upd1.xbias; ++ nn.zbias = upd1.zbias; ++ } ++ } ++ } else { ++ if (cx != 0 && cz != 0) { ++ // If the heading is somewhat ambiguous, try to disambiguate based on ++ // ancestor nodes. ++ if (upd1.xbias != 0) cz = 0; ++ if (upd1.zbias != 0) cx = 0; ++ } ++ heading = computeHeading(cx, cz); ++ ++ // Propagate that heading to descendant nodes. ++ for (int i=0; i<24; i++) { ++ final UpdateNode nn = neighbor_nodes[i]; ++ if (nn != null) { ++ nn.xbias = cx; ++ nn.zbias = cz; ++ } ++ } ++ } ++ ++ // Reorder neighboring UpdateNode objects according to the forward direction ++ // determined above. ++ orientNeighbors(neighbor_nodes, upd1.neighbor_nodes, heading); ++ } ++ ++ /* ++ * For any redstone wire block in layer N, inform neighbors to recompute their states ++ * in layers N+1 and N+2; ++ */ ++ private void propagateChanges(final Level worldIn, final UpdateNode upd1, final int layer) { ++ if (upd1.neighbor_nodes == null) { ++ // If this node has not been expanded yet, find its neighbors ++ findNeighbors(worldIn, upd1); ++ } ++ ++ final BlockPos pos = upd1.self; ++ ++ // All neighbors may be scheduled for layer N+1 ++ final int layer1 = layer + 1; ++ ++ // If the node being updated (upd1) has already been expanded, then merely ++ // schedule updates to its neighbors. ++ for (int i = 0; i < 24; i++) { ++ final UpdateNode upd2 = upd1.neighbor_nodes[i]; ++ ++ // This test ensures that an UpdateNode is never scheduled to the same layer ++ // more than once. Also, skip non-connecting redstone wire blocks ++ if (upd2 != null && layer1 > upd2.layer) { ++ upd2.layer = layer1; ++ updateQueue1.add(upd2); ++ ++ // Keep track of which block updated this neighbor ++ upd2.parent = pos; ++ } ++ } ++ ++ // Nodes above and below are scheduled ALSO for layer N+2 ++ final int layer2 = layer + 2; ++ ++ // Repeat of the loop above, but only for the first four (above and below) neighbors ++ // and for layer N+2; ++ for (int i = 0; i < 4; i++) { ++ final UpdateNode upd2 = upd1.neighbor_nodes[i]; ++ if (upd2 != null && layer2 > upd2.layer) { ++ upd2.layer = layer2; ++ updateQueue2.add(upd2); ++ upd2.parent = pos; ++ } ++ } ++ } ++ ++ // The breadth-first search below will send block updates to blocks ++ // that are not redstone wire. If one of those updates results in ++ // a distant redstone wire getting an update, then this.neighborChanged ++ // will get called. This would be a reentrant call, and ++ // it is necessary to properly integrate those updates into the ++ // on-going search through redstone wire. Thus, we make the layer ++ // currently being processed visible at the object level. ++ ++ // The current layer being processed by the breadth-first search ++ private int currentWalkLayer = 0; ++ ++ private void shiftQueue() { ++ final List t = updateQueue0; ++ t.clear(); ++ updateQueue0 = updateQueue1; ++ updateQueue1 = updateQueue2; ++ updateQueue2 = t; ++ } ++ ++ /* ++ * Perform a breadth-first (layer by layer) traversal through redstone ++ * wire blocks, propagating value changes to neighbors in an order ++ * that is a function of distance from the initial call to ++ * this.neighborChanged. ++ */ ++ private void breadthFirstWalk(final Level worldIn) { ++ shiftQueue(); ++ currentWalkLayer = 1; ++ ++ // Loop over all layers ++ while (updateQueue0.size()>0 || updateQueue1.size()>0) { ++ // Get the set of blocks in this layer ++ final List thisLayer = updateQueue0; ++ ++ // Loop over all blocks in the layer. Recall that ++ // this is a List, preserving the insertion order of ++ // left-to-right based on direction of information flow. ++ for (UpdateNode upd : thisLayer) { ++ if (upd.type == UpdateNode.Type.REDSTONE) { ++ // If the node is is redstone wire, ++ // schedule updates to neighbors if its value ++ // has changed. ++ updateNode(worldIn, upd, currentWalkLayer); ++ } else { ++ // If this block is not redstone wire, send a block update. ++ // Redstone wire blocks get state updates, but they don't ++ // need block updates. Only non-redstone neighbors need updates. ++ ++ // World.neighborChanged is called from ++ // World.notifyNeighborsOfStateChange, and ++ // notifyNeighborsOfStateExcept. We don't use ++ // World.notifyNeighborsOfStateChange here, since we are ++ // already keeping track of all of the neighbor positions ++ // that need to be updated. All on its own, handling neighbors ++ // this way reduces block updates by 1/3 (24 instead of 36). ++ worldIn.neighborChanged(upd.self, wire, upd.parent); ++ } ++ } ++ ++ // Move on to the next layer ++ shiftQueue(); ++ currentWalkLayer++; ++ } ++ ++ currentWalkLayer = 0; ++ } ++ ++ /* ++ * Normally, when Minecraft is computing redstone wire power changes, and a wire power level ++ * change sends a block update to a neighboring functional component (e.g. piston, repeater, etc.), ++ * those updates are queued. Only once all redstone wire updates are complete will any component ++ * action generate any further block updates to redstone wire. Instant repeater lines, for instance, ++ * will process all wire updates for one redstone line, after which the pistons will zero-tick, ++ * after which the next redstone line performs all of its updates. Thus, each wire is processed in its ++ * own discrete wave. ++ * ++ * However, there are some corner cases where this pattern breaks, with a proof of concept discovered ++ * by Rays Works, which works the same in vanilla. The scenario is as follows: ++ * (1) A redstone wire is conducting a signal. ++ * (2) Part-way through that wave of updates, a neighbor is updated that causes an update to a completely ++ * separate redstone wire. ++ * (3) This results in a call to BlockRedstoneWire.neighborChanged for that other wire, in the middle of ++ * an already on-going propagation through the first wire. ++ * ++ * The vanilla code, being depth-first, would end up fully processing the second wire before going back ++ * to finish processing the first one. (Although technically, vanilla has no special concept of "being ++ * in the middle" of processing updates to a wire.) For the breadth-first algorithm, we give this ++ * situation special handling, where the updates for the second wire are incorporated into the schedule ++ * for the first wire, and then the callstack is allowed to unwind back to the on-going search loop in ++ * order to continue processing both the first and second wire in the order of distance from the initial ++ * trigger. ++ */ ++ private BlockState scheduleReentrantNeighborChanged(final Level worldIn, final BlockPos pos, final BlockState newState, final BlockPos source) { ++ if (source != null) { ++ // If the cause of the redstone wire update is known, we can use that to help determine ++ // direction of information flow. ++ UpdateNode src = nodeCache.get(source); ++ if (src == null) { ++ src = new UpdateNode(); ++ src.self = source; ++ src.parent = source; ++ src.visited = true; ++ identifyNode(worldIn, src); ++ nodeCache.put(source, src); ++ } ++ } ++ ++ // Find or generate a node for the redstone block position receiving the update ++ UpdateNode upd = nodeCache.get(pos); ++ if (upd == null) { ++ upd = new UpdateNode(); ++ upd.self = pos; ++ upd.parent = pos; ++ upd.visited = true; ++ identifyNode(worldIn, upd); ++ nodeCache.put(pos, upd); ++ } ++ upd.currentState = newState; ++ ++ // Receiving this block update may mean something in the world changed. ++ // Therefore we clear the cached block info about all neighbors of ++ // the position receiving the update and then re-identify what they are. ++ if (upd.neighbor_nodes != null) { ++ for (int i=0; i<24; i++) { ++ final UpdateNode upd2 = upd.neighbor_nodes[i]; ++ if (upd2 == null) continue; ++ upd2.type = UpdateNode.Type.UNKNOWN; ++ upd2.currentState = null; ++ identifyNode(worldIn, upd2); ++ } ++ } ++ ++ // The block at 'pos' is a redstone wire and has been updated already by calling ++ // wire.calculateCurrentChanges, so we don't schedule that. However, we do need ++ // to schedule its neighbors. By passing the current value of 'currentWalkLayer' to ++ // propagateChanges, the neighbors of 'pos' are scheduled for layers currentWalkLayer+1 ++ // and currentWalkLayer+2. ++ propagateChanges(worldIn, upd, currentWalkLayer); ++ ++ // Return here. The call stack will unwind back to the first call to ++ // updateSurroundingRedstone, whereupon the new updates just scheduled will ++ // be propagated. This also facilitates elimination of superfluous and ++ // redundant block updates. ++ return newState; ++ } ++ ++ /* ++ * New version of pre-existing updateSurroundingRedstone, which is called from ++ * wire.updateSurroundingRedstone, which is called from wire.neighborChanged and a ++ * few other methods in BlockRedstoneWire. This sets off the breadth-first ++ * walk through all redstone dust connected to the initial position triggered. ++ */ ++ public BlockState updateSurroundingRedstone(final Level worldIn, final BlockPos pos, final BlockState state, final BlockPos source) { ++ // Check this block's neighbors and see if its power level needs to change ++ // Use the calculateCurrentChanges method in BlockRedstoneWire since we have no ++ // cached block states at this point. ++ final BlockState newState = wire.calculateCurrentChanges(worldIn, pos, pos, state); ++ ++ // If no change, exit ++ if (newState == state) { ++ return state; ++ } ++ ++ // Check to see if this update was received during an on-going breadth first search ++ if (currentWalkLayer > 0 || nodeCache.size() > 0) { ++ // As breadthFirstWalk progresses, it sends block updates to neighbors. Some of those ++ // neighbors may affect the world so as to cause yet another redstone wire block to receive ++ // an update. If that happens, we need to integrate those redstone wire updates into the ++ // already on-going graph walk being performed by breadthFirstWalk. ++ return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); ++ } ++ // If there are no on-going walks through redstone wire, then start a new walk. ++ ++ // If the source of the block update to the redstone wire at 'pos' is known, we can use ++ // that to help determine the direction of information flow. ++ if (source != null) { ++ final UpdateNode src = new UpdateNode(); ++ src.self = source; ++ src.parent = source; ++ src.visited = true; ++ nodeCache.put(source, src); ++ identifyNode(worldIn, src); ++ } ++ ++ // Create a node representing the block at 'pos', and then propagate updates ++ // to its neighbors. As stated above, the call to wire.calculateCurrentChanges ++ // already performs the update to the block at 'pos', so it is not added to the schedule. ++ final UpdateNode upd = new UpdateNode(); ++ upd.self = pos; ++ upd.parent = source!=null ? source : pos; ++ upd.currentState = newState; ++ upd.type = UpdateNode.Type.REDSTONE; ++ upd.visited = true; ++ nodeCache.put(pos, upd); ++ propagateChanges(worldIn, upd, 0); ++ ++ // Perform the walk over all directly reachable redstone wire blocks, propagating wire value ++ // updates in a breadth first order out from the initial update received for the block at 'pos'. ++ breadthFirstWalk(worldIn); ++ ++ // With the whole search completed, clear the list of all known blocks. ++ // We do not want to keep around state information that may be changed by other code. ++ // In theory, we could cache the neighbor block positions, but that is a separate ++ // optimization. ++ nodeCache.clear(); ++ ++ return newState; ++ } ++ ++ // For any array of neighbors in an UpdateNode object, these are always ++ // the indices of the four immediate neighbors at the same Y coordinate. ++ private static final int[] rs_neighbors = {4, 5, 6, 7}; ++ private static final int[] rs_neighbors_up = {9, 11, 13, 15}; ++ private static final int[] rs_neighbors_dn = {8, 10, 12, 14}; ++ ++ /* ++ * Updated calculateCurrentChanges that is optimized for speed and uses ++ * the UpdateNode's neighbor array to find the redstone states of neighbors ++ * that might power it. ++ */ ++ private BlockState calculateCurrentChanges(final Level worldIn, final UpdateNode upd) { ++ BlockState state = upd.currentState; ++ final int i = state.getValue(RedStoneWireBlock.POWER).intValue(); ++ int j = 0; ++ j = getMaxCurrentStrength(upd, j); ++ int l = 0; ++ ++ wire.setCanProvidePower(false); ++ // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, ++ // and I'm not ready to try to replicate even more functionality from ++ // elsewhere in Minecraft into this accelerator. So sadly, we must ++ // suffer the performance hit of this very expensive call. If there ++ // is consistency to what this call returns, we may be able to cache it. ++ final int k = worldIn.isBlockIndirectlyGettingPowered(upd.self); ++ wire.setCanProvidePower(true); ++ ++ // The variable 'k' holds the maximum redstone power value of any adjacent blocks. ++ // If 'k' has the highest level of all neighbors, then the power level of this ++ // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the ++ // following loop can affect the power level of the wire. Therefore, the loop is ++ // skipped if k is already 15. ++ if (k < 15) { ++ if (upd.neighbor_nodes == null) { ++ // If this node's neighbors are not known, expand the node ++ findNeighbors(worldIn, upd); ++ } ++ ++ // These remain constant, so pull them out of the loop. ++ // Regardless of which direction is forward, the UpdateNode for the ++ // position directly above the node being calculated is always ++ // at index 1. ++ UpdateNode center_up = upd.neighbor_nodes[1]; ++ boolean center_up_is_cube = center_up.currentState.isRedstoneConductor(worldIn, center_up.self); // TODO ++ ++ for (int m = 0; m < 4; m++) { ++ // Get the neighbor array index of each of the four cardinal ++ // neighbors. ++ int n = rs_neighbors[m]; ++ ++ // Get the max redstone power level of each of the cardinal ++ // neighbors ++ UpdateNode neighbor = upd.neighbor_nodes[n]; ++ l = getMaxCurrentStrength(neighbor, l); ++ ++ // Also check the positions above and below the cardinal ++ // neighbors ++ boolean neighbor_is_cube = neighbor.currentState.isRedstoneConductor(worldIn, neighbor.self); // TODO ++ if (!neighbor_is_cube) { ++ UpdateNode neighbor_down = upd.neighbor_nodes[rs_neighbors_dn[m]]; ++ l = getMaxCurrentStrength(neighbor_down, l); ++ } else ++ if (!center_up_is_cube) { ++ UpdateNode neighbor_up = upd.neighbor_nodes[rs_neighbors_up[m]]; ++ l = getMaxCurrentStrength(neighbor_up, l); ++ } ++ } ++ } ++ ++ // The new code sets this RedstoneWire block's power level to the highest neighbor ++ // minus 1. This usually results in wire power levels dropping by 2 at a time. ++ // This optimization alone has no impact on update order, only the number of updates. ++ j = l - 1; ++ ++ // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will ++ // always be in the range of 0 to 15, the following if will correct that. ++ if (k > j) j = k; ++ ++ // egg82's amendment ++ // Adding Bukkit's BlockRedstoneEvent - er.. event. ++ if (i != j) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(upd.self.getX(), upd.self.getY(), upd.self.getZ()), i, j); ++ worldIn.getCraftServer().getPluginManager().callEvent(event); ++ j = event.getNewCurrent(); ++ } ++ ++ if (i != j) { ++ // If the power level has changed from its previous value, compute a new state ++ // and set it in the world. ++ // Possible optimization: Don't commit state changes to the world until they ++ // need to be known by some nearby non-redstone-wire block. ++ BlockPos pos = new BlockPos(upd.self.getX(), upd.self.getY(), upd.self.getZ()); ++ if (wire.canSurvive(null, worldIn, pos)) { ++ state = state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(j)); ++ worldIn.setBlock(upd.self, state, 2); ++ } ++ } ++ ++ return state; ++ } ++ ++ /* ++ * Optimized function to compute a redstone wire's power level based on cached ++ * state. ++ */ ++ private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) { ++ if (upd.type != UpdateNode.Type.REDSTONE) return strength; ++ final int i = upd.currentState.getValue(RedStoneWireBlock.POWER).intValue(); ++ return i > strength ? i : strength; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9b50b8030174338c04b60d441b980131e1d593e4..ebb92f88e0402681c47834bcf45e6b236748289a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -659,6 +659,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + } + ++ public void neighborChanged(BlockPos pos, Block blockIn, BlockPos fromPos) { neighborChanged(pos, blockIn, fromPos); } // Paper - OBFHELPER + public void neighborChanged(BlockPos sourcePos, Block sourceBlock, BlockPos neighborPos) { + if (!this.isClientSide) { + BlockState iblockdata = this.getBlockState(sourcePos); +@@ -1287,6 +1288,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.getSignal(pos.below(), Direction.DOWN) > 0 ? true : (this.getSignal(pos.above(), Direction.UP) > 0 ? true : (this.getSignal(pos.north(), Direction.NORTH) > 0 ? true : (this.getSignal(pos.south(), Direction.SOUTH) > 0 ? true : (this.getSignal(pos.west(), Direction.WEST) > 0 ? true : this.getSignal(pos.east(), Direction.EAST) > 0)))); + } + ++ public int isBlockIndirectlyGettingPowered(BlockPos pos) { return this.getBestNeighborSignal(pos); } // Paper - OBFHELPER + public int getBestNeighborSignal(BlockPos pos) { + int i = 0; + Direction[] aenumdirection = Level.DIRECTIONS; +diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +index 7318536fe89cddda305007a9ab115970bf18f65d..e5558b73c6159e4c1901d286535a7875924434e9 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.level.block; + ++import com.destroystokyo.paper.PaperConfig; ++import com.destroystokyo.paper.util.RedstoneWireTurbo; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -49,7 +51,7 @@ public class RedStoneWireBlock extends Block { + private final Map SHAPES_CACHE = Maps.newHashMap(); + private static final Vector3f[] COLORS = new Vector3f[16]; + private final BlockState crossState; +- private boolean shouldSignal = true; ++ private boolean shouldSignal = true; public final boolean canProvidePower() { return this.shouldSignal; } public final void setCanProvidePower(boolean value) { this.shouldSignal = value; } // Paper - OBFHELPER + + public RedStoneWireBlock(BlockBehaviour.Properties settings) { + super(settings); +@@ -236,6 +238,121 @@ public class RedStoneWireBlock extends Block { + return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); + } + ++ // Paper start - Optimize redstone ++ // The bulk of the new functionality is found in RedstoneWireTurbo.java ++ RedstoneWireTurbo turbo = new RedstoneWireTurbo(this); ++ ++ /* ++ * Modified version of pre-existing updateSurroundingRedstone, which is called from ++ * this.neighborChanged and a few other methods in this class. ++ * Note: Added 'source' argument so as to help determine direction of information flow ++ */ ++ private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, BlockPos source) { ++ if (worldIn.paperConfig.useEigencraftRedstone) { ++ turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ return; ++ } ++ updatePowerStrength(worldIn, pos, state); ++ } ++ ++ /* ++ * Slightly modified method to compute redstone wire power levels from neighboring blocks. ++ * Modifications cut the number of power level changes by about 45% from vanilla, and this ++ * optimization synergizes well with the breadth-first search implemented in ++ * RedstoneWireTurbo. ++ * Note: RedstoneWireTurbo contains a faster version of this code. ++ * Note: Made this public so that RedstoneWireTurbo can access it. ++ */ ++ public BlockState calculateCurrentChanges(Level worldIn, BlockPos pos1, BlockPos pos2, BlockState state) { ++ BlockState iblockstate = state; ++ int i = state.getValue(POWER); ++ int j = 0; ++ j = this.getPower(j, worldIn.getBlockState(pos2)); ++ this.setCanProvidePower(false); ++ int k = worldIn.isBlockIndirectlyGettingPowered(pos1); ++ this.setCanProvidePower(true); ++ ++ if (!worldIn.paperConfig.useEigencraftRedstone) { ++ // This code is totally redundant to if statements just below the loop. ++ if (k > 0 && k > j - 1) { ++ j = k; ++ } ++ } ++ ++ int l = 0; ++ ++ // The variable 'k' holds the maximum redstone power value of any adjacent blocks. ++ // If 'k' has the highest level of all neighbors, then the power level of this ++ // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the ++ // following loop can affect the power level of the wire. Therefore, the loop is ++ // skipped if k is already 15. ++ if (!worldIn.paperConfig.useEigencraftRedstone || k < 15) { ++ for (Direction enumfacing : Direction.Plane.HORIZONTAL) { ++ BlockPos blockpos = pos1.relative(enumfacing); ++ boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); ++ ++ if (flag) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos)); ++ } ++ ++ if (worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && !worldIn.getBlockState(pos1.above()).isRedstoneConductor(worldIn, pos1)) { ++ if (flag && pos1.getY() >= pos2.getY()) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos.above())); ++ } ++ } else if (!worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos.below())); ++ } ++ } ++ } ++ ++ if (!worldIn.paperConfig.useEigencraftRedstone) { ++ // The old code would decrement the wire value only by 1 at a time. ++ if (l > j) { ++ j = l - 1; ++ } else if (j > 0) { ++ --j; ++ } else { ++ j = 0; ++ } ++ ++ if (k > j - 1) { ++ j = k; ++ } ++ } else { ++ // The new code sets this RedstoneWire block's power level to the highest neighbor ++ // minus 1. This usually results in wire power levels dropping by 2 at a time. ++ // This optimization alone has no impact on update order, only the number of updates. ++ j = l - 1; ++ ++ // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will ++ // always be in the range of 0 to 15, the following if will correct that. ++ if (k > j) j = k; ++ } ++ ++ if (i != j) { ++ state = state.setValue(POWER, j); ++ ++ if (worldIn.getBlockState(pos1) == iblockstate) { ++ worldIn.setBlock(pos1, state, 2); ++ } ++ ++ // 1.16(.1?) dropped the need for blocks needing updates. ++ // Whether this is necessary after all is to be seen. ++// if (!worldIn.paperConfig.useEigencraftRedstone) { ++// // The new search algorithm keeps track of blocks needing updates in its own data structures, ++// // so only add anything to blocksNeedingUpdate if we're using the vanilla update algorithm. ++// this.getBlocksNeedingUpdate().add(pos1); ++// ++// for (EnumDirection enumfacing1 : EnumDirection.values()) { ++// this.getBlocksNeedingUpdate().add(pos1.shift(enumfacing1)); ++// } ++// } ++ } ++ ++ return state; ++ } ++ // Paper end ++ + private void updatePowerStrength(Level world, BlockPos pos, BlockState state) { + int i = this.calculateTargetStrength(world, pos); + +@@ -305,6 +422,8 @@ public class RedStoneWireBlock extends Block { + return Math.max(i, j - 1); + } + ++ private int getPower(int min, BlockState iblockdata) { return Math.max(min, getPower(iblockdata)); } // Paper - Optimize redstone ++ private int getPower(BlockState iblockdata) { return this.getWireSignal(iblockdata); } // Paper - OBFHELPER + private int getWireSignal(BlockState state) { + return state.is((Block) this) ? (Integer) state.getValue(RedStoneWireBlock.POWER) : 0; + } +@@ -327,7 +446,7 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -354,7 +473,7 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -389,7 +508,7 @@ public class RedStoneWireBlock extends Block { + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + if (!world.isClientSide) { + if (state.canSurvive(world, pos)) { +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - Optimize redstone + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); diff --git a/Remapped-Spigot-Server-Patches/0559-Fix-hex-colors-not-working-in-some-kick-messages.patch b/Remapped-Spigot-Server-Patches/0559-Fix-hex-colors-not-working-in-some-kick-messages.patch new file mode 100644 index 000000000..dfda54388 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0559-Fix-hex-colors-not-working-in-some-kick-messages.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 16:57:25 -0400 +Subject: [PATCH] Fix hex colors not working in some kick messages + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 6f98be2b9b00f71dd041e7511c70166fdecf0749..c648b73a4c478f9d8020274205d6684f7c7c416f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -50,7 +50,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + synchronized (throttleTracker) { + if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { + throttleTracker.put(address, currentTime); +- TranslatableComponent chatmessage = new TranslatableComponent(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage); // Paper - Configurable connection throttle kick message ++ Component chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage, true)[0]; // Paper - Configurable connection throttle kick message // Paper - Fix hex colors not working in some kick messages + this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); + this.connection.disconnect(chatmessage); + return; +@@ -76,12 +76,12 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + // CraftBukkit end + if (packet.getProtocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- TranslatableComponent chatmessage; ++ Component chatmessage; // Paper - Fix hex colors not working in some kick messages + + if (packet.getProtocolVersion() < 754) { +- chatmessage = new TranslatableComponent( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- chatmessage = new TranslatableComponent( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } + + this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); +@@ -99,7 +99,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + if (event.callEvent()) { + // If we've failed somehow, let the client know so and go no further. + if (event.isFailed()) { +- chatmessage = new TranslatableComponent(event.getFailMessage()); ++ Component chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(event.getFailMessage(), true)[0]; // Paper - Fix hex colors not working in some kick messages + this.getNetworkManager().send(new ClientboundLoginDisconnectPacket(chatmessage)); + this.getNetworkManager().disconnect(chatmessage); + return; +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 659bf14cf3c949b896d0333f893a3d5e16ab9c92..573963a09f15046cfcaab83aef906801ce70d75a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -104,14 +104,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + // CraftBukkit start + @Deprecated + public void disconnect(String s) { +- try { +- Component ichatbasecomponent = new TextComponent(s); +- ServerLoginPacketListenerImpl.LOGGER.info("Disconnecting {}: {}", this.getUserName(), s); +- this.connection.send(new ClientboundLoginDisconnectPacket(ichatbasecomponent)); +- this.connection.disconnect(ichatbasecomponent); +- } catch (Exception exception) { +- ServerLoginPacketListenerImpl.LOGGER.error("Error whilst disconnecting player", exception); +- } ++ disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s, true)[0]); // Paper - Fix hex colors not working in some kick messages + } + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0560-PortalCreateEvent-needs-to-know-its-entity.patch b/Remapped-Spigot-Server-Patches/0560-PortalCreateEvent-needs-to-know-its-entity.patch new file mode 100644 index 000000000..0287c9ed0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0560-PortalCreateEvent-needs-to-know-its-entity.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 21 Aug 2020 20:57:54 +0200 +Subject: [PATCH] PortalCreateEvent needs to know its entity + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 04b717326524f400da3562655c25db59e72814ec..a9256fc4a0bc3cd277cb372a9c090028e03482f5 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -366,7 +366,7 @@ public final class ItemStack { + net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition); + + if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically +- block.getBlock().onPlace(block, world, newblockposition, oldBlock, true); ++ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext + } + + world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index cd005f7dbfcaf3cebae3a92da36e0d40c93dbf79..ad37261e716b15d62fc2083d137cdac818308cdd 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -7,6 +7,7 @@ import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; +@@ -66,20 +67,23 @@ public abstract class BaseFireBlock extends Block { + super.entityInside(state, world, pos, entity); + } + ++ // Paper start - ItemActionContext param ++ @Override public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { this.onPlace(state, world, pos, oldState, notify, null); } + @Override +- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- if (!oldState.is(state.getBlock())) { ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { ++ // Paper end ++ if (!iblockdata1.is(iblockdata.getBlock())) { + if (inPortalDimension(world)) { +- Optional optional = PortalShape.findEmptyPortalShape((LevelAccessor) world, pos, Direction.Axis.X); ++ Optional optional = PortalShape.findEmptyPortalShape((LevelAccessor) world, blockposition, Direction.Axis.X); + + if (optional.isPresent()) { +- ((PortalShape) optional.get()).createPortal(); ++ ((PortalShape) optional.get()).createPortal(itemActionContext); // Paper - pass ItemActionContext param + return; + } + } + +- if (!state.canSurvive(world, pos)) { +- fireExtinguished(world, pos); // CraftBukkit - fuel block broke ++ if (!iblockdata.canSurvive(world, blockposition)) { ++ fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke + } + + } +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index ac63c5bef5b35b158e57835d765bbdd15fc60664..e690e7c366fc087d3b28d61323dcc78bb7154aed 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -15,6 +15,7 @@ import net.minecraft.core.Vec3i; + import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; +@@ -363,9 +364,11 @@ public class FireBlock extends BaseFireBlock { + } + + @Override +- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- super.onPlace(state, world, pos, oldState, notify); +- world.getBlockTicks().a(pos, this, getFireTickDelay(world.random)); ++ // Paper start - ItemActionContext param ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { ++ super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, itemActionContext); ++ // Paper end ++ world.getBlockTicks().a(blockposition, this, getFireTickDelay(world.random)); + } + + private static int getFireTickDelay(Random random) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index df2836b071158729728411f5b228cc38dddd4d4e..f2fefdad26057c722085e60ba837fe2c117f55f7 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -32,6 +32,7 @@ import net.minecraft.world.item.DyeColor; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.EmptyBlockGetter; + import net.minecraft.world.level.Level; +@@ -119,6 +120,12 @@ public abstract class BlockBehaviour { + DebugPackets.sendNeighborsUpdatePacket(world, pos); + } + ++ // Paper start - add ItemActionContext param ++ @Deprecated ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { ++ this.onPlace(iblockdata, world, blockposition, iblockdata1, flag); ++ } ++ // Paper end + @Deprecated + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +index 7a99adbe39ca2566d42ed67dc9d6f609005f3d6f..500744b6383390266efed9e35a000511210cb5b9 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +@@ -11,6 +11,7 @@ import net.minecraft.tags.BlockTags; + import net.minecraft.tags.Tag; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.EntityDimensions; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.NetherPortalBlock; +@@ -181,7 +182,10 @@ public class PortalShape { + } + + // CraftBukkit start - return boolean +- public boolean createPortal() { ++ // Paper start - ItemActionContext param ++ @Deprecated public boolean createPortal() { return this.createPortal(null); } ++ public boolean createPortal(UseOnContext itemActionContext) { ++ // Paper end + org.bukkit.World bworld = this.level.getLevel().getWorld(); + + // Copy below for loop +@@ -190,8 +194,7 @@ public class PortalShape { + BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> { + blocks.setBlock(blockposition, iblockdata, 18); + }); +- +- PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE); ++ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, itemActionContext == null || itemActionContext.getPlayer() == null ? null : itemActionContext.getPlayer().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param + this.level.getLevel().getServer().server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { diff --git a/Remapped-Spigot-Server-Patches/0561-Fix-CraftTeam-null-check.patch b/Remapped-Spigot-Server-Patches/0561-Fix-CraftTeam-null-check.patch new file mode 100644 index 000000000..d30a89128 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0561-Fix-CraftTeam-null-check.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Sun, 30 Aug 2020 15:30:29 +0800 +Subject: [PATCH] Fix CraftTeam null check + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index 7ebcba4ada42f5599d56cfdeb75dbf62f2a09b78..222e3d4e379fd5ca50c122f70e90ed11b2f5e1f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -253,7 +253,7 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + + @Override + public boolean hasEntry(String entry) throws IllegalArgumentException, IllegalStateException { +- Validate.notNull("Entry cannot be null"); ++ Validate.notNull(entry, "Entry cannot be null"); // Paper + + CraftScoreboard scoreboard = checkState(); + diff --git a/Remapped-Spigot-Server-Patches/0562-Add-more-Evoker-API.patch b/Remapped-Spigot-Server-Patches/0562-Add-more-Evoker-API.patch new file mode 100644 index 000000000..af8a4c551 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0562-Add-more-Evoker-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 15:28:35 +0200 +Subject: [PATCH] Add more Evoker API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java +index c0a5b0074480aad717177c92b28fa27b8a1d707d..617075955506500dc2d9c734398c48b8fc10b69d 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java +@@ -40,7 +40,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + + public class Evoker extends SpellcasterIllager { + +- private Sheep wololoTarget; ++ private Sheep wololoTarget; public final Sheep getWololoTarget() { return this.wololoTarget; } public final void setWololoTarget(Sheep sheep) { this.wololoTarget = sheep; } // Paper - OBFHELPER + + public Evoker(EntityType type, Level world) { + super(type, world); +@@ -59,7 +59,7 @@ public class Evoker extends SpellcasterIllager { + this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); + this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); + this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); +- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).canUse()); ++ this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers(new Class[0])); // Paper - decompile fix + this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); + this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, false)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +index 29fec87c938c4252cf5c9473ce9e5c1908ea9063..950e35f67f88138cc2ce923be1ea7976bd317d1f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import net.minecraft.world.entity.animal.Sheep; + import net.minecraft.world.entity.monster.SpellcasterIllager; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; +@@ -35,4 +36,17 @@ public class CraftEvoker extends CraftSpellcaster implements Evoker { + public void setCurrentSpell(Evoker.Spell spell) { + getHandle().setIsCastingSpell(spell == null ? SpellcasterIllager.IllagerSpell.NONE : SpellcasterIllager.IllagerSpell.byId(spell.ordinal())); + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.entity.Sheep getWololoTarget() { ++ Sheep sheep = getHandle().getWololoTarget(); ++ return sheep == null ? null : (org.bukkit.entity.Sheep) sheep.getBukkitEntity(); ++ } ++ ++ @Override ++ public void setWololoTarget(org.bukkit.entity.Sheep sheep) { ++ getHandle().setWololoTarget(sheep == null ? null : ((org.bukkit.craftbukkit.entity.CraftSheep) sheep).getHandle()); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0563-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch b/Remapped-Spigot-Server-Patches/0563-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch new file mode 100644 index 000000000..163eac24a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0563-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch @@ -0,0 +1,126 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 11 Aug 2020 19:16:09 +0200 +Subject: [PATCH] Add a way to get translation keys for blocks, entities and + materials + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index ae50030df7512c56c552e800b74ef4c69ec6d6d2..d38828485d6deb08036e11d8bf16b3d63a60fbae 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -278,6 +278,7 @@ public class EntityType { + return Registry.ENTITY_TYPE.getKey(type); + } + ++ public static Optional> getByName(String name) { return byString(name); } // Paper - OBFHELPER + public static Optional> byString(String id) { + return Registry.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(id)); + } +@@ -431,6 +432,7 @@ public class EntityType { + return this.category; + } + ++ public String getDescriptionId() { return getDescriptionId(); } // Paper - OBFHELPER + public String getDescriptionId() { + if (this.descriptionId == null) { + this.descriptionId = Util.makeDescriptionId("entity", Registry.ENTITY_TYPE.getKey(this)); +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index 6fce16e89c5492654c891d5754714360a7649bca..58400e84830c93675b0a1fe632be5e217c19a932 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -56,7 +56,7 @@ public class Item implements ItemLike { + private final FoodProperties foodProperties; + + public static int getId(Item item) { +- return item == null ? 0 : Registry.ITEM.getId((Object) item); ++ return item == null ? 0 : Registry.ITEM.getId(item); // Paper - Fix Decompiler Issue + } + + public static Item byId(int id) { +@@ -152,6 +152,7 @@ public class Item implements ItemLike { + return Registry.ITEM.getKey(this).getPath(); + } + ++ public String getOrCreateDescriptionId() { return getOrCreateDescriptionId(); } // Paper - OBFHELPER + protected String getOrCreateDescriptionId() { + if (this.descriptionId == null) { + this.descriptionId = Util.makeDescriptionId("item", Registry.ITEM.getKey(this)); +@@ -164,6 +165,7 @@ public class Item implements ItemLike { + return this.getOrCreateDescriptionId(); + } + ++ public String getDescriptionId(ItemStack itemStack) { return getDescriptionId(itemStack); } // Paper - OBFHELPER + public String getDescriptionId(ItemStack stack) { + return this.getDescriptionId(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 5b84ee4091e354c4b6500f58a31931f2a6827ffc..baa587e73a71d6324bb7817fa4702a7c3a2db726 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -318,6 +318,7 @@ public class Block extends BlockBehaviour implements ItemLike { + return !this.material.isBuildable() && !this.material.isLiquid(); + } + ++ public String getOrCreateDescriptionId() { return getDescriptionId(); } // Paper - OBFHELPER + public String getDescriptionId() { + if (this.descriptionId == null) { + this.descriptionId = Util.makeDescriptionId("block", Registry.BLOCK.getKey(this)); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 05f0833f762436bf8f5f5875c7e3cfed1da11e1c..e09f65f0b06c8fb9a965b921c2c8e68ae2ac1e55 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -761,5 +761,10 @@ public class CraftBlock implements Block { + public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { + return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMSBlock().defaultBlockState().getSoundType()); + } ++ ++ @Override ++ public String getTranslationKey() { ++ return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index a5a5038a84434e69fda8f6b41d2f00b4989e25ae..de5d02a1345f9886200f0540ac08be0df5878708 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -43,6 +43,7 @@ import org.bukkit.Registry; + import org.bukkit.UnsafeValues; + import org.bukkit.advancement.Advancement; + import org.bukkit.block.data.BlockData; ++import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.craftbukkit.legacy.CraftLegacy; +@@ -418,6 +419,30 @@ public final class CraftMagicNumbers implements UnsafeValues { + throw new RuntimeException(); + } + } ++ ++ @Override ++ public String getTranslationKey(Material mat) { ++ if (mat.isBlock()) { ++ return getBlock(mat).getOrCreateDescriptionId(); ++ } ++ return getItem(mat).getDescriptionId(); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.block.Block block) { ++ return ((org.bukkit.craftbukkit.block.CraftBlock)block).getNMS().getBlock().getOrCreateDescriptionId(); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.entity.EntityType type) { ++ return net.minecraft.world.entity.EntityType.getByName(type.getName()).map(net.minecraft.world.entity.EntityType::getDescriptionId).orElse(null); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.inventory.ItemStack itemStack) { ++ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); ++ return nmsItemStack.getItem().getDescriptionId(nmsItemStack); ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0564-Create-HoverEvent-from-ItemStack-Entity.patch b/Remapped-Spigot-Server-Patches/0564-Create-HoverEvent-from-ItemStack-Entity.patch new file mode 100644 index 000000000..a7252fed8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0564-Create-HoverEvent-from-ItemStack-Entity.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Mon, 6 Jul 2020 22:18:04 +0200 +Subject: [PATCH] Create HoverEvent from ItemStack Entity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 746755f76ae177b2eeccf66f8cd95e6ffd5acad9..2e0be9771ca3511f5d9364c57235556b70f07ec6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -365,5 +365,40 @@ public final class CraftItemFactory implements ItemFactory { + + return nms != null ? net.minecraft.locale.Language.getInstance().translateKey(nms.getItem().getDescriptionId()) : null; + } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(ItemStack itemStack) { ++ net.md_5.bungee.api.chat.ItemTag itemTag = net.md_5.bungee.api.chat.ItemTag.ofNbt(CraftItemStack.asNMSCopy(itemStack).getOrCreateTag().toString()); ++ return new net.md_5.bungee.api.chat.hover.content.Item( ++ itemStack.getType().getKey().toString(), ++ itemStack.getAmount(), ++ itemTag); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(entity.getCustomName()) ? null : new net.md_5.bungee.api.chat.TextComponent(entity.getCustomName())); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, String customName) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(customName) ? null : new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ customName); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent[] customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0565-Cache-block-data-strings.patch b/Remapped-Spigot-Server-Patches/0565-Cache-block-data-strings.patch new file mode 100644 index 000000000..b4924166c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0565-Cache-block-data-strings.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 6 Dec 2018 19:52:50 -0500 +Subject: [PATCH] Cache block data strings + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 735c3c983e96e4e6f36de0975909fc48cb042081..5c5903867432894b47bc62d89989f78c36a84ca1 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1953,6 +1953,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop%s", nms, bukkit); + } + ++ // Paper start - cache block data strings ++ private static Map stringDataCache = new HashMap<>(); ++ ++ static { ++ // cache all of the default states at startup, will not cache ones with the custom states inside of the ++ // brackets in a different order, though ++ reloadCache(); ++ } ++ ++ public static void reloadCache() { ++ stringDataCache.clear(); ++ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); ++ } ++ // Paper end ++ + public static CraftBlockData newData(Material material, String data) { + Preconditions.checkArgument(material == null || material.isBlock(), "Cannot get data for not block %s", material); + ++ // Paper start - cache block data strings ++ if (material != null) { ++ Block block = CraftMagicNumbers.getBlock(material); ++ if (block != null) { ++ ResourceLocation key = Registry.BLOCK.getKey(block); ++ data = data == null ? key.toString() : key + data; ++ } ++ } ++ ++ CraftBlockData cached = stringDataCache.computeIfAbsent(data, s -> createNewData(null, s)); ++ return (CraftBlockData) cached.clone(); ++ } ++ ++ private static CraftBlockData createNewData(Material material, String data) { ++ // Paper end - cache block data strings + BlockState blockData; + Block block = CraftMagicNumbers.getBlock(material); + Map, Comparable> parsed = null; diff --git a/Remapped-Spigot-Server-Patches/0566-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/Remapped-Spigot-Server-Patches/0566-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch new file mode 100644 index 000000000..1e30590a6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0566-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 25 Aug 2020 20:45:36 -0400 +Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported + +Uses correct setPositionRotation for Entity teleporting instead of setLocation +as this is how Vanilla teleports entities. + +Cancel any pending motion when teleported. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ab6494f5a872bba5398bef0367b4d9257786f61e..ab45497e8f7720c9d60626b32e9c95779af676b0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -691,7 +691,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (packet.getId() == this.awaitingTeleport && this.awaitingPositionFromClient != null) { // CraftBukkit +- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.yRot, this.player.xRot); ++ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.yRot, this.player.xRot); // Paper - use proper setPositionRotation for teleportation + this.lastGoodX = this.awaitingPositionFromClient.x; + this.lastGoodY = this.awaitingPositionFromClient.y; + this.lastGoodZ = this.awaitingPositionFromClient.z; +@@ -1536,7 +1536,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit end + + this.awaitingTeleportTime = this.tickCount; +- this.player.absMoveTo(d0, d1, d2, f, f1); ++ this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper setPositionRotation for teleportation + this.player.forceCheckHighPriority(); // Paper + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index aea2457510c75214bbb925307155611e981f115f..d69981a1b5a40418c7d17de5f3bece30592ae586 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -143,6 +143,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; ++ public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation + static boolean isLevelAtLeast(CompoundTag tag, int level) { + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } +@@ -1406,6 +1407,13 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // Paper - cancel entity velocity if teleported ++ if (!preserveMotion) { ++ this.deltaMovement = Vec3.ZERO; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end + this.setPosAndOldPos(x, y, z); + this.yRot = yaw; + this.xRot = pitch; +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 6ca378ec7868b855d46c749910c656f82ddb009f..091e72474ac199c38fff979a5faf524e011d8d0a 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -165,6 +165,7 @@ public abstract class BaseSpawner { + return; + } + ++ entity.preserveMotion = true; // Paper - preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), world.random.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob entityinsentient = (Mob) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index deeae62e9926f9435907c68e7d35e7420f5e79dd..1275768762884416fa3c68dab3a6671b24949976 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -556,7 +556,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // entity.setLocation() throws no event, and so cannot be cancelled +- entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper setPosition, as per vanilla teleporting + // SPIGOT-619: Force sync head rotation also + entity.setYHeadRot(location.getYaw()); + ((net.minecraft.server.level.ServerLevel) entity.level).updateChunkPos(entity); // Spigot - register to new chunk diff --git a/Remapped-Spigot-Server-Patches/0567-Add-additional-open-container-api-to-HumanEntity.patch b/Remapped-Spigot-Server-Patches/0567-Add-additional-open-container-api-to-HumanEntity.patch new file mode 100644 index 000000000..4f031f39d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0567-Add-additional-open-container-api-to-HumanEntity.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 26 Aug 2020 02:12:31 -0400 +Subject: [PATCH] Add additional open container api to HumanEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 878a62e04962aafeaf192075fbe08e319298a800..aceb57c93c91730345f49f78838780c41ce2dcef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -459,6 +459,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().containerMenu.getBukkitView(); + } + ++ // Paper start - Add additional containers ++ @Override ++ public InventoryView openAnvil(Location location, boolean force) { ++ return openInventory(location, force, Material.ANVIL); ++ } ++ ++ @Override ++ public InventoryView openCartographyTable(Location location, boolean force) { ++ return openInventory(location, force, Material.CARTOGRAPHY_TABLE); ++ } ++ ++ @Override ++ public InventoryView openGrindstone(Location location, boolean force) { ++ return openInventory(location, force, Material.GRINDSTONE); ++ } ++ ++ @Override ++ public InventoryView openLoom(Location location, boolean force) { ++ return openInventory(location, force, Material.LOOM); ++ } ++ ++ @Override ++ public InventoryView openSmithingTable(Location location, boolean force) { ++ return openInventory(location, force, Material.SMITHING_TABLE); ++ } ++ ++ @Override ++ public InventoryView openStonecutter(Location location, boolean force) { ++ return openInventory(location, force, Material.STONECUTTER); ++ } ++ ++ private InventoryView openInventory(Location location, boolean force, Material material) { ++ org.spigotmc.AsyncCatcher.catchOp("open" + material); ++ if (location == null) { ++ location = getLocation(); ++ } ++ if (!force) { ++ Block block = location.getBlock(); ++ if (block.getType() != material) { ++ return null; ++ } ++ } ++ net.minecraft.world.level.block.Block block; ++ if (material == Material.ANVIL) { ++ block = Blocks.ANVIL; ++ } else if (material == Material.CARTOGRAPHY_TABLE) { ++ block = Blocks.CARTOGRAPHY_TABLE; ++ } else if (material == Material.GRINDSTONE) { ++ block = Blocks.GRINDSTONE; ++ } else if (material == Material.LOOM) { ++ block = Blocks.LOOM; ++ } else if (material == Material.SMITHING_TABLE) { ++ block = Blocks.SMITHING_TABLE; ++ } else if (material == Material.STONECUTTER) { ++ block = Blocks.STONECUTTER; ++ } else { ++ throw new IllegalArgumentException("Unsupported inventory type: " + material); ++ } ++ getHandle().openMenu(block.getMenuProvider(null, getHandle().level, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); ++ getHandle().containerMenu.checkReachable = !force; ++ return getHandle().containerMenu.getBukkitView(); ++ } ++ // Paper end ++ + @Override + public void closeInventory() { + // Paper start diff --git a/Remapped-Spigot-Server-Patches/0568-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/Remapped-Spigot-Server-Patches/0568-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch new file mode 100644 index 000000000..a02addc37 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0568-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 12 Sep 2020 17:21:38 -0400 +Subject: [PATCH] Cache DataFixerUpper Rewrite Rules on demand + +Mojang precaches every single potential rewrite rule that could ever +exist on server startup. This includes rules from all the way back to versions from 6+ years ago. + +This is the source of why the server hogs every CPU core at 100% every start. + +For anyone who hard resets for updates or has force upgraded their entire world, this +results in completely wasted cpu cycles. + +This massive CPU usage also delays server startup time. + +We improve this by making "min version to precache" that defaults to a future version +so that no rewrite rules are precached. + +someone who expects to be converting a lot chunks could theoretically set +-DPaper.minPrecachedDatafixVersion= as a startup +parameter and only build from that point on. + +However this will likely never be needed as the server will still run +the same cache logic on demand when it's actually needed. The only +cost would be some delay on the FIRST chunk conversion, but paper already +runs chunk conversions on another thread so this will likely never be +a concern for TPS. + +This patch will significantly reduce CPU use on startup, reduce memory usage, +and improve server startup time. + +diff --git a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +index edb77982d273e9492ab1a669ca1ad89da2ec3c3e..abc265b00044b14abb55c2628d454ee01fef467b 100644 +--- a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java ++++ b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +@@ -26,8 +26,10 @@ public class DataFixerBuilder { + private final Int2ObjectSortedMap schemas = new Int2ObjectAVLTreeMap<>(); + private final List globalList = Lists.newArrayList(); + private final IntSortedSet fixerVersions = new IntAVLTreeSet(); ++ private final int minDataFixPrecacheVersion; // Paper + + public DataFixerBuilder(final int dataVersion) { ++ minDataFixPrecacheVersion = Integer.getInteger("Paper.minPrecachedDatafixVersion", dataVersion+1) * 10; // Paper - default to precache nothing - mojang stores versions * 10 to allow for 'sub versions' + this.dataVersion = dataVersion; + } + +@@ -65,6 +67,7 @@ public class DataFixerBuilder { + final IntBidirectionalIterator iterator = fixerUpper.fixerVersions().iterator(); + while (iterator.hasNext()) { + final int versionKey = iterator.nextInt(); ++ if (versionKey < minDataFixPrecacheVersion) continue; // Paper + final Schema schema = schemas.get(versionKey); + for (final String typeName : schema.types()) { + CompletableFuture.runAsync(() -> { diff --git a/Remapped-Spigot-Server-Patches/0569-Extend-block-drop-capture-to-capture-all-items-added.patch b/Remapped-Spigot-Server-Patches/0569-Extend-block-drop-capture-to-capture-all-items-added.patch new file mode 100644 index 000000000..21f6d53f4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0569-Extend-block-drop-capture-to-capture-all-items-added.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 17 Sep 2020 00:36:05 +0100 +Subject: [PATCH] Extend block drop capture to capture all items added to the + world + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 22266fda4de9b5fbace3b8e55ce390b8d7e75a65..fe7b71fbb3963beafe93a5d86bebdd629c7ec8f2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -95,6 +95,7 @@ import net.minecraft.world.entity.ai.village.poi.PoiType; + import net.minecraft.world.entity.animal.horse.SkeletonHorse; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.monster.Drowned; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.raid.Raid; +@@ -1284,6 +1285,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } else if (this.isUUIDUsed(entity)) { + return false; + } else { ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof ItemEntity) { ++ captureDrops.add((ItemEntity) entity); ++ return true; ++ } ++ // Paper end ++ + if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { + return false; + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 79f3e4176145c42debb9adc1e68175cf063c1f22..6269e37f2859417a80e6de16045f1c2325f9746f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -6,6 +6,7 @@ import net.minecraft.world.InteractionResult; + import net.minecraft.world.InteractionResultHolder; + import net.minecraft.world.MenuProvider; + import net.minecraft.world.entity.EquipmentSlot; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.DoubleHighBlockItem; + import net.minecraft.world.item.ItemStack; +@@ -418,10 +419,12 @@ public class ServerPlayerGameMode { + // return true; // CraftBukkit + } + // CraftBukkit start ++ java.util.List itemsToDrop = level.captureDrops; // Paper - store current list ++ level.captureDrops = null; // Paper - Remove this earlier so that we can actually drop stuff + if (event.isDropItems()) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, level.captureDrops); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - use stored ref + } +- level.captureDrops = null; ++ //world.captureDrops = null; // Paper - move up + + // Drop event experience + if (flag && event != null) { diff --git a/Remapped-Spigot-Server-Patches/0570-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/Remapped-Spigot-Server-Patches/0570-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch new file mode 100644 index 000000000..07195903c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0570-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 27 Sep 2020 16:25:24 +0200 +Subject: [PATCH] Don't mark dirty in invalid locations (SPIGOT-6086) + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 82205ad13ef0e987bd83979d06331545efe0a60a..50dc47e8c03ce274d558bc0dfa73ba3ab5fbae5c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -388,6 +388,7 @@ public class ChunkHolder { + } + + public void blockChanged(BlockPos blockposition) { ++ if (!blockposition.isValidLocation()) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks + LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { diff --git a/Remapped-Spigot-Server-Patches/0571-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/Remapped-Spigot-Server-Patches/0571-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch new file mode 100644 index 000000000..6eb1a44bc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0571-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Fri, 28 Aug 2020 01:41:26 +0200 +Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and + non-conflicting Entity Ids + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d69981a1b5a40418c7d17de5f3bece30592ae586..fa22c0d7f676c96d34bf56d80181d6b047f2ff0b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3476,4 +3476,10 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.incrementAndGet(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index de5d02a1345f9886200f0540ac08be0df5878708..9a16882deee21faf78ea46e08b2f2ad3fbb6021b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -443,6 +443,10 @@ public final class CraftMagicNumbers implements UnsafeValues { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + return nmsItemStack.getItem().getDescriptionId(nmsItemStack); + } ++ ++ public int nextEntityId() { ++ return net.minecraft.world.entity.Entity.nextEntityId(); ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0572-Lazily-track-plugin-scoreboards-by-default.patch b/Remapped-Spigot-Server-Patches/0572-Lazily-track-plugin-scoreboards-by-default.patch new file mode 100644 index 000000000..e06db589a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0572-Lazily-track-plugin-scoreboards-by-default.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sat, 3 Oct 2020 04:15:09 -0400 +Subject: [PATCH] Lazily track plugin scoreboards by default + +On servers with plugins that constantly churn through scoreboards, there is a risk of +degraded GC performance due to the number of scoreboards held on by weak references. +Most plugins don't even need the (vanilla) functionality that requires all plugin +scoreboards to be tracked by the server. Instead, only track scoreboards when an +objective is added with a non-dummy criteria. + +This is a breaking change, however the change is a much more sensible default. In case +this breaks your workflow you can always force all scoreboards to be tracked with +settings.track-plugin-scoreboards in paper.yml. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 545948f20efd6c8dd42140b565af94cd6b52b661..7d50aded88f5b7dfebaea1aebc86231f7b5c4e25 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -462,4 +462,9 @@ public class PaperConfig { + private static void maxJoinsPerTick() { + maxJoinsPerTick = getInt("settings.max-joins-per-tick", 3); + } ++ ++ public static boolean trackPluginScoreboards; ++ private static void trackPluginScoreboards() { ++ trackPluginScoreboards = getBoolean("settings.track-plugin-scoreboards", false); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index 4c93be31fd95d731327479519ecb34a08785c1ca..57537b8871dd5c54d97f3effe1802a3396644e46 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -18,6 +18,7 @@ import org.bukkit.scoreboard.Team; + + public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + final Scoreboard board; ++ boolean registeredGlobally = false; // Paper + + CraftScoreboard(Scoreboard board) { + this.board = board; +@@ -44,6 +45,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.isTrue(name.length() <= 16, "The name '" + name + "' is longer than the limit of 16 characters"); + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ // Paper start - the block comment from the old registerNewObjective didnt cause a conflict when rebasing, so this block wasn't added to the adventure registerNewObjective ++ if (craftCriteria.criteria != net.minecraft.world.scores.criteria.ObjectiveCriteria.DUMMY && !registeredGlobally) { ++ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); ++ registeredGlobally = true; ++ } ++ // Paper end + net.minecraft.world.scores.Objective objective = board.registerObjective(name, craftCriteria.criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); + return new CraftObjective(this, objective); + } +@@ -65,6 +72,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ // Paper start ++ if (craftCriteria.criteria != net.minecraft.server.IScoreboardCriteria.DUMMY && !registeredGlobally) { ++ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); ++ registeredGlobally = true; ++ } ++ // Paper end + ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, CraftChatMessage.fromStringOrNull(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); + return new CraftObjective(this, objective);*/ // Paper + return registerNewObjective(name, criteria, io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(displayName), renderType); // Paper +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index 30b0d4986a7edcf324d94d9304d66d0567098855..8217e35f5a3093e63a165ee8c8b30bf3f28f3bd3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -30,6 +30,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + public CraftScoreboardManager(MinecraftServer minecraftserver, net.minecraft.world.scores.Scoreboard scoreboardServer) { + mainScoreboard = new CraftScoreboard(scoreboardServer); ++ mainScoreboard.registeredGlobally = true; // Paper + server = minecraftserver; + scoreboards.add(mainScoreboard); + } +@@ -43,10 +44,22 @@ public final class CraftScoreboardManager implements ScoreboardManager { + public CraftScoreboard getNewScoreboard() { + org.spigotmc.AsyncCatcher.catchOp("scoreboard creation"); // Spigot + CraftScoreboard scoreboard = new CraftScoreboard(new ServerScoreboard(server)); ++ // Paper start ++ if (com.destroystokyo.paper.PaperConfig.trackPluginScoreboards) { ++ scoreboard.registeredGlobally = true; + scoreboards.add(scoreboard); ++ } ++ // Paper end + return scoreboard; + } + ++ // Paper start ++ public void registerScoreboardForVanilla(CraftScoreboard scoreboard) { ++ org.spigotmc.AsyncCatcher.catchOp("scoreboard registration"); ++ scoreboards.add(scoreboard); ++ } ++ // Paper end ++ + // CraftBukkit method + public CraftScoreboard getPlayerBoard(CraftPlayer player) { + CraftScoreboard board = playerBoards.get(player); diff --git a/Remapped-Spigot-Server-Patches/0573-Entity-isTicking.patch b/Remapped-Spigot-Server-Patches/0573-Entity-isTicking.patch new file mode 100644 index 000000000..22d5c7950 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0573-Entity-isTicking.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 21:39:16 -0500 +Subject: [PATCH] Entity#isTicking + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fa22c0d7f676c96d34bf56d80181d6b047f2ff0b..a623c22cd03ad92657e661851fddc76a30986755 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -49,6 +49,7 @@ import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.TicketType; +@@ -3481,5 +3482,9 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public static int nextEntityId() { + return ENTITY_COUNTER.incrementAndGet(); + } ++ ++ public boolean isTicking() { ++ return ((ServerChunkCache) level.getChunkSource()).isInEntityTickingChunk(this); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 1275768762884416fa3c68dab3a6671b24949976..502773ae60290125057fb342a3358ff55927d196 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1159,5 +1159,9 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isInLava() { + return getHandle().isInLava(); + } ++ ++ public boolean isTicking() { ++ return getHandle().isTicking(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0574-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/Remapped-Spigot-Server-Patches/0574-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch new file mode 100644 index 000000000..2d6ec9791 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0574-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 22:00:27 -0500 +Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is + not enabled + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5c5903867432894b47bc62d89989f78c36a84ca1..954dce690852da87a37e7797c6f9f549242e511a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2018,6 +2018,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + diff --git a/Remapped-Spigot-Server-Patches/0575-Fix-Not-a-string-Map-Conversion-spam.patch b/Remapped-Spigot-Server-Patches/0575-Fix-Not-a-string-Map-Conversion-spam.patch new file mode 100644 index 000000000..5b9dbf362 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0575-Fix-Not-a-string-Map-Conversion-spam.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 8 Oct 2020 00:00:25 -0400 +Subject: [PATCH] Fix "Not a string" Map Conversion spam + +The maps did convert successfully, but had noisy logs due to Spigot +implementing this logic incorrectly. + +This stops the spam by converting the old format to new before +requesting the world. + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 7582c7cd4235d212a0cf66a4c59ce0cedaa360ad..e7b178127228dea5a17ba0fbd6bae148d70e8eb5 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -12,6 +12,8 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.StringTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.Packet; +@@ -94,7 +96,26 @@ public class MapItemSavedData extends SavedData { + + @Override + public void load(CompoundTag tag) { +- DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, tag.get("dimension"))); // CraftBukkit - decompile error ++ // Paper start - fix "Not a string" spam ++ Tag dimension = tag.get("dimension"); ++ if (dimension instanceof NumericTag && ((NumericTag) dimension).getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { ++ long least = tag.getLong("UUIDLeast"); ++ long most = tag.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ this.uniqueId = new UUID(most, least); ++ CraftWorld world = (CraftWorld) server.getWorld(this.uniqueId); ++ if (world != null) { ++ dimension = StringTag.create("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); ++ } else { ++ dimension = StringTag.create("bukkit:_invalidworld_"); ++ } ++ } else { ++ dimension = StringTag.create("bukkit:_invalidworld_"); ++ } ++ } ++ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error ++ // Paper end - fix "Not a string" spam + Logger logger = MapItemSavedData.LOGGER; + + logger.getClass(); diff --git a/Remapped-Spigot-Server-Patches/0576-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch b/Remapped-Spigot-Server-Patches/0576-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch new file mode 100644 index 000000000..d952b410f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0576-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sun, 4 Oct 2020 19:55:25 -0700 +Subject: [PATCH] Fix CME on adding a passenger in CreatureSpawnEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a623c22cd03ad92657e661851fddc76a30986755..90e9797cc1f3a4aa0a2bee28dca364e6f6dd0c0b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3180,7 +3180,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public Stream getSelfAndPassengers() { +- return Stream.concat(Stream.of(this), this.passengers.stream().flatMap(Entity::getSelfAndPassengers)); ++ return Stream.concat(Stream.of(this), com.google.common.collect.ImmutableList.copyOf(this.passengers).stream().flatMap(Entity::getSelfAndPassengers)); // Paper + } + + public boolean hasOnePlayerPassenger() { diff --git a/Remapped-Spigot-Server-Patches/0577-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch b/Remapped-Spigot-Server-Patches/0577-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch new file mode 100644 index 000000000..02442096a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0577-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Wed, 18 Mar 2020 00:07:46 -0500 +Subject: [PATCH] MC-147729: Drop items that are extra from a crafting recipe + + +diff --git a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java +index a18aa176850bef45afcaf5742e9afbfa39281e22..c6ba6aabf94c26cccbd14689ea32373c17bbccc4 100644 +--- a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java ++++ b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java +@@ -71,7 +71,12 @@ public class ServerPlaceRecipe implements PlaceRecipe +Date: Wed, 1 Jun 2016 23:29:17 -0400 +Subject: [PATCH] Reset Ender Crystals on Dragon Spawn + +Crystals can end up in a bad state in certain conditions which causes +an exception on the expected number of crystals going negative. + +This ensures the crystals/pillars are in expected state when the dragon spawns. + +See #3522 + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 4b18931225ef60dbcffd7fcc20d0e9ce62348a07..590df3b93b897613cad74f9920aec62b33a2f7f7 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -441,6 +441,7 @@ public class EndDragonFight { + entityenderdragon.moveTo(0.0D, 128.0D, 0.0D, this.level.random.nextFloat() * 360.0F, 0.0F); + this.level.addFreshEntity(entityenderdragon); + this.dragonUUID = entityenderdragon.getUUID(); ++ this.resetSpikeCrystals(); // Paper + return entityenderdragon; + } + diff --git a/Remapped-Spigot-Server-Patches/0579-Fix-for-large-move-vectors-crashing-server.patch b/Remapped-Spigot-Server-Patches/0579-Fix-for-large-move-vectors-crashing-server.patch new file mode 100644 index 000000000..42d90f77e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0579-Fix-for-large-move-vectors-crashing-server.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 17 May 2020 23:47:33 -0700 +Subject: [PATCH] Fix for large move vectors crashing server + +Check movement distance also based on current position. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ab45497e8f7720c9d60626b32e9c95779af676b0..3a114bec14fcc6c1e1045e2b99178a6adb25f387 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -516,19 +516,24 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.getLevel(); +- double d0 = entity.getX(); +- double d1 = entity.getY(); +- double d2 = entity.getZ(); +- double d3 = packet.getX(); +- double d4 = packet.getY(); +- double d5 = packet.getZ(); ++ double d0 = entity.getX();double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.getY();double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.getZ();double fromZ = d2; // Paper - OBFHELPER ++ double d3 = packet.getX();double toX = d3; // Paper - OBFHELPER ++ double d4 = packet.getY();double toY = d4; // Paper - OBFHELPER ++ double d5 = packet.getZ();double toZ = d5; // Paper - OBFHELPER + float f = packet.getYRot(); + float f1 = packet.getXRot(); + double d6 = d3 - this.vehicleFirstGoodX; + double d7 = d4 - this.vehicleFirstGoodY; + double d8 = d5 - this.vehicleFirstGoodZ; + double d9 = entity.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - fromX; ++ double currDeltaY = toY - fromY; ++ double currDeltaZ = toZ - fromZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ // Paper end - fix large move vectors killing the server + + + // CraftBukkit start - handle custom speeds and skipped ticks +@@ -1230,7 +1235,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + double d2 = this.player.getZ(); + double d3 = this.player.getY(); + double d4 = packet.getX(this.player.getX());double toX = d4; // Paper - OBFHELPER +- double d5 = packet.getY(this.player.getY()); ++ double d5 = packet.getY(this.player.getY());double toY = d5; // Paper - OBFHELPER + double d6 = packet.getZ(this.player.getZ());double toZ = d6; // Paper - OBFHELPER + float f = packet.getYRot(this.player.yRot); + float f1 = packet.getXRot(this.player.xRot); +@@ -1238,7 +1243,12 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + double d8 = d5 - this.firstGoodY; + double d9 = d6 - this.firstGoodZ; + double d10 = this.player.getDeltaMovement().lengthSqr(); +- double d11 = d7 * d7 + d8 * d8 + d9 * d9; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - prevX; ++ double currDeltaY = toY - prevY; ++ double currDeltaZ = toZ - prevZ; ++ double d11 = Math.max(d7 * d7 + d8 * d8 + d9 * d9, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d11 > 1.0D) { diff --git a/Remapped-Spigot-Server-Patches/0580-Optimise-getType-calls.patch b/Remapped-Spigot-Server-Patches/0580-Optimise-getType-calls.patch new file mode 100644 index 000000000..f1103a399 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0580-Optimise-getType-calls.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 3 Jun 2020 11:37:13 -0700 +Subject: [PATCH] Optimise getType calls + +Remove the map lookup for converting from Block->Bukkit Material + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +index 9958cdf55cef2177fafd0cdae48da9db064af9f1..4263ac345d57e36e010e3dd009130c02799b249c 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +@@ -11,6 +11,19 @@ public class BlockState extends BlockBehaviour.BlockStateBase { + + public static final Codec CODEC = codec((Codec) Registry.BLOCK, Block::defaultBlockState).stable(); + ++ ++ // Paper start - optimise getType calls ++ org.bukkit.Material cachedMaterial; ++ ++ public final org.bukkit.Material getBukkitMaterial() { ++ if (this.cachedMaterial == null) { ++ this.cachedMaterial = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getBlock()); ++ } ++ ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls ++ + public BlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec mapcodec) { + super(block, propertyMap, mapcodec); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index fe00c8fdacc28a68c732aac0b887ea107d87e979..227974a85a81c623311301e28e83e85424b05f3d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -78,7 +78,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + public Material getBlockType(int x, int y, int z) { + CraftChunk.validateChunkCoordinates(x, y, z); + +- return CraftMagicNumbers.getMaterial(blockids[y >> 4].get(x, y & 0xF, z).getBlock()); ++ return blockids[y >> 4].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index e09f65f0b06c8fb9a965b921c2c8e68ae2ac1e55..37eceaef1212e2ee13aa763a5ede24ec170e5391 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -211,7 +211,7 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(world.getBlockState(position).getBlock()); ++ return world.getBlockState(position).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 754559c9e47032845fdc94eedad0cece0e0ae4c7..7e4c522605cf04a38a7a0f54e7ac6ea0e372c98d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -134,7 +134,7 @@ public class CraftBlockState implements BlockState { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(data.getBlock()); ++ return data.getBukkitMaterial(); // Paper - optimise getType calls + } + + public void setFlag(int flag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 03e065bf92cafd376ad0f878584cdac6fd196245..d93f2373f3127462aa2f9b69f7cc808b58d47b61 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -45,7 +45,7 @@ public class CraftBlockData implements BlockData { + + @Override + public Material getMaterial() { +- return CraftMagicNumbers.getMaterial(state.getBlock()); ++ return state.getBukkitMaterial(); // Paper - optimise getType calls + } + + public BlockState getState() { +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index fd2cb2a584fea360fcf8180338708f35c4e3dc1f..a463b89888460e5a894098c20eb4c4a78c04642c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -73,7 +73,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + + @Override + public Material getType(int x, int y, int z) { +- return CraftMagicNumbers.getMaterial(getTypeId(x, y, z).getBlock()); ++ return getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0581-Villager-resetOffers.patch b/Remapped-Spigot-Server-Patches/0581-Villager-resetOffers.patch new file mode 100644 index 000000000..2d03e1410 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0581-Villager-resetOffers.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 7 Oct 2019 00:15:37 -0500 +Subject: [PATCH] Villager#resetOffers + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 9eee68a5a84e121698d26bd54212a72c75e16251..407a68edf6408400f1a6c5bb1a6cbbfae08ac2cd 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -111,6 +111,13 @@ public abstract class AbstractVillager extends AgableMob implements Npc, Merchan + return this.tradingPlayer != null; + } + ++ // Paper start ++ public void resetOffers() { ++ this.offers = new MerchantOffers(); ++ this.updateTrades(); ++ } ++ // Paper end ++ + @Override + public MerchantOffers getOffers() { + if (this.offers == null) { +@@ -232,6 +239,7 @@ public abstract class AbstractVillager extends AgableMob implements Npc, Merchan + return this.level; + } + ++ protected final void updateTrades() { updateTrades(); } // Paper - OBFHELPER + protected abstract void updateTrades(); + + protected void addOffersFromItemListings(MerchantOffers recipeList, VillagerTrades.ItemListing[] pool, int count) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +index 8ffdfe3e8229b556838eab18dcb6bfb0c05a6063..7f887e883a87f2df7ae428ffddb072724d602d62 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +@@ -70,4 +70,11 @@ public class CraftAbstractVillager extends CraftAgeable implements AbstractVilla + public HumanEntity getTrader() { + return getMerchant().getTrader(); + } ++ ++ // Paper start ++ @Override ++ public void resetOffers() { ++ getHandle().resetOffers(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0582-Improve-inlinig-for-some-hot-IBlockData-methods.patch b/Remapped-Spigot-Server-Patches/0582-Improve-inlinig-for-some-hot-IBlockData-methods.patch new file mode 100644 index 000000000..fc2f46516 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0582-Improve-inlinig-for-some-hot-IBlockData-methods.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 20:46:50 -0700 +Subject: [PATCH] Improve inlinig for some hot IBlockData methods + + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index f2fefdad26057c722085e60ba837fe2c117f55f7..17baae6b11f191f4738a107c7e62ea5bdac17a3c 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -390,7 +390,14 @@ public abstract class BlockBehaviour { + } + // Paper end + ++ // Paper start ++ protected boolean isTicking; ++ protected FluidState fluid; ++ // Paper end ++ + public void initCache() { ++ this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() ++ this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking() + if (!this.getBlock().hasDynamicShape()) { + this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); + } +@@ -429,19 +436,19 @@ public abstract class BlockBehaviour { + return this.getBlock().getOcclusionShape(this.asState(), world, pos); + } + +- public boolean hasLargeCollisionShape() { ++ public final boolean hasLargeCollisionShape() { // Paper + return this.cache == null || this.cache.largeCollisionShape; + } + +- public boolean useShapeForLightOcclusion() { ++ public final boolean useShapeForLightOcclusion() { // Paper + return this.useShapeForLightOcclusion; + } + +- public int getLightEmission() { ++ public final int getLightEmission() { // Paper + return this.lightEmission; + } + +- public boolean isAir() { ++ public final boolean isAir() { // Paper + return this.isAir; + } + +@@ -507,7 +514,7 @@ public abstract class BlockBehaviour { + } + } + +- public boolean canOcclude() { ++ public final boolean canOcclude() { // Paper + return this.canOcclude; + } + +@@ -679,12 +686,12 @@ public abstract class BlockBehaviour { + return this.getBlock().is(block); + } + +- public FluidState getFluidState() { +- return this.getBlock().getFluidState(this.asState()); ++ public final FluidState getFluidState() { // Paper ++ return this.fluid; // Paper - moved into init + } + +- public boolean isRandomlyTicking() { +- return this.getBlock().isRandomlyTicking(this.asState()); ++ public final boolean isRandomlyTicking() { // Paper ++ return this.isTicking; // Paper - moved into init + } + + public SoundType getSoundType() { +diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java +index c077e4a7ce6a484956206eaab9dfde057b7e429a..69cf9e149c207336c537a00f08251a2252752df6 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidState.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidState.java +@@ -20,8 +20,12 @@ public final class FluidState extends StateHolder { + + public static final Codec CODEC = codec((Codec) Registry.FLUID, Fluid::defaultFluidState).stable(); + ++ // Paper start ++ protected final boolean isEmpty; ++ // Paper end + public FluidState(Fluid fluid, ImmutableMap, Comparable> propertiesMap, MapCodec mapcodec) { + super(fluid, propertiesMap, mapcodec); ++ this.isEmpty = fluid.isEmpty(); // Paper - moved from isEmpty() + } + + public Fluid getType() { +@@ -33,7 +37,7 @@ public final class FluidState extends StateHolder { + } + + public boolean isEmpty() { +- return this.getType().isEmpty(); ++ return this.isEmpty; // Paper - moved into constructor + } + + public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/Remapped-Spigot-Server-Patches/0583-Retain-block-place-order-when-capturing-blockstates.patch b/Remapped-Spigot-Server-Patches/0583-Retain-block-place-order-when-capturing-blockstates.patch new file mode 100644 index 000000000..1d1bff4b3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0583-Retain-block-place-order-when-capturing-blockstates.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 7 Aug 2020 04:27:56 -0700 +Subject: [PATCH] Retain block place order when capturing blockstates + +Fixes twisted vines not connecting properly when grown via +bonemeal by a player. + +In general, look at making this logic more robust (i.e properly handling +cases where a captured entry is overriden) - but for now this will do. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index ebb92f88e0402681c47834bcf45e6b236748289a..2ad8a4558aa812885adebee8c05dab45f2bf5f90 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -135,7 +135,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; + public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper +- public Map capturedTileEntities = new HashMap<>(); ++ public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper + public List captureDrops; + public long ticksPerAnimalSpawns; + public long ticksPerMonsterSpawns; diff --git a/Remapped-Spigot-Server-Patches/0584-Reduce-blockpos-allocation-from-pathfinding.patch b/Remapped-Spigot-Server-Patches/0584-Reduce-blockpos-allocation-from-pathfinding.patch new file mode 100644 index 000000000..9371d97ae --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0584-Reduce-blockpos-allocation-from-pathfinding.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 25 Apr 2020 17:10:55 -0700 +Subject: [PATCH] Reduce blockpos allocation from pathfinding + + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index 7ae24381b91c282745b7fe5f6897865e74bc0acf..3c460682611969a5db136aa41ca0d230c6228d56 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -498,7 +498,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + return BlockPathTypes.DANGER_FIRE; + } + +- if (iblockaccess.getFluidState(blockposition_mutableblockposition).is((Tag) FluidTags.WATER)) { ++ if (iblockdata.getFluidState().is((Tag) FluidTags.WATER)) { // Paper - remove another getType call + return BlockPathTypes.WATER_BORDER; + } + } // Paper +@@ -528,7 +528,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + } else if (iblockdata.is(Blocks.COCOA)) { + return BlockPathTypes.COCOA; + } else { +- FluidState fluid = iblockaccess.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper - remove another get type call + + return fluid.is((Tag) FluidTags.WATER) ? BlockPathTypes.WATER : (fluid.is((Tag) FluidTags.LAVA) ? BlockPathTypes.LAVA : (isBurningBlock(iblockdata) ? BlockPathTypes.DAMAGE_FIRE : (DoorBlock.isWoodenDoor(iblockdata) && !(Boolean) iblockdata.getValue(DoorBlock.OPEN) ? BlockPathTypes.DOOR_WOOD_CLOSED : (block instanceof DoorBlock && material == Material.ORE && !(Boolean) iblockdata.getValue(DoorBlock.OPEN) ? BlockPathTypes.DOOR_IRON_CLOSED : (block instanceof DoorBlock && (Boolean) iblockdata.getValue(DoorBlock.OPEN) ? BlockPathTypes.DOOR_OPEN : (block instanceof BaseRailBlock ? BlockPathTypes.RAIL : (block instanceof LeavesBlock ? BlockPathTypes.LEAVES : (!block.is((Tag) BlockTags.FENCES) && !block.is((Tag) BlockTags.WALLS) && (!(block instanceof FenceGateBlock) || (Boolean) iblockdata.getValue(FenceGateBlock.OPEN)) ? (!iblockdata.isPathfindable(iblockaccess, blockposition, PathComputationType.LAND) ? BlockPathTypes.BLOCKED : BlockPathTypes.OPEN) : BlockPathTypes.FENCE)))))))); + } diff --git a/Remapped-Spigot-Server-Patches/0585-Fix-item-locations-dropped-from-campfires.patch b/Remapped-Spigot-Server-Patches/0585-Fix-item-locations-dropped-from-campfires.patch new file mode 100644 index 000000000..535e3ffd3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0585-Fix-item-locations-dropped-from-campfires.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 20:32:25 -0500 +Subject: [PATCH] Fix item locations dropped from campfires + +Fixes #4259 by not flooring the blockposition among other weirdness + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 0c90b1b13611843ba4402c8ccf0b15781b85e773..6c38361d744eae763b6c131ad314485f5a88fcfc 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -14,6 +14,7 @@ import net.minecraft.world.Clearable; + import net.minecraft.world.ContainerHelper; + import net.minecraft.world.Containers; + import net.minecraft.world.SimpleContainer; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.CampfireCookingRecipe; + import net.minecraft.world.item.crafting.RecipeType; +@@ -91,7 +92,11 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable, Ticka + result = blockCookEvent.getResult(); + itemstack1 = CraftItemStack.asNMSCopy(result); + // CraftBukkit end +- Containers.dropItemStack(this.level, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack1); ++ // Paper start ++ ItemEntity droppedItem = new ItemEntity(this.level, blockposition.getX() + 0.5D, blockposition.getY() + 0.5D, blockposition.getZ() + 0.5D, itemstack1.split(this.level.random.nextInt(21) + 10)); ++ droppedItem.setDeltaMovement(this.level.random.nextGaussian() * 0.05D, this.level.random.nextGaussian() * 0.05D + 0.2D, this.level.random.nextGaussian() * 0.05D); ++ this.level.addFreshEntity(droppedItem); ++ // Paper end + this.items.set(i, ItemStack.EMPTY); + this.markUpdated(); + } diff --git a/Remapped-Spigot-Server-Patches/0586-Player-elytra-boost-API.patch b/Remapped-Spigot-Server-Patches/0586-Player-elytra-boost-API.patch new file mode 100644 index 000000000..92e087e79 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0586-Player-elytra-boost-API.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Tue, 14 Apr 2020 12:05:22 +0200 +Subject: [PATCH] Player elytra boost API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 7aae63d22167dc1b3ec7e8bc8672855c2038007e..94240b70e245bdc3dda60420f5787f8d5dcc1958 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -67,6 +67,7 @@ import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.attributes.AttributeInstance; + import net.minecraft.world.entity.ai.attributes.AttributeMap; + import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.projectile.FireworkRocketEntity; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.item.enchantment.Enchantments; +@@ -2281,6 +2282,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + throw new RuntimeException("Unknown settings type"); + } ++ ++ @Override ++ public org.bukkit.entity.Firework boostElytra(ItemStack firework) { ++ Validate.isTrue(isGliding(), "Player must be gliding"); ++ Validate.isTrue(firework != null, "firework == null"); ++ Validate.isTrue(firework.getType() == Material.FIREWORK_ROCKET, "Firework must be Material.FIREWORK_ROCKET"); ++ ++ net.minecraft.world.item.ItemStack item = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(firework); ++ net.minecraft.world.level.Level world = ((CraftWorld) getWorld()).getHandle(); ++ FireworkRocketEntity entity = new FireworkRocketEntity(world, item, getHandle()); ++ return world.addFreshEntity(entity) ++ ? (org.bukkit.entity.Firework) entity.getBukkitEntity() ++ : null; ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0587-Fixed-TileEntityBell-memory-leak.patch b/Remapped-Spigot-Server-Patches/0587-Fixed-TileEntityBell-memory-leak.patch new file mode 100644 index 000000000..de814154f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0587-Fixed-TileEntityBell-memory-leak.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: giacomo <32515303+giacomozama@users.noreply.github.com> +Date: Sat, 10 Oct 2020 12:15:33 +0200 +Subject: [PATCH] Fixed TileEntityBell memory leak + +TileEntityBell has a list of entities (entitiesAtRing) that was not being cleared at the right time, causing leaks whenever a bell would be rung near a crowd of entities. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +index aa45a142aa11acc9fd08b4877891741f3cbd936d..3f9179a7678091875161a34d13b6ec0e78025c4c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +@@ -27,8 +27,8 @@ public class BellBlockEntity extends BlockEntity implements TickableBlockEntity + public int ticks; + public boolean shaking; + public Direction clickDirection; +- private List nearbyEntities; +- private boolean resonating; ++ private List nearbyEntities; private List getEntitiesAtRing() { return this.nearbyEntities; } // Paper - OBFHELPER ++ private boolean resonating; private boolean getShouldReveal() { return this.resonating; } // Paper - OBFHELPER + private int resonationTicks; + + public BellBlockEntity() { +@@ -57,6 +57,11 @@ public class BellBlockEntity extends BlockEntity implements TickableBlockEntity + + if (this.ticks >= 50) { + this.shaking = false; ++ // Paper start ++ if (!this.getShouldReveal()) { ++ this.getEntitiesAtRing().clear(); ++ } ++ // Paper end + this.ticks = 0; + } + +@@ -71,6 +76,7 @@ public class BellBlockEntity extends BlockEntity implements TickableBlockEntity + } else { + this.makeRaidersGlow(this.level); + this.showBellParticles(this.level); ++ this.getEntitiesAtRing().clear(); // Paper + this.resonating = false; + } + } +@@ -111,11 +117,12 @@ public class BellBlockEntity extends BlockEntity implements TickableBlockEntity + LivingEntity entityliving = (LivingEntity) iterator.next(); + + if (entityliving.isAlive() && !entityliving.removed && blockposition.closerThan((Position) entityliving.position(), 32.0D)) { +- entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, (Object) this.level.getGameTime()); ++ entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, this.level.getGameTime()); // Paper - decompile fix + } + } + } + ++ this.getEntitiesAtRing().removeIf(e -> !e.isAlive()); // Paper + } + + private boolean areRaidersNearby() { diff --git a/Remapped-Spigot-Server-Patches/0588-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/Remapped-Spigot-Server-Patches/0588-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch new file mode 100644 index 000000000..6072b2edd --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0588-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Toon Schoenmakers +Date: Fri, 23 Oct 2020 15:01:44 +0200 +Subject: [PATCH] Avoid error bubbling up when item stack is empty in fishing + loot + +This can realistically only happen if there's custom loot active on fishing +which can return 0 items. This would disconnect the player who's fishing. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 7bff012f3cd4458673ee02e5f5f830fc0ef983a3..ef71cca9922ed134ec82fb2982d375bf16f47b17 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -481,9 +481,15 @@ public class FishingHook extends Projectile { + + while (iterator.hasNext()) { + ItemStack itemstack1 = (ItemStack) iterator.next(); +- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), itemstack1); ++ // Paper start, new EntityItem would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty ++ // if the item stack is empty we instead just have our entityitem as null ++ ItemEntity entityitem = null; ++ if (!itemstack1.isEmpty()) { ++ entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), itemstack1); ++ } ++ // Paper end + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null + playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); + this.level.getCraftServer().getPluginManager().callEvent(playerFishEvent); + +@@ -496,8 +502,12 @@ public class FishingHook extends Projectile { + double d2 = entityhuman.getZ() - this.getZ(); + double d3 = 0.1D; + +- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); +- this.level.addFreshEntity(entityitem); ++ // Paper start, entity item can be null, so we need to check against this ++ if (entityitem != null) { ++ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); ++ this.level.addFreshEntity(entityitem); ++ } ++ // Paper end + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { + entityhuman.level.addFreshEntity(new ExperienceOrb(entityhuman.level, entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper diff --git a/Remapped-Spigot-Server-Patches/0589-Add-getOfflinePlayerIfCached-String.patch b/Remapped-Spigot-Server-Patches/0589-Add-getOfflinePlayerIfCached-String.patch new file mode 100644 index 000000000..b58137270 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0589-Add-getOfflinePlayerIfCached-String.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> +Date: Sun, 25 Oct 2020 18:34:50 +1100 +Subject: [PATCH] Add getOfflinePlayerIfCached(String) + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e599be15af17e5e45d2b694c30140cc4a787a7f5..046fbc646d2818bb2c7e08ff22093523e8246523 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1612,6 +1612,28 @@ public final class CraftServer implements Server { + return result; + } + ++ // Paper start ++ @Override ++ @Nullable ++ public OfflinePlayer getOfflinePlayerIfCached(String name) { ++ Validate.notNull(name, "Name cannot be null"); ++ Validate.notEmpty(name, "Name cannot be empty"); ++ ++ OfflinePlayer result = getPlayerExact(name); ++ if (result == null) { ++ GameProfile profile = console.getProfileCache().getProfileIfCached(name); ++ ++ if (profile != null) { ++ result = getOfflinePlayer(profile); ++ } ++ } else { ++ offlinePlayers.remove(result.getUniqueId()); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOfflinePlayer(UUID id) { + Validate.notNull(id, "UUID cannot be null"); diff --git a/Remapped-Spigot-Server-Patches/0590-Add-ignore-discounts-API.patch b/Remapped-Spigot-Server-Patches/0590-Add-ignore-discounts-API.patch new file mode 100644 index 000000000..e20018337 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0590-Add-ignore-discounts-API.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Mon, 9 Nov 2020 20:44:51 +0100 +Subject: [PATCH] Add ignore discounts API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index e9912551e6a19d6ad3b20fad1b716577b9d28f99..415fa3591add1f1ab22dd5866e110dbfccd0ec93 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -459,6 +459,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); ++ if (merchantrecipe.ignoreDiscounts) continue; // Paper + + // CraftBukkit start + int bonus = -Mth.floor((float) i * merchantrecipe.getPriceMultiplier()); +@@ -478,6 +479,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator1.hasNext()) { + MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); ++ if (merchantrecipe1.ignoreDiscounts) continue; // Paper + double d0 = 0.3D + 0.0625D * (double) j; + int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index fa74813e0fe76612023830b2fc41d41aa0b4f10e..25a0f180967911d5916eb71334a94baec84eafbf 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -19,6 +19,7 @@ public class MerchantOffer { + private int demand; + public float priceMultiplier; + public int xp; ++ public boolean ignoreDiscounts; // Paper + // CraftBukkit start + private CraftMerchantRecipe bukkitHandle; + +@@ -27,7 +28,12 @@ public class MerchantOffer { + } + + public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, CraftMerchantRecipe bukkit) { +- this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier); ++ // Paper start - add ignoreDiscounts param ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, false, bukkit); ++ } ++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, ignoreDiscounts); ++ // Paper end + this.bukkitHandle = bukkit; + } + // CraftBukkit end +@@ -59,6 +65,7 @@ public class MerchantOffer { + + this.specialPriceDiff = nbttagcompound.getInt("specialPrice"); + this.demand = nbttagcompound.getInt("demand"); ++ this.ignoreDiscounts = nbttagcompound.getBoolean("Paper.IgnoreDiscounts"); // Paper + } + + public MerchantOffer(ItemStack buyItem, ItemStack sellItem, int maxUses, int rewardedExp, float priceMultiplier) { +@@ -70,10 +77,19 @@ public class MerchantOffer { + } + + public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int rewardedExp, float priceMultiplier) { +- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, rewardedExp, priceMultiplier, 0); ++ // Paper start - add ignoreDiscounts param ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, rewardedExp, priceMultiplier, false); ++ } ++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, boolean ignoreDiscounts) { ++ this(itemstack, itemstack1, itemstack2, i, j, k, f, 0, ignoreDiscounts); + } + + public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, int l) { ++ this(itemstack, itemstack1, itemstack2, i, j, k, f, l, false); ++ } ++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, int l, boolean ignoreDiscounts) { ++ this.ignoreDiscounts = ignoreDiscounts; ++ // Paper end + this.rewardExp = true; + this.xp = 1; + this.baseCostA = itemstack; +@@ -189,6 +205,7 @@ public class MerchantOffer { + nbttagcompound.putFloat("priceMultiplier", this.priceMultiplier); + nbttagcompound.putInt("specialPrice", this.specialPriceDiff); + nbttagcompound.putInt("demand", this.demand); ++ nbttagcompound.putBoolean("Paper.IgnoreDiscounts", this.ignoreDiscounts); // Paper + return nbttagcompound; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +index 212f9c7f3e73ffedf27b94abeac957b7d866a086..a6c8588f10c4c109833aea6a8b02c9048b6d9ea4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +@@ -17,7 +17,12 @@ public class CraftMerchantRecipe extends MerchantRecipe { + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { +- super(result, uses, maxUses, experienceReward, experience, priceMultiplier); ++ // Paper start - add ignoreDiscounts param ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { ++ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, ignoreDiscounts); ++ // Paper end + this.handle = new net.minecraft.world.item.trading.MerchantOffer( + net.minecraft.world.item.ItemStack.EMPTY, + net.minecraft.world.item.ItemStack.EMPTY, +@@ -26,6 +31,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + maxUses, + experience, + priceMultiplier, ++ ignoreDiscounts, // Paper - add ignoreDiscounts param + this + ); + this.setExperienceReward(experienceReward); +@@ -81,6 +87,18 @@ public class CraftMerchantRecipe extends MerchantRecipe { + handle.priceMultiplier = priceMultiplier; + } + ++ // Paper start ++ @Override ++ public boolean shouldIgnoreDiscounts() { ++ return this.handle.ignoreDiscounts; ++ } ++ ++ @Override ++ public void setIgnoreDiscounts(boolean ignoreDiscounts) { ++ this.handle.ignoreDiscounts = ignoreDiscounts; ++ } ++ // Paper end ++ + public net.minecraft.world.item.trading.MerchantOffer toMinecraft() { + List ingredients = getIngredients(); + Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); +@@ -95,7 +113,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + if (recipe instanceof CraftMerchantRecipe) { + return (CraftMerchantRecipe) recipe; + } else { +- CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier()); ++ CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.shouldIgnoreDiscounts()); // Paper - shouldIgnoreDiscounts + craft.setIngredients(recipe.getIngredients()); + + return craft; diff --git a/Remapped-Spigot-Server-Patches/0591-Toggle-for-removing-existing-dragon.patch b/Remapped-Spigot-Server-Patches/0591-Toggle-for-removing-existing-dragon.patch new file mode 100644 index 000000000..184345a27 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0591-Toggle-for-removing-existing-dragon.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 30 Sep 2020 22:49:14 +0200 +Subject: [PATCH] Toggle for removing existing dragon + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 02bb85364560784adea47c877c13291c3d016b86..424754a0183b071d20c86f0420cec784a8992e2b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -683,4 +683,12 @@ public class PaperWorldConfig { + log("Using vanilla redstone algorithm."); + } + } ++ ++ public boolean shouldRemoveDragon = false; ++ private void shouldRemoveDragon() { ++ shouldRemoveDragon = getBoolean("should-remove-dragon", shouldRemoveDragon); ++ if (shouldRemoveDragon) { ++ log("The Ender Dragon will be removed if she already exists without a portal."); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 590df3b93b897613cad74f9920aec62b33a2f7f7..84447e9845edad2d228b94184b35b4afb453a14b 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -210,7 +210,7 @@ public class EndDragonFight { + this.dragonUUID = entityenderdragon.getUUID(); + EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); + this.dragonKilled = false; +- if (!flag) { ++ if (!flag && this.level.paperConfig.shouldRemoveDragon) { // Paper + EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); + entityenderdragon.remove(); + this.dragonUUID = null; diff --git a/Remapped-Spigot-Server-Patches/0592-Fix-client-lag-on-advancement-loading.patch b/Remapped-Spigot-Server-Patches/0592-Fix-client-lag-on-advancement-loading.patch new file mode 100644 index 000000000..2affffb90 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0592-Fix-client-lag-on-advancement-loading.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 31 Oct 2020 11:49:01 -0700 +Subject: [PATCH] Fix client lag on advancement loading + +When new advancements are added via the UnsafeValues#loadAdvancement +API, it triggers a full datapack reload when this is not necessary. The +advancement is already loaded directly into the advancement registry, +and the point of saving the advancement to the Bukkit datapack seems to +be for persistence. By removing the call to reload datapacks when an +advancement is loaded, the client no longer completely freezes up when +adding a new advancement. +To ensure the client still receives the updated advancement data, we +manually reload the advancement data for all players, which +normally takes place as a part of the datapack reloading. + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index acb41cc3965154c5b515cd8e808bf2cf5dc850e4..b8d3f2c59199e245e2035d6205dd1a042aa93f77 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -97,6 +97,7 @@ public class PlayerAdvancements { + + } + ++ public final void reload(ServerAdvancementManager advancementDataWorld) { this.reload(advancementDataWorld); } // Paper - OBFHELPER + public void reload(ServerAdvancementManager advancementLoader) { + this.stopListening(); + this.advancements.clear(); +@@ -393,6 +394,7 @@ public class PlayerAdvancements { + + } + ++ public final void sendUpdateIfNeeded(ServerPlayer entityPlayer) { this.flushDirty(entityPlayer); } // Paper - OBFHELPER + public void flushDirty(ServerPlayer player) { + if (this.isFirstPacket || !this.visibilityChanged.isEmpty() || !this.progressChanged.isEmpty()) { + Map map = Maps.newHashMap(); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 9a16882deee21faf78ea46e08b2f2ad3fbb6021b..ac94fd569bd4c79e30adef148e09e395ba8c1812 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -307,7 +307,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + +- MinecraftServer.getServer().getPlayerList().reloadResources(); ++ // Paper start ++ //MinecraftServer.getServer().getPlayerList().reload(); ++ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { ++ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); ++ player.getAdvancements().sendUpdateIfNeeded(player); ++ }); ++ // Paper end + + return bukkit; + } diff --git a/Remapped-Spigot-Server-Patches/0593-Item-no-age-no-player-pickup.patch b/Remapped-Spigot-Server-Patches/0593-Item-no-age-no-player-pickup.patch new file mode 100644 index 000000000..0c17d7553 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0593-Item-no-age-no-player-pickup.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Smith +Date: Sat, 7 Nov 2020 01:20:33 +0000 +Subject: [PATCH] Item no age & no player pickup + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 9a410f557988d737c3b930a79ef2ccb2b5c8b406..aff17bb3bd22de492b9736d27b7f3e4bdb37134c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -10,6 +10,12 @@ import org.bukkit.entity.Item; + import org.bukkit.inventory.ItemStack; + + public class CraftItem extends CraftEntity implements Item { ++ ++ // Paper start ++ private final static int NO_AGE_TIME = (int) Short.MIN_VALUE; ++ private final static int NO_PICKUP_TIME = (int) Short.MAX_VALUE; ++ // Paper end ++ + private final ItemEntity item; + + public CraftItem(CraftServer server, Entity entity, ItemEntity item) { +@@ -57,6 +63,26 @@ public class CraftItem extends CraftEntity implements Item { + public void setCanMobPickup(boolean canMobPickup) { + item.canMobPickup = canMobPickup; + } ++ ++ @Override ++ public boolean canPlayerPickup() { ++ return item.pickupDelay != NO_PICKUP_TIME; ++ } ++ ++ @Override ++ public void setCanPlayerPickup(boolean canPlayerPickup) { ++ item.pickupDelay = canPlayerPickup ? 0 : NO_PICKUP_TIME; ++ } ++ ++ @Override ++ public boolean willAge() { ++ return item.age != NO_AGE_TIME; ++ } ++ ++ @Override ++ public void setWillAge(boolean willAge) { ++ item.age = willAge ? 0 : NO_AGE_TIME; ++ } + // Paper End + + @Override diff --git a/Remapped-Spigot-Server-Patches/0594-Beacon-API-custom-effect-ranges.patch b/Remapped-Spigot-Server-Patches/0594-Beacon-API-custom-effect-ranges.patch new file mode 100644 index 000000000..78d729cb3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0594-Beacon-API-custom-effect-ranges.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 12:39:08 -0600 +Subject: [PATCH] Beacon API - custom effect ranges + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 5f75c6d653a31f65fcf9c0e280d796e15d059c00..fed29e5707e2a7f64159d284c52647dd91e1948e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -71,6 +71,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + return (hasSecondaryEffect()) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, getLevelCb(), getAmplification(), true, true)) : null; + } + // CraftBukkit end ++ // Paper start - add field/methods for custom range ++ private final String PAPER_RANGE_TAG = "Paper.Range"; ++ private double effectRange = -1; ++ ++ public double getEffectRange() { ++ if (this.effectRange < 0) { ++ return this.levels * 10 + 10; ++ } else { ++ return effectRange; ++ } ++ } ++ ++ public void setEffectRange(double range) { ++ this.effectRange = range; ++ } ++ ++ public void resetEffectRange() { ++ this.effectRange = -1; ++ } ++ // Paper end + + public BeaconBlockEntity() { + super(BlockEntityType.BEACON); +@@ -261,7 +281,8 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + + public List getHumansInRange() { + { +- double d0 = (double) (this.levels * 10 + 10); ++ // Paper - custom beacon ranges ++ double d0 = this.getEffectRange(); + + AABB axisalignedbb = (new AABB(this.worldPosition)).inflate(d0).expandTowards(0.0D, (double) this.level.getMaxBuildHeight(), 0.0D); + List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.player.Player.class, axisalignedbb); +@@ -361,6 +382,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + this.secondaryPower = MobEffect.byId(tag.getInt("Secondary")); + this.levels = tag.getInt("Levels"); // SPIGOT-5053, use where available + // CraftBukkit end ++ // Paper ++ this.effectRange = tag.contains(PAPER_RANGE_TAG, 6) ? tag.getDouble(PAPER_RANGE_TAG) : -1; ++ + if (tag.contains("CustomName", 8)) { + this.name = Component.Serializer.fromJson(tag.getString("CustomName")); + } +@@ -377,6 +401,8 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + if (this.name != null) { + tag.putString("CustomName", Component.Serializer.toJson(this.name)); + } ++ // Paper ++ tag.putDouble(PAPER_RANGE_TAG, this.effectRange); + + this.lockKey.addToTag(tag); + return tag; +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index 940fef58f14e06213c7f305f67dcb8918976c03d..2a10a9352fdb52f5cb27eae2b6d3baa9ff95e486 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -108,4 +108,19 @@ public class CraftBeacon extends CraftBlockEntityState implem + public void setLock(String key) { + this.getSnapshot().lockKey = (key == null) ? LockCode.NO_LOCK : new LockCode(key); + } ++ ++ @Override ++ public double getEffectRange() { ++ return this.getSnapshot().getEffectRange(); ++ } ++ ++ @Override ++ public void setEffectRange(double range) { ++ this.getSnapshot().setEffectRange(range); ++ } ++ ++ @Override ++ public void resetEffectRange() { ++ this.getSnapshot().resetEffectRange(); ++ } + } diff --git a/Remapped-Spigot-Server-Patches/0595-Add-API-for-quit-reason.patch b/Remapped-Spigot-Server-Patches/0595-Add-API-for-quit-reason.patch new file mode 100644 index 000000000..988b77b05 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0595-Add-API-for-quit-reason.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:19:52 +0100 +Subject: [PATCH] Add API for quit reason + + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 6f7cbce5a049d87d4a0ed7cc4517cb4e8694efb5..3ba9c38fc44a8edba9b504112a383249052a0035 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -137,12 +137,15 @@ public class Connection extends SimpleChannelInboundHandler> { + + this.handlingFault = true; + if (this.channel.isOpen()) { ++ ServerPlayer player = this.getPlayer(); // Paper + if (throwable instanceof TimeoutException) { + Connection.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper + this.disconnect(new TranslatableComponent("disconnect.timeout")); + } else { + TranslatableComponent chatmessage = new TranslatableComponent("disconnect.genericReason", new Object[]{"Internal Exception: " + throwable}); + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper + if (flag) { + Connection.LOGGER.debug("Failed to sent packet", throwable); + this.send(new ClientboundDisconnectPacket(chatmessage), (future) -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index bd4d4ace35e966e819aa461d3962fe06ff402be7..1b5f24920c46cd238a79f5a2857d26fa1c12b983 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -258,6 +258,7 @@ public class ServerPlayer extends Player implements ContainerListener { + double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + + boolean needsChunkCenterUpdate; // Paper - no-tick view distance ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { + super(world, world.getSpawn(), world.getSharedSpawnAngle(), profile); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3a114bec14fcc6c1e1045e2b99178a6adb25f387..ccfe5a1ec1e9895f7462b7c676fee80903502a88 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -447,6 +447,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), (future) -> { + this.connection.disconnect(ichatbasecomponent); + }); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7e44c911f4abc5c7d0e89513bf2cfc3516f13492..cda21726f4929b03191e912550d4e4b1232b3b0b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -586,7 +586,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())), entityplayer.quitReason); // Paper - quit reason + if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + diff --git a/Remapped-Spigot-Server-Patches/0596-Seed-based-feature-search.patch b/Remapped-Spigot-Server-Patches/0596-Seed-based-feature-search.patch new file mode 100644 index 000000000..40529ba82 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0596-Seed-based-feature-search.patch @@ -0,0 +1,115 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 13 Jan 2020 15:40:32 +0100 +Subject: [PATCH] Seed based feature search + +This tries to work around the issue where the server will load +surrounding chunks up to a radius of 100 chunks in order to search for +features e.g. when running the /locate command or for treasure maps +(issue #2312). +This is done by backporting Mojang's change in 1.17 which makes it so +that the biome (generated by the seed) is checked first if the feature +can be generated before actually to load the chunk. + +Additionally to that the center location of the target chunk is simply +returned if the chunk is not loaded to avoid the sync chunk load. +As this can lead to less precise locations a toggle is provided to +enable the sync loading of the target chunk again. + +The main downside of this is that it breaks once the seed or generator +changes but this should usually not happen. A config option to disable +this completely is added though in case that should ever be necessary. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 424754a0183b071d20c86f0420cec784a8992e2b..97870622e41cca36d9c7493bfad796f35f3831f4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -337,6 +337,14 @@ public class PaperWorldConfig { + } + } + ++ public boolean seedBasedFeatureSearch = true; ++ public boolean seedBasedFeatureSearchLoadsChunks = false; ++ private void seedBasedFeatureSearch() { ++ seedBasedFeatureSearch = getBoolean("seed-based-feature-search", seedBasedFeatureSearch); ++ seedBasedFeatureSearchLoadsChunks = getBoolean("seed-based-feature-search-loads-chunks", seedBasedFeatureSearchLoadsChunks); ++ log("Feature search is based on seed: " + seedBasedFeatureSearch + ", loads chunks:" + seedBasedFeatureSearchLoadsChunks); ++ } ++ + public int maxCollisionsPerEntity; + private void maxEntityCollision() { + maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); +diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +index 4a5f318adf5bc2ca1c3fab5d173a99cddd77ab85..f61a3eda40328922b95f166be4dc604500e000be 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkPos.java ++++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +@@ -68,10 +68,12 @@ public class ChunkPos { + } + } + ++ public int getBlockX() { return getMinBlockX(); } // Paper - OBFHELPER + public int getMinBlockX() { + return this.x << 4; + } + ++ public int getBlockZ() { return getMinBlockZ(); } // Paper - OBFHELPER + public int getMinBlockZ() { + return this.z << 4; + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 2ad8a4558aa812885adebee8c05dab45f2bf5f90..066d5f7ee93351bff67c0d39ee9d940ac51515d8 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1511,8 +1511,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.profiler; + } + +- @Override +- public BiomeManager getBiomeManager() { ++ public BiomeManager getBiomeManager() { return getBiomeManager(); } // Paper - OBFHELPER ++ @Override public BiomeManager getBiomeManager() { + return this.biomeManager; + } + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +index d22ac114440d807a6cf5f286961bc63935fa7823..3a1909c0b198d89539f4351d70a39d16cfd84987 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +@@ -23,6 +23,7 @@ public class BiomeManager { + return new BiomeManager(source, this.biomeZoomSeed, this.zoomer); + } + ++ public Biome getBiome(BlockPos blockposition) { return getBiome(blockposition); } // Paper - OBFHELPER + public Biome getBiome(BlockPos pos) { + return this.zoomer.getBiome(this.biomeZoomSeed, pos.getX(), pos.getY(), pos.getZ(), this.noiseBiomeSource); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +index 0624b8270bc28c83c5479cd51fa4633ed5c36f44..6b24590a1ac460a7fd4bbc2c70d4a4981378e79c 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java +@@ -176,7 +176,24 @@ public abstract class StructureFeature { + int j2 = i1 + k * l1; + ChunkPos chunkcoordintpair = this.getPotentialFeatureChunk(config, worldSeed, seededrandom, i2, j2); + if (!world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper +- ChunkAccess ichunkaccess = world.getChunk(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); ++ // Paper start - seed based feature search ++ ChunkAccess ichunkaccess = null; ++ if (structureAccessor.getWorld().paperConfig.seedBasedFeatureSearch) { ++ Biome biomeBase = structureAccessor.getWorld().getBiomeManager().getBiome(new BlockPos(chunkcoordintpair.getBlockX() + 9, 0, chunkcoordintpair.getBlockZ() + 9)); ++ if (!biomeBase.getGenerationSettings().isValidStart(this)) { ++ continue; ++ } ++ if (!structureAccessor.getWorld().paperConfig.seedBasedFeatureSearchLoadsChunks) { ++ ichunkaccess = structureAccessor.getWorld().getChunkIfLoaded(chunkcoordintpair.x, chunkcoordintpair.z); ++ if (ichunkaccess == null) { ++ return chunkcoordintpair.asPosition().add(8, searchStartPos.getY(), 8); ++ } ++ } ++ } ++ if (ichunkaccess == null) { ++ ichunkaccess = world.getChunk(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); ++ } ++ // Paper end + StructureStart structurestart = structureAccessor.getStartForFeature(SectionPos.of(ichunkaccess.getPos(), 0), this, ichunkaccess); + + if (structurestart != null && structurestart.e()) { diff --git a/Remapped-Spigot-Server-Patches/0597-Add-Wandering-Trader-spawn-rate-config-options.patch b/Remapped-Spigot-Server-Patches/0597-Add-Wandering-Trader-spawn-rate-config-options.patch new file mode 100644 index 000000000..2dbaff374 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0597-Add-Wandering-Trader-spawn-rate-config-options.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 20 Aug 2020 11:20:12 -0700 +Subject: [PATCH] Add Wandering Trader spawn rate config options + +Adds config options for modifying the spawn rates of Wandering Traders. +These values are all easy to understand and configure after a quick read of this +page on the Minecraft wiki: https://minecraft.gamepedia.com/Wandering_Trader#Spawning +Usages of the vanilla WanderingTraderSpawnDelay and WanderingTraderSpawnChance values +in IWorldServerData are removed as they were only used in certain places, with hardcoded +values used in other places. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 97870622e41cca36d9c7493bfad796f35f3831f4..5a451cc855de57f79a57670ba38e3af2343cb510 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -699,4 +699,17 @@ public class PaperWorldConfig { + log("The Ender Dragon will be removed if she already exists without a portal."); + } + } ++ ++ public int wanderingTraderSpawnMinuteTicks = 1200; ++ public int wanderingTraderSpawnDayTicks = 24000; ++ public int wanderingTraderSpawnChanceFailureIncrement = 25; ++ public int wanderingTraderSpawnChanceMin = 25; ++ public int wanderingTraderSpawnChanceMax = 75; ++ private void wanderingTraderSettings() { ++ wanderingTraderSpawnMinuteTicks = getInt("wandering-trader.spawn-minute-length", wanderingTraderSpawnMinuteTicks); ++ wanderingTraderSpawnDayTicks = getInt("wandering-trader.spawn-day-length", wanderingTraderSpawnDayTicks); ++ wanderingTraderSpawnChanceFailureIncrement = getInt("wandering-trader.spawn-chance-failure-increment", wanderingTraderSpawnChanceFailureIncrement); ++ wanderingTraderSpawnChanceMin = getInt("wandering-trader.spawn-chance-min", wanderingTraderSpawnChanceMin); ++ wanderingTraderSpawnChanceMax = getInt("wandering-trader.spawn-chance-max", wanderingTraderSpawnChanceMax); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index c297051bc48ff20a6764496cbf11eef601761d13..9074d57e1576db2da3e4c76add4f7e07e5567879 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -30,49 +30,59 @@ public class WanderingTraderSpawner implements CustomSpawner { + + private final Random random = new Random(); + private final ServerLevelData serverLevelData; +- private int tickDelay; +- private int spawnDelay; +- private int spawnChance; ++ private int tickDelay; public final int getMinuteTimer() { return this.tickDelay; } public final void setMinuteTimer(int x) { this.tickDelay = x; } // Paper - OBFHELPER ++ private int spawnDelay; public final int getDayTimer() { return this.spawnDelay; } public final void setDayTimer(int x) { this.spawnDelay = x; } // Paper - OBFHELPER ++ private int spawnChance; public final int getSpawnChance() { return this.spawnChance; } public final void setSpawnChance(int x) { this.spawnChance = x; } // Paper - OBFHELPER + + public WanderingTraderSpawner(ServerLevelData properties) { + this.serverLevelData = properties; +- this.tickDelay = 1200; +- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); +- this.spawnChance = properties.getWanderingTraderSpawnChance(); +- if (this.spawnDelay == 0 && this.spawnChance == 0) { +- this.spawnDelay = 24000; +- properties.setWanderingTraderSpawnDelay(this.spawnDelay); +- this.spawnChance = 25; +- properties.setWanderingTraderSpawnChance(this.spawnChance); +- } ++ // Paper start ++ this.setMinuteTimer(Integer.MIN_VALUE); ++ //this.d = iworlddataserver.v(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //this.e = iworlddataserver.w(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //if (this.d == 0 && this.e == 0) { ++ // this.d = 24000; ++ // iworlddataserver.g(this.d); ++ // this.e = 25; ++ // iworlddataserver.h(this.e); ++ //} ++ // Paper end + + } + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ // Paper start ++ if (this.getMinuteTimer() == Integer.MIN_VALUE) { ++ this.setMinuteTimer(world.paperConfig.wanderingTraderSpawnMinuteTicks); ++ this.setDayTimer(world.paperConfig.wanderingTraderSpawnDayTicks); ++ this.setSpawnChance(world.paperConfig.wanderingTraderSpawnChanceMin); ++ } + if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay > 0) { ++ } else if (this.getMinuteTimer() - 1 > 0) { ++ this.setMinuteTimer(this.getMinuteTimer() - 1); + return 0; + } else { +- this.tickDelay = 1200; +- this.spawnDelay -= 1200; +- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); +- if (this.spawnDelay > 0) { ++ this.setMinuteTimer(world.paperConfig.wanderingTraderSpawnMinuteTicks); ++ this.setDayTimer(getDayTimer() - world.paperConfig.wanderingTraderSpawnMinuteTicks); ++ //this.b.g(this.d); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways ++ if (this.getDayTimer() > 0) { + return 0; + } else { +- this.spawnDelay = 24000; ++ this.setDayTimer(world.paperConfig.wanderingTraderSpawnDayTicks); + if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { +- int i = this.spawnChance; ++ int i = this.getSpawnChance(); + +- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); +- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ this.setSpawnChance(Mth.clamp(i + world.paperConfig.wanderingTraderSpawnChanceFailureIncrement, world.paperConfig.wanderingTraderSpawnChanceMin, world.paperConfig.wanderingTraderSpawnChanceMax)); ++ //this.b.h(this.e); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(world)) { +- this.spawnChance = 25; ++ this.setSpawnChance(world.paperConfig.wanderingTraderSpawnChanceMin); ++ // Paper end + return 1; + } else { + return 0; diff --git a/Remapped-Spigot-Server-Patches/0598-Significantly-improve-performance-of-the-end-generat.patch b/Remapped-Spigot-Server-Patches/0598-Significantly-improve-performance-of-the-end-generat.patch new file mode 100644 index 000000000..22a13c79b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0598-Significantly-improve-performance-of-the-end-generat.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SuperCoder7979 <25208576+SuperCoder7979@users.noreply.github.com> +Date: Tue, 3 Nov 2020 23:48:05 -0600 +Subject: [PATCH] Significantly improve performance of the end generation + +This patch implements a noise cache for the end which significantly reduces the computation time of generation. This results in about a 3x improvement. + +Original code by SuperCoder7979 and Gegy in Lithium, licensed under LGPL-3.0 (Source: https://github.com/jellysquid3/lithium-fabric) + +Co-authored-by: Gegy +Co-authored-by: Dylan Xaldin +Co-authored-by: pop4959 + +diff --git a/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java b/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java +index 063369d3a64b4afc9cc6e1d20360900595e1a05f..f01d1b01ebc31f0967a73871f278aac9e414fb67 100644 +--- a/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java ++++ b/src/main/java/net/minecraft/world/level/biome/TheEndBiomeSource.java +@@ -3,10 +3,12 @@ package net.minecraft.world.level.biome; + import com.google.common.collect.ImmutableList; + import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; ++import it.unimi.dsi.fastutil.HashCommon; // Paper + import java.util.List; + import net.minecraft.core.Registry; + import net.minecraft.resources.RegistryLookupCodec; + import net.minecraft.util.Mth; ++import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.levelgen.WorldgenRandom; + import net.minecraft.world.level.levelgen.synth.SimplexNoise; + +@@ -27,6 +29,16 @@ public class TheEndBiomeSource extends BiomeSource { + private final Biome midlands; + private final Biome islands; + private final Biome barrens; ++ // Paper start ++ private static final class NoiseCache { ++ public long[] keys = new long[8192]; ++ public float[] values = new float[8192]; ++ public NoiseCache() { ++ java.util.Arrays.fill(keys, Long.MIN_VALUE); ++ } ++ } ++ private static final ThreadLocal> noiseCache = ThreadLocal.withInitial(java.util.WeakHashMap::new); ++ // Paper end + + public TheEndBiomeSource(Registry biomeRegistry, long seed) { + this(biomeRegistry, seed, (Biome) biomeRegistry.lifecycle(Biomes.THE_END), (Biome) biomeRegistry.lifecycle(Biomes.END_HIGHLANDS), (Biome) biomeRegistry.lifecycle(Biomes.END_MIDLANDS), (Biome) biomeRegistry.lifecycle(Biomes.SMALL_END_ISLANDS), (Biome) biomeRegistry.lifecycle(Biomes.END_BARRENS)); +@@ -81,13 +93,27 @@ public class TheEndBiomeSource extends BiomeSource { + + f = Mth.clamp(f, -100.0F, 80.0F); + ++ NoiseCache cache = noiseCache.get().computeIfAbsent(noisegenerator3handler, m -> new NoiseCache()); // Paper + for (int k1 = -12; k1 <= 12; ++k1) { + for (int l1 = -12; l1 <= 12; ++l1) { + long i2 = (long) (k + k1); + long j2 = (long) (l + l1); + +- if (i2 * i2 + j2 * j2 > 4096L && noisegenerator3handler.getValue((double) i2, (double) j2) < -0.8999999761581421D) { +- float f1 = (Mth.abs((float) i2) * 3439.0F + Mth.abs((float) j2) * 147.0F) % 13.0F + 9.0F; ++ // Paper start - Significantly improve end generation performance by using a noise cache ++ long key = ChunkPos.asLong((int) i2, (int) j2); ++ int index = (int) HashCommon.mix(key) & 8191; ++ float f1 = Float.MIN_VALUE; ++ if (cache.keys[index] == key) { ++ f1 = cache.values[index]; ++ } else { ++ if (i2 * i2 + j2 * j2 > 4096L && noisegenerator3handler.getValue((double) i2, (double) j2) < -0.8999999761581421D) { ++ f1 = (Mth.abs((float) i2) * 3439.0F + Mth.abs((float) j2) * 147.0F) % 13.0F + 9.0F; ++ } ++ cache.keys[index] = key; ++ cache.values[index] = f1; ++ } ++ if (f1 != Float.MIN_VALUE) { ++ // Paper end + float f2 = (float) (i1 - k1 * 2); + float f3 = (float) (j1 - l1 * 2); + float f4 = 100.0F - Mth.sqrt(f2 * f2 + f3 * f3) * f1; diff --git a/Remapped-Spigot-Server-Patches/0599-Expose-world-spawn-angle.patch b/Remapped-Spigot-Server-Patches/0599-Expose-world-spawn-angle.patch new file mode 100644 index 000000000..0086ded8d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0599-Expose-world-spawn-angle.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Tue, 17 Nov 2020 19:13:09 +0200 +Subject: [PATCH] Expose world spawn angle + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index cda21726f4929b03191e912550d4e4b1232b3b0b..7e07fd0c8dec9f9cdeda65dfa0ccf42b4dde010e 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -890,7 +890,7 @@ public abstract class PlayerList { + if (location == null) { + worldserver1 = this.server.getLevel(Level.OVERWORLD); + blockposition = entityplayer1.getSpawnPoint(worldserver1); +- location = new Location(worldserver1.getWorld(), (double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.1F), (double) ((float) blockposition.getZ() + 0.5F)); ++ location = new Location(worldserver1.getWorld(), (double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.1F), (double) ((float) blockposition.getZ() + 0.5F), worldserver1.levelData.getSpawnAngle(), 0.0F); // Paper - use world spawn angle + } + + Player respawnPlayer = cserver.getPlayer(entityplayer1); +diff --git a/src/main/java/net/minecraft/world/level/storage/LevelData.java b/src/main/java/net/minecraft/world/level/storage/LevelData.java +index 12a2371b15588ae84824d7a2d36a6d4c37e77013..078cee770a77b77d9b4a777754599d7b0b31f54b 100644 +--- a/src/main/java/net/minecraft/world/level/storage/LevelData.java ++++ b/src/main/java/net/minecraft/world/level/storage/LevelData.java +@@ -12,6 +12,7 @@ public interface LevelData { + + int getZSpawn(); + ++ default float getSpawnAngle() { return getSpawnAngle(); } // Paper - OBFHELPER + float getSpawnAngle(); + + long getGameTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 28bf53bc9fca21f57cd4851adf508d833ecdd33b..aaf97c13babce3b0ffc639ef950d59d1eba1398a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -362,7 +362,7 @@ public class CraftWorld implements World { + @Override + public Location getSpawnLocation() { + BlockPos spawn = world.getSpawn(); +- return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ()); ++ return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ(), world.levelData.getSpawnAngle(), 0.0F); // Paper - expose world spawn angle + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0600-Add-Destroy-Speed-API.patch b/Remapped-Spigot-Server-Patches/0600-Add-Destroy-Speed-API.patch new file mode 100644 index 000000000..53910619a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0600-Add-Destroy-Speed-API.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ineusia +Date: Mon, 26 Oct 2020 11:48:06 -0500 +Subject: [PATCH] Add Destroy Speed API + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 37eceaef1212e2ee13aa763a5ede24ec170e5391..9defb202761296a825d035e27ddc51e17a311647 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -766,5 +766,23 @@ public class CraftBlock implements Block { + public String getTranslationKey() { + return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); + } ++ ++ @Override ++ public float getDestroySpeed(ItemStack itemStack, boolean considerEnchants) { ++ net.minecraft.world.item.ItemStack nmsItemStack; ++ if (itemStack instanceof CraftItemStack) { ++ nmsItemStack = ((CraftItemStack) itemStack).getHandle(); ++ } else { ++ nmsItemStack = CraftItemStack.asNMSCopy(itemStack); ++ } ++ float speed = nmsItemStack.getItem().getDestroySpeed(nmsItemStack, this.getNMSBlock().defaultBlockState()); ++ if (speed > 1.0F && considerEnchants) { ++ int enchantLevel = net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BLOCK_EFFICIENCY, nmsItemStack); ++ if (enchantLevel > 0) { ++ speed += enchantLevel * enchantLevel + 1; ++ } ++ } ++ return speed; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0601-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/Remapped-Spigot-Server-Patches/0601-Fix-Player-spawnParticle-x-y-z-precision-loss.patch new file mode 100644 index 000000000..95a480a03 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0601-Fix-Player-spawnParticle-x-y-z-precision-loss.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Esophose +Date: Sat, 3 Oct 2020 18:57:47 -0600 +Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 94240b70e245bdc3dda60420f5787f8d5dcc1958..40380fff222cc1f3340cf6a6c4afbe60aaa5d3a6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2008,7 +2008,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } +- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); ++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.toNMS(particle, data), true, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - Fix x/y/z coordinate precision loss + getHandle().connection.send(packetplayoutworldparticles); + + } diff --git a/Remapped-Spigot-Server-Patches/0602-Add-LivingEntity-clearActiveItem.patch b/Remapped-Spigot-Server-Patches/0602-Add-LivingEntity-clearActiveItem.patch new file mode 100644 index 000000000..308d4591a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0602-Add-LivingEntity-clearActiveItem.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anrza +Date: Wed, 15 Jul 2020 12:08:49 +0200 +Subject: [PATCH] Add LivingEntity#clearActiveItem + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index eb136af0f99f5d7520ceabb98cefd5a01122872c..170bb1124ee396a85dd64baed8110e39823ad849 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -773,6 +773,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().useItem.asBukkitMirror(); + } + ++ // Paper start ++ @Override ++ public void clearActiveItem() { ++ getHandle().stopUsingItem(); ++ } ++ // Paper end ++ + @Override + public int getItemUseRemainingTime() { + return getHandle().getItemUseRemainingTime(); diff --git a/Remapped-Spigot-Server-Patches/0603-Add-PlayerItemCooldownEvent.patch b/Remapped-Spigot-Server-Patches/0603-Add-PlayerItemCooldownEvent.patch new file mode 100644 index 000000000..4d8772cbb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0603-Add-PlayerItemCooldownEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Tue, 25 Aug 2020 13:48:33 +0200 +Subject: [PATCH] Add PlayerItemCooldownEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +index 93161583c215e1832570b39e72f7e7cfb94a700c..1983cdcefed60795e8c88737ae3459d5821cdcfa 100644 +--- a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java ++++ b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +@@ -1,16 +1,27 @@ + package net.minecraft.world.item; + ++import io.papermc.paper.event.player.PlayerItemCooldownEvent; // Paper + import net.minecraft.network.protocol.game.ClientboundCooldownPacket; + import net.minecraft.server.level.ServerPlayer; + + public class ServerItemCooldowns extends ItemCooldowns { + +- private final ServerPlayer player; ++ private final ServerPlayer player; public ServerPlayer getEntityPlayer() { return player; } // Paper - OBFHELPER + + public ServerItemCooldowns(ServerPlayer player) { + this.player = player; + } + ++ // Paper start ++ @Override ++ public void addCooldown(Item item, int duration) { ++ PlayerItemCooldownEvent event = new PlayerItemCooldownEvent(getEntityPlayer().getBukkitEntity(), org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(item), duration); ++ if (event.callEvent()) { ++ super.addCooldown(item, event.getCooldown()); ++ } ++ } ++ // Paper end ++ + @Override + protected void onCooldownStarted(Item item, int duration) { + super.onCooldownStarted(item, duration); diff --git a/Remapped-Spigot-Server-Patches/0604-More-lightning-API.patch b/Remapped-Spigot-Server-Patches/0604-More-lightning-API.patch new file mode 100644 index 000000000..6c4c8bac1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0604-More-lightning-API.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Sun, 26 Jul 2020 14:44:09 +0200 +Subject: [PATCH] More lightning API + + +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index 4b0dbeded2b8a475d32f518957909d3495a4b6fc..3fdef4511e21e453b89e42a8f41e587fe300ba6b 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -28,7 +28,7 @@ public class LightningBolt extends Entity { + + private int life; + public long seed; +- private int flashes; ++ private int flashes; public int getFlashCount() { return flashes; } public void setFlashCount(int flashes) { this.flashes = flashes; } // Paper - OBFHELPER + public boolean visualOnly; + @Nullable + private ServerPlayer cause; +@@ -46,6 +46,16 @@ public class LightningBolt extends Entity { + this.visualOnly = cosmetic; + } + ++ // Paper start ++ public int getLifeTicks() { ++ return life; ++ } ++ ++ public void setLifeTicks(int lifeTicks) { ++ this.life = lifeTicks; ++ } ++ // Paper end ++ + @Override + public SoundSource getSoundSource() { + return SoundSource.WEATHER; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index 26506c22592b58b208487fb244985361d70988a8..c1593bb345b38deb4d8b28a73d8dc6246c17b873 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -45,4 +45,27 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @Override ++ public int getFlashCount() { ++ return getHandle().getFlashCount(); ++ } ++ ++ @Override ++ public void setFlashCount(int flashes) { ++ com.google.common.base.Preconditions.checkArgument(flashes >= 0, "Flashes has to be a positive number!"); ++ getHandle().setFlashCount(flashes); ++ } ++ ++ @Override ++ public int getLifeTicks() { ++ return getHandle().getLifeTicks(); ++ } ++ ++ @Override ++ public void setLifeTicks(int lifeTicks) { ++ getHandle().setLifeTicks(lifeTicks); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0605-Climbing-should-not-bypass-cramming-gamerule.patch b/Remapped-Spigot-Server-Patches/0605-Climbing-should-not-bypass-cramming-gamerule.patch new file mode 100644 index 000000000..c25768004 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0605-Climbing-should-not-bypass-cramming-gamerule.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 20:59:00 +0200 +Subject: [PATCH] Climbing should not bypass cramming gamerule + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5a451cc855de57f79a57670ba38e3af2343cb510..7d3207a9af8360ddad228281d6aa65e1a0d24157 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -712,4 +712,9 @@ public class PaperWorldConfig { + wanderingTraderSpawnChanceMin = getInt("wandering-trader.spawn-chance-min", wanderingTraderSpawnChanceMin); + wanderingTraderSpawnChanceMax = getInt("wandering-trader.spawn-chance-max", wanderingTraderSpawnChanceMax); + } ++ ++ public boolean fixClimbingBypassingCrammingRule = false; ++ private void fixClimbingBypassingCrammingRule() { ++ fixClimbingBypassingCrammingRule = getBoolean("fix-climbing-bypassing-cramming-rule", fixClimbingBypassingCrammingRule); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 90e9797cc1f3a4aa0a2bee28dca364e6f6dd0c0b..ec98f5f59ca2b4cb58eb00ed8cdfa364f8bacd88 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1573,6 +1573,12 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean isPushable() { ++ // Paper start ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index 8ce62148ebaeac9988e7c9d4b2f7ee57f58d883e..8d1e24c2fa844971908ae7ac918a8950026b40a6 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -51,11 +51,17 @@ public final class EntitySelector { + } + + public static Predicate pushableBy(Entity entity) { ++ // Paper start - ignoreClimbing param ++ return pushable(entity, false); ++ } ++ ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end + Team scoreboardteambase = entity.getTeam(); + Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteambase == null ? Team.CollisionRule.ALWAYS : scoreboardteambase.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { +- if (!entity1.canCollideWithCb(entity) || !entity.canCollideWithCb(entity1)) { // CraftBukkit - collidable API ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithCb(entity) || !entity.canCollideWithCb(entity1)) { // CraftBukkit - collidable API // Paper - isCollidable + return false; + } else if (entity.level.isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { + return false; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9724d4222311345a44aa101ec47523a1909fbe8f..57b933afdbb2136ed48170da6945eb2b92edb4db 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -134,7 +134,6 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + +-import co.aikar.timings.MinecraftTimings; // Paper + + public abstract class LivingEntity extends Entity { + +@@ -2956,7 +2955,7 @@ public abstract class LivingEntity extends Entity { + return; + } + // Paper - end don't run getEntities if we're not going to use its result +- List list = this.level.getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this)); ++ List list = this.level.getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, level.paperConfig.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule + + if (!list.isEmpty()) { + // Paper - move up +@@ -3094,9 +3093,16 @@ public abstract class LivingEntity extends Entity { + return !this.removed && this.collides; // CraftBukkit + } + ++ // Paper start + @Override + public boolean isPushable() { +- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit ++ return this.isCollidable(level.paperConfig.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit ++ // Paper end + } + + // CraftBukkit start - collidable API +diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +index e56c575d744e1efe9a7512f337b781dc3715f6be..8e02e3a4464ab4096637fc69c03d083988bb426e 100644 +--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java ++++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +@@ -75,7 +75,7 @@ public class Bat extends AmbientCreature { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +index 918628c0ed8fb32d44c034fddf045f08659c10f8..d6a9d0e94d80f1924cedef913829d15762456537 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +@@ -368,8 +368,8 @@ public class Parrot extends ShoulderRidingEntity implements FlyingAnimal { + } + + @Override +- public boolean isPushable() { +- return super.isPushable(); // CraftBukkit - collidable API ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper ++ return super.isCollidable(ignoreClimbing); // CraftBukkit - collidable API // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index d9bfc754d7544a54ff214b41b4f6c0a6bc66df28..b298bcfb665b1036cd21445cec1518069eb08f06 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -226,7 +226,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return !this.isVehicle(); + } + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 5714aa450ac09788bcf1c2790d4f1581c9a7c28b..a89573670f7dccfd8f0c81fcd95673b6faf3fc10 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -358,7 +358,7 @@ public class ArmorStand extends LivingEntity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 1257a740a4ab79870fe89057782e8ffc6c658c14..9cbde70787d8044f0edeb3d459231dd7fbb79584 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -144,7 +144,7 @@ public abstract class AbstractMinecart extends Entity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index bddc51c656f04f25744ec29cabab31d465cf8bce..4f82c6797fe9bbb1a29420ea15277be50e44808c 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -149,7 +149,7 @@ public class Boat extends Entity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + diff --git a/Remapped-Spigot-Server-Patches/0606-Added-missing-default-perms-for-commands.patch b/Remapped-Spigot-Server-Patches/0606-Added-missing-default-perms-for-commands.patch new file mode 100644 index 000000000..25843d00d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0606-Added-missing-default-perms-for-commands.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 16 Nov 2020 12:01:52 -0800 +Subject: [PATCH] Added missing default perms for commands + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +index d5f4ece060b61de9ca5292d1f2411c709de5ece2..f0a57d225b81a505ff12425155ba838d8fad990c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +@@ -31,6 +31,59 @@ public final class CommandPermissions { + DefaultPermissions.registerPermission(PREFIX + "effect", "Allows the user to add/remove effects on players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "selector", "Allows the use of selectors", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "trigger", "Allows the use of the trigger command", PermissionDefault.TRUE, commands); ++ // Paper start ++ DefaultPermissions.registerPermission(PREFIX + "attribute", "Allows the user to query, add, remove or set an entity attribute", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "advancement", "Allows the user to give, remove, or check player advancements", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "ban", "Allows the user to add players to banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "ban-ip", "Allows the user to add ip address to banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "banlist", "Allows the user to display banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "bossbar", "Allows the user to create and modify bossbars", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "clear", "Allows the user to clear items from player inventory", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "clone", "Allows the user to copy blocks from one place to another", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "data", "Allows the user to get, merge, modify, and remove block entity and entity NBT data", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "datapack", "Allows the user to control loaded data packs", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "debug", "Allows the user to start or stop a debugging session", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "deop", "Allows the user to revoke operator status from a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "difficulty", "Allows the user to set the difficulty level", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "enchant", "Allows the user to enchant a player item", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "execute", "Allows the user to execute another command", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "fill", "Allows the user to fill a region with a specific block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "forceload", "Allows the user to force chunks to be constantly loaded or not", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "function", "Allows the user to run a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "gamerule", "Allows a user to set or query a game rule value", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "locate", "Allows the user to locate the closest structure", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "locatebiome", "Allows the user to locate the closest biome", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "loot", "Allows the user to drop items from an inventory slot onto the ground", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "op", "Allows the user to grant operator status to a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "pardon", "Allows the user to remove entries from the banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "particle", "Allows the user to create particles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "playsound", "Allows the user to play a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "recipe", "Allows the user to give or take recipes", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload loot tables, advancements, and functions from disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "replaceitem", "Allows the user to replace items in inventories", PermissionDefault.OP, commands); // Remove in 1.17 (replaced by /item) ++ DefaultPermissions.registerPermission(PREFIX + "save-all", "Allows the user to save the server to disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "save-off", "Allows the user disable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "save-on", "Allows the user enable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "schedule", "Allows the user to delay the execution of a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "scoreboard", "Allows the user manage scoreboard objectives and players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setblock", "Allows the user to change a block to another block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setidletimeout", "Allows the user to set the time before idle players are kicked", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setworldspawn", "Allows the user to set the world spawn", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spawnpoint", "Allows the user to set the spawn point for a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spectate", "Allows the user to make one player in spectator mode spectate an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spreadplayers", "Allows the user to teleport entities to random locations", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "stopsound", "Allows the user to stop a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "summon", "Allows the user to summon an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "tag", "Allows the user to control entity tags", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "team", "Allows the user to control teams", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "teammsg", "Allows the user to specify the message to send to team", PermissionDefault.TRUE, commands); ++ DefaultPermissions.registerPermission(PREFIX + "tellraw", "Allows the user to display a JSON message to players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "time", "Allows the user to change or query the world's game time", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "title", "Allows the user to manage screen titles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "weather", "Allows the user to set the weather", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "whitelist", "Allows the user to manage the server whitelist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "worldborder", "Allows the user to manage the world border", PermissionDefault.OP, commands); ++ // Paper end + + DefaultPermissions.registerPermission("minecraft.admin.command_feedback", "Receive command broadcasts when sendCommandFeedback is true", PermissionDefault.OP, commands); + diff --git a/Remapped-Spigot-Server-Patches/0607-Add-PlayerShearBlockEvent.patch b/Remapped-Spigot-Server-Patches/0607-Add-PlayerShearBlockEvent.patch new file mode 100644 index 000000000..c9ae36d16 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0607-Add-PlayerShearBlockEvent.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 15:02:48 -0400 +Subject: [PATCH] Add PlayerShearBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 1a01937f5250f3a529932dc2fdd9e1ebd9ae896a..125e646b39ef4a59be3989df16b5625c5504aac1 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.PlayerShearBlockEvent; // Paper - PlayerShearBlockEvent namespace conflicts ++ + import java.util.Iterator; + import java.util.List; + import java.util.Random; +@@ -10,6 +12,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.Tag; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -116,8 +119,19 @@ public class BeehiveBlock extends BaseEntityBlock { + + if (i >= 5) { + if (itemstack.getItem() == Items.SHEARS) { ++ // Paper start - Add PlayerShearBlockEvent ++ PlayerShearBlockEvent event = new PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (hand == InteractionHand.OFF_HAND ? org.bukkit.inventory.EquipmentSlot.OFF_HAND : org.bukkit.inventory.EquipmentSlot.HAND), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.NEUTRAL, 1.0F, 1.0F); +- dropHoneycomb(world, pos); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { ++ dropItem(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); ++ } ++ // Paper end + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); + }); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index baa587e73a71d6324bb7817fa4702a7c3a2db726..5f1853c3f22661809437bdb49f002482da7195f2 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -262,13 +262,13 @@ public class Block extends BlockBehaviour implements ItemLike { + + } + +- public static void popResource(Level world, BlockPos pos, ItemStack stack) { +- if (!world.isClientSide && !stack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { ++ public static void popResource(Level world, BlockPos pos, ItemStack stack) { dropItem(world, pos, stack); } public static void dropItem(Level world, BlockPos blockposition, ItemStack itemstack) { // Paper - OBFHELPER ++ if (!world.isClientSide && !itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { + float f = 0.5F; + double d0 = (double) (world.random.nextFloat() * 0.5F) + 0.25D; + double d1 = (double) (world.random.nextFloat() * 0.5F) + 0.25D; + double d2 = (double) (world.random.nextFloat() * 0.5F) + 0.25D; +- ItemEntity entityitem = new ItemEntity(world, (double) pos.getX() + d0, (double) pos.getY() + d1, (double) pos.getZ() + d2, stack); ++ ItemEntity entityitem = new ItemEntity(world, (double) blockposition.getX() + d0, (double) blockposition.getY() + d1, (double) blockposition.getZ() + d2, itemstack); + + entityitem.setDefaultPickUpDelay(); + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +index fe528f7dee693982285ce035abd9929cb4e0cbdf..7cac13ad06ee7acfc3bdcbf79318dbfbd2a31e02 100644 +--- a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block; + + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.world.InteractionHand; +@@ -14,6 +15,7 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.BlockHitResult; ++import io.papermc.paper.event.block.PlayerShearBlockEvent; // Paper - PlayerShearBlockEvent namespace conflicts + + public class PumpkinBlock extends StemGrownBlock { + +@@ -27,15 +29,26 @@ public class PumpkinBlock extends StemGrownBlock { + + if (itemstack.getItem() == Items.SHEARS) { + if (!world.isClientSide) { ++ // Paper start - Add PlayerShearBlockEvent ++ PlayerShearBlockEvent event = new PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (hand == InteractionHand.OFF_HAND ? org.bukkit.inventory.EquipmentSlot.OFF_HAND : org.bukkit.inventory.EquipmentSlot.HAND), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + Direction enumdirection = hit.getDirection(); + Direction enumdirection1 = enumdirection.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : enumdirection; + + world.playSound((Player) null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlock(pos, (BlockState) Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, enumdirection1), 11); +- ItemEntity entityitem = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) enumdirection1.getStepX() * 0.65D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D + (double) enumdirection1.getStepZ() * 0.65D, new ItemStack(Items.PUMPKIN_SEEDS, 4)); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { ++ ItemEntity entityitem = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) enumdirection1.getStepX() * 0.65D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D + (double) enumdirection1.getStepZ() * 0.65D, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); ++ // Paper end + + entityitem.setDeltaMovement(0.05D * (double) enumdirection1.getStepX() + world.random.nextDouble() * 0.02D, 0.05D, 0.05D * (double) enumdirection1.getStepZ() + world.random.nextDouble() * 0.02D); + world.addFreshEntity(entityitem); ++ } // Paper - Add PlayerShearBlockEvent + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); + }); diff --git a/Remapped-Spigot-Server-Patches/0608-Add-warning-for-servers-not-running-on-Java-16.patch b/Remapped-Spigot-Server-Patches/0608-Add-warning-for-servers-not-running-on-Java-16.patch new file mode 100644 index 000000000..ce4786d7f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0608-Add-warning-for-servers-not-running-on-Java-16.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kyle Wood +Date: Wed, 2 Dec 2020 21:58:45 -0800 +Subject: [PATCH] Add warning for servers not running on Java 16 + + +diff --git a/src/main/java/io/papermc/paper/util/PaperJvmChecker.java b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fdf3ff8894e5e202229d1be52fe3c92ea039ef15 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.util; ++ ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++public class PaperJvmChecker { ++ ++ private static int getJvmVersion() { ++ String javaVersion = System.getProperty("java.version"); ++ final Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion); ++ if (!matcher.find()) { ++ LogManager.getLogger().warn("Failed to determine Java version; Could not parse: {}", javaVersion); ++ return -1; ++ } ++ ++ final String version = matcher.group(1); ++ try { ++ return Integer.parseInt(version); ++ } catch (final NumberFormatException e) { ++ LogManager.getLogger().warn("Failed to determine Java version; Could not parse {} from {}", version, javaVersion, e); ++ return -1; ++ } ++ } ++ ++ public static void checkJvm() { ++ if (getJvmVersion() < 16) { ++ final Logger logger = LogManager.getLogger(); ++ logger.warn("************************************************************"); ++ logger.warn("* WARNING - YOU ARE RUNNING AN OUTDATED VERSION OF JAVA."); ++ logger.warn("* PAPER WILL STOP BEING COMPATIBLE WITH THIS VERSION OF"); ++ logger.warn("* JAVA WHEN MINECRAFT 1.17 IS RELEASED."); ++ logger.warn("*"); ++ logger.warn("* Please update the version of Java you use to run Paper"); ++ logger.warn("* to at least Java 16. When Paper for Minecraft 1.17 is"); ++ logger.warn("* released support for versions of Java before 16 will"); ++ logger.warn("* be dropped."); ++ logger.warn("*"); ++ logger.warn("* Current Java version: {}", System.getProperty("java.version")); ++ logger.warn("*"); ++ logger.warn("* Check this forum post for more information: "); ++ logger.warn("* https://papermc.io/java16"); ++ logger.warn("************************************************************"); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 954dce690852da87a37e7797c6f9f549242e511a..c83f2636ae93d92381e019d5b13ac82c5a1d30bf 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -180,6 +180,7 @@ import org.bukkit.event.server.ServerLoadEvent; + + import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot ++import io.papermc.paper.util.PaperJvmChecker; // Paper + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements SnooperPopulator, CommandSource, AutoCloseable { + +@@ -1075,6 +1076,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 2 Dec 2020 20:17:54 -0800 +Subject: [PATCH] Set spigots verbose world setting to false by def + + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 6b015c1f26facb4e82d75b252164dec05731ca6c..094a934c168d232b0550c3efe722f2ebfbdf8e24 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -20,7 +20,7 @@ public class SpigotWorldConfig + + public void init() + { +- this.verbose = getBoolean( "verbose", true ); ++ this.verbose = getBoolean( "verbose", false ); // Paper + + log( "-------- World Settings For [" + worldName + "] --------" ); + SpigotConfig.readConfig( SpigotWorldConfig.class, this ); diff --git a/Remapped-Spigot-Server-Patches/0610-Fix-curing-zombie-villager-discount-exploit.patch b/Remapped-Spigot-Server-Patches/0610-Fix-curing-zombie-villager-discount-exploit.patch new file mode 100644 index 000000000..852cffc98 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0610-Fix-curing-zombie-villager-discount-exploit.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:14:20 -0600 +Subject: [PATCH] Fix curing zombie villager discount exploit + +This fixes the exploit used to gain absurd trading discounts with infecting +and curing a villager on repeat by simply resetting the relevant part of +the reputation when it is cured. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 7d3207a9af8360ddad228281d6aa65e1a0d24157..a3b3e3e04b7a5e3a351992e06870cc91fbd8adc8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -717,4 +717,9 @@ public class PaperWorldConfig { + private void fixClimbingBypassingCrammingRule() { + fixClimbingBypassingCrammingRule = getBoolean("fix-climbing-bypassing-cramming-rule", fixClimbingBypassingCrammingRule); + } ++ ++ public boolean fixCuringZombieVillagerDiscountExploit = true; ++ private void fixCuringExploit() { ++ fixCuringZombieVillagerDiscountExploit = getBoolean("game-mechanics.fix-curing-zombie-villager-discount-exploit", fixCuringZombieVillagerDiscountExploit); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index 57832c392910d22aa81ac2b4816d043dd7ac867a..9a68201bab4fcbad69c85e2469a103634b65d7b3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -223,6 +223,7 @@ public class GossipContainer { + + } + ++ public final void removeReputationForType(GossipType reputationType) { this.remove(reputationType); } // Paper - OBFHELPER + public void remove(GossipType gossipType) { + this.entries.removeInt(gossipType); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 415fa3591add1f1ab22dd5866e110dbfccd0ec93..7817071b1964b962c8f4017d5bb39d74ca0ca3e4 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -1013,6 +1013,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void onReputationEventFrom(ReputationEventType interaction, Entity entity) { + if (interaction == ReputationEventType.ZOMBIE_VILLAGER_CURED) { ++ // Paper start - fix MC-181190 ++ if (level.paperConfig.fixCuringZombieVillagerDiscountExploit) { ++ final GossipContainer.EntityGossips playerReputation = this.getReputation().getReputations().get(entity.getUUID()); ++ if (playerReputation != null) { ++ playerReputation.removeReputationForType(GossipType.MAJOR_POSITIVE); ++ playerReputation.removeReputationForType(GossipType.MINOR_POSITIVE); ++ } ++ } ++ // Paper end + this.gossips.add(entity.getUUID(), GossipType.MAJOR_POSITIVE, 20); + this.gossips.add(entity.getUUID(), GossipType.MINOR_POSITIVE, 25); + } else if (interaction == ReputationEventType.TRADE) { diff --git a/Remapped-Spigot-Server-Patches/0611-Limit-recipe-packets.patch b/Remapped-Spigot-Server-Patches/0611-Limit-recipe-packets.patch new file mode 100644 index 000000000..dc1bd9567 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0611-Limit-recipe-packets.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 12 Dec 2020 23:45:28 +0000 +Subject: [PATCH] Limit recipe packets + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7d50aded88f5b7dfebaea1aebc86231f7b5c4e25..652d87fc5d566dba8018c81676329f0e0bca471b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -334,6 +334,13 @@ public class PaperConfig { + tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); + } + ++ public static int autoRecipeIncrement = 1; ++ public static int autoRecipeLimit = 20; ++ private static void autoRecipieLimiters() { ++ autoRecipeIncrement = getInt("settings.spam-limiter.recipe-spam-increment", autoRecipeIncrement); ++ autoRecipeLimit = getInt("settings.spam-limiter.recipe-spam-limit", autoRecipeLimit); ++ } ++ + public static boolean velocitySupport; + public static boolean velocityOnlineMode; + public static byte[] velocitySecretKey; +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ccfe5a1ec1e9895f7462b7c676fee80903502a88..e50e42cce60c725cdd981d8927e379c5760d9200 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.PaperConfig; + import com.google.common.collect.Lists; + import com.google.common.primitives.Doubles; + import com.google.common.primitives.Floats; +@@ -174,6 +175,7 @@ import io.papermc.paper.adventure.ChatProcessor; // Paper + import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.util.concurrent.ExecutionException; + import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; ++import org.bukkit.Bukkit; // Paper + import org.bukkit.Location; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.event.CraftEventFactory; +@@ -232,6 +234,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + private volatile int chatSpamTickCount; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(ServerGamePacketListenerImpl.class, "chatThrottle"); + private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits ++ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit + // CraftBukkit end + private int dropSpamTickCount; + private final Int2ShortMap expectedAcks = new Int2ShortOpenHashMap(); +@@ -380,6 +383,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit start + for (int spam; (spam = this.chatSpamTickCount) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ; + if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable ++ if (recipeSpamPackets.get() > 0) recipeSpamPackets.getAndDecrement(); // Paper + /* Use thread-safe field access instead + if (this.chatThrottle > 0) { + --this.chatThrottle; +@@ -2786,6 +2790,14 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ // Paper start ++ if (!Bukkit.isPrimaryThread()) { ++ if (recipeSpamPackets.addAndGet(PaperConfig.autoRecipeIncrement) > PaperConfig.autoRecipeLimit) { ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ return; ++ } ++ } ++ // Paper end + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu.isSynched(this.player) && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/Remapped-Spigot-Server-Patches/0612-Fix-CraftSound-backwards-compatibility.patch b/Remapped-Spigot-Server-Patches/0612-Fix-CraftSound-backwards-compatibility.patch new file mode 100644 index 000000000..60e8ef1e6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0612-Fix-CraftSound-backwards-compatibility.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 17 Dec 2020 15:25:49 -0600 +Subject: [PATCH] Fix CraftSound backwards compatibility + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftSound.java b/src/main/java/org/bukkit/craftbukkit/CraftSound.java +index 266563e72b563fd9db85f17bca710bbe45e8a22d..b2667c5f0794d521766203fea3299f12e21f5c76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftSound.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftSound.java +@@ -26,4 +26,10 @@ public class CraftSound { + public static Sound getBukkit(SoundEvent soundEffect) { + return Registry.SOUNDS.get(CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.SOUND_EVENT.getKey(soundEffect))); + } ++ ++ // Paper start ++ public static String getSound(Sound sound) { ++ return sound.getKey().getKey(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0613-MC-4-Fix-item-position-desync.patch b/Remapped-Spigot-Server-Patches/0613-MC-4-Fix-item-position-desync.patch new file mode 100644 index 000000000..3d3624adc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0613-MC-4-Fix-item-position-desync.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:24:52 -0600 +Subject: [PATCH] MC-4: Fix item position desync + +This fixes item position desync (MC-4) by running the item coordinates +through the encode/decode methods of the packet that causes the precision +loss, which forces the server to lose the same precision as the client +keeping them in sync. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 652d87fc5d566dba8018c81676329f0e0bca471b..c56e7fb18f9a56c8025eb70a524f028b5942da37 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -474,4 +474,9 @@ public class PaperConfig { + private static void trackPluginScoreboards() { + trackPluginScoreboards = getBoolean("settings.track-plugin-scoreboards", false); + } ++ ++ public static boolean fixEntityPositionDesync = true; ++ private static void fixEntityPositionDesync() { ++ fixEntityPositionDesync = getBoolean("settings.fix-entity-position-desync", fixEntityPositionDesync); ++ } + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundMoveEntityPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundMoveEntityPacket.java +index cb10c87728b5f9062c4bdd1fe5e4b2c7a558f323..6b97d60d923e772c7284e674bc3f2e9a5a0ddead 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundMoveEntityPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundMoveEntityPacket.java +@@ -19,11 +19,11 @@ public class ClientboundMoveEntityPacket implements Packet { ++ private static final float[] SIN = (float[]) Util.make((new float[65536]), (afloat) -> { // Paper - decompile error + for (int i = 0; i < afloat.length; ++i) { + afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D); + } +@@ -49,6 +49,7 @@ public class Mth { + return d0 < (double) i ? i - 1 : i; + } + ++ public static long floorLong(double d0) { return lfloor(d0); } // Paper - OBFHELPER + public static long lfloor(double d0) { + long i = (long) d0; + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 9311f9f411d09d4460f0be8235957fab9e195b7a..7476ae301fb4ee503944d39022cb25ccb19f1232 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -549,4 +549,16 @@ public class ItemEntity extends Entity { + public Packet getAddEntityPacket() { + return new ClientboundAddEntityPacket(this); + } ++ ++ // Paper start - fix MC-4 ++ public void setPosRaw(double x, double y, double z) { ++ if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { ++ // encode/decode from PacketPlayOutEntity ++ x = Mth.floorLong(x * 4096.0D) * (1 / 4096.0D); ++ y = Mth.floorLong(y * 4096.0D) * (1 / 4096.0D); ++ z = Mth.floorLong(z * 4096.0D) * (1 / 4096.0D); ++ } ++ super.setPosRaw(x, y, z); ++ } ++ // Paper end - fix MC-4 + } diff --git a/Remapped-Spigot-Server-Patches/0614-Player-Chunk-Load-Unload-Events.patch b/Remapped-Spigot-Server-Patches/0614-Player-Chunk-Load-Unload-Events.patch new file mode 100644 index 000000000..d7abc567f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0614-Player-Chunk-Load-Unload-Events.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Mon, 5 Oct 2020 21:25:16 +0200 +Subject: [PATCH] Player Chunk Load/Unload Events + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 1b5f24920c46cd238a79f5a2857d26fa1c12b983..ff831ca0cbc0cabbf78178c609ccf70d78da7980 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -136,6 +136,8 @@ import net.minecraft.world.level.dimension.DimensionType; + import net.minecraft.world.level.portal.PortalInfo; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++import io.papermc.paper.event.packet.PlayerChunkLoadEvent; // Paper ++import io.papermc.paper.event.packet.PlayerChunkUnloadEvent; // Paper + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import net.minecraft.world.Container; +@@ -2095,11 +2097,21 @@ public class ServerPlayer extends Player implements ContainerListener { + public void trackChunk(ChunkPos chunkcoordintpair, Packet packet, Packet packet1) { + this.connection.send(packet1); + this.connection.send(packet); ++ // Paper start ++ if(PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new PlayerChunkLoadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkcoordintpair.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + public void untrackChunk(ChunkPos chunkcoordintpair) { + if (this.isAlive()) { + this.connection.send(new ClientboundForgetLevelChunkPacket(chunkcoordintpair.x, chunkcoordintpair.z)); ++ // Paper start ++ if(PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new PlayerChunkUnloadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkcoordintpair.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + } diff --git a/Remapped-Spigot-Server-Patches/0615-Optimize-Dynamic-get-Missing-Keys.patch b/Remapped-Spigot-Server-Patches/0615-Optimize-Dynamic-get-Missing-Keys.patch new file mode 100644 index 000000000..5a4efe035 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0615-Optimize-Dynamic-get-Missing-Keys.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 21 Dec 2020 11:01:42 -0500 +Subject: [PATCH] Optimize Dynamic#get Missing Keys + +get was calling toString() on every NBT object that was ever asked for an optional +key from the object to build a string for the error text. + +When done on large NBT objects, this was using a ton of computation time building the +JSON representation of the NBT object. + +Now we will just skip the value when 99.9999% of the time the text is never even printed. + +diff --git a/src/main/java/com/mojang/serialization/Dynamic.java b/src/main/java/com/mojang/serialization/Dynamic.java +index a75d3db046dc985a03b4b870c91f41de1bd66bad..044facc9de9e8e582d7953d681c0c051578979c3 100644 +--- a/src/main/java/com/mojang/serialization/Dynamic.java ++++ b/src/main/java/com/mojang/serialization/Dynamic.java +@@ -17,6 +17,7 @@ import java.util.stream.Stream; + + @SuppressWarnings("unused") + public class Dynamic extends DynamicLike { ++ private static final boolean DEBUG_MISSING_KEYS = Boolean.getBoolean("Paper.debugDynamicMissingKeys"); // Paper + private final T value; + + public Dynamic(final DynamicOps ops) { +@@ -113,7 +114,7 @@ public class Dynamic extends DynamicLike { + return new OptionalDynamic<>(ops, ops.getMap(value).flatMap(m -> { + final T value = m.get(key); + if (value == null) { +- return DataResult.error("key missing: " + key + " in " + this.value); ++ return DataResult.error(DEBUG_MISSING_KEYS ? "key missing: " + key + " in " + this.value : "key missing: " + key); // Paper + } + return DataResult.success(new Dynamic<>(ops, value)); + })); diff --git a/Remapped-Spigot-Server-Patches/0616-Expose-LivingEntity-hurt-direction.patch b/Remapped-Spigot-Server-Patches/0616-Expose-LivingEntity-hurt-direction.patch new file mode 100644 index 000000000..d17950689 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0616-Expose-LivingEntity-hurt-direction.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 13 Dec 2020 05:32:05 +0200 +Subject: [PATCH] Expose LivingEntity hurt direction + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 57b933afdbb2136ed48170da6945eb2b92edb4db..02ddb84c563b3149c4f1b0e24899ce8a21ad61bb 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -160,7 +160,7 @@ public abstract class LivingEntity extends Entity { + public int removeStingerTime; + public int hurtTime; + public int hurtDuration; +- public float hurtDir; ++ public float hurtDir; public final float getHurtDirection() { return hurtDir; } public final void setHurtDirection(float hurtDirection) { this.hurtDir = hurtDirection; } // Paper - OBFHELPER + public int deathTime; + public float oAttackAnim; + public float attackAnim; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 170bb1124ee396a85dd64baed8110e39823ad849..a8d21382d5859edfd12e01a48924ce780790b4b7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -818,5 +818,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { + getHandle().take(((CraftItem) item).getHandle(), quantity); + } ++ ++ @Override ++ public float getHurtDirection() { ++ return getHandle().getHurtDirection(); ++ } ++ ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ getHandle().setHurtDirection(hurtDirection); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0617-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/Remapped-Spigot-Server-Patches/0617-Add-OBSTRUCTED-reason-to-BedEnterResult.patch new file mode 100644 index 000000000..d2162d041 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0617-Add-OBSTRUCTED-reason-to-BedEnterResult.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:43:39 -0800 +Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 64cfa14aa4e32430a6970fd4f3654a56146ba807..34c7b1213b3f83ff1a1f2d606a9c25e57fea8ef3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -259,6 +259,10 @@ public class CraftEventFactory { + return BedEnterResult.TOO_FAR_AWAY; + case NOT_SAFE: + return BedEnterResult.NOT_SAFE; ++ // Paper start ++ case OBSTRUCTED: ++ return BedEnterResult.OBSTRUCTED; ++ // Paper end + default: + return BedEnterResult.OTHER_PROBLEM; + } diff --git a/Remapped-Spigot-Server-Patches/0618-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch b/Remapped-Spigot-Server-Patches/0618-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch new file mode 100644 index 000000000..f57f5ee83 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0618-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 27 Dec 2020 11:31:06 +0000 +Subject: [PATCH] Do not crash from invalid ingredient lists in + VillagerAcquireTradeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 407a68edf6408400f1a6c5bb1a6cbbfae08ac2cd..335000713279b7964c5172937483678c671bfed1 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -270,7 +270,11 @@ public abstract class AbstractVillager extends AgableMob implements Npc, Merchan + Bukkit.getPluginManager().callEvent(event); + } + if (!event.isCancelled()) { +- recipeList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ // Paper start ++ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); ++ if (craftMerchantRecipe.getIngredients().isEmpty()) return; ++ recipeList.add(craftMerchantRecipe.toMinecraft()); ++ // Paper end + } + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0619-added-PlayerTradeEvent.patch b/Remapped-Spigot-Server-Patches/0619-added-PlayerTradeEvent.patch new file mode 100644 index 000000000..358936535 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0619-added-PlayerTradeEvent.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jul 2020 16:12:10 -0700 +Subject: [PATCH] added PlayerTradeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 40ab66f888f30a5506e3aa96a4b32485452e8978..87dd6c012bf1ca6a1e8df44dc0957c4c67d02adc 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -91,7 +91,7 @@ import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; + public abstract class Mob extends LivingEntity { + + private static final EntityDataAccessor DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE); +- public int ambientSoundTime; ++ public int ambientSoundTime;public void setAmbientSoundTime(int time) { this.ambientSoundTime = time; } // Paper - OBFHELPER + protected int xpReward; + protected LookControl lookControl; + protected MoveControl moveControl; +@@ -293,6 +293,7 @@ public abstract class Mob extends LivingEntity { + this.entityData.define(Mob.DATA_MOB_FLAGS_ID, (byte) 0); + } + ++ public int getAmbientSoundInterval() { return getAmbientSoundInterval(); } // Paper - OBFHELPER + public int getAmbientSoundInterval() { + return 80; + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 335000713279b7964c5172937483678c671bfed1..d31b773f8aff09c9f214662aa3aa0c904119e12c 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -37,6 +37,9 @@ import org.bukkit.craftbukkit.inventory.CraftMerchant; + import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; + import org.bukkit.event.entity.VillagerAcquireTradeEvent; + // CraftBukkit end ++// Paper start ++import io.papermc.paper.event.player.PlayerTradeEvent; ++// Paper end + + public abstract class AbstractVillager extends AgableMob implements Npc, Merchant { + +@@ -133,16 +136,27 @@ public abstract class AbstractVillager extends AgableMob implements Npc, Merchan + + @Override + public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); +- this.ambientSoundTime = -this.getAmbientSoundInterval(); +- this.rewardTradeXp(offer); ++ // Paper - moved down ++ // Paper start + if (this.tradingPlayer instanceof ServerPlayer) { +- CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); ++ PlayerTradeEvent event = new PlayerTradeEvent(((ServerPlayer) this.tradingPlayer).getBukkitEntity(), (org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), offer.asBukkit(), true, true); ++ event.callEvent(); ++ if (!event.isCancelled()) { ++ MerchantOffer recipe = CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ if (event.willIncreaseTradeUses()) recipe.increaseUses(); ++ this.setAmbientSoundTime(-getAmbientSoundInterval()); ++ if (event.isRewardingExp()) this.rewardTradeXp(recipe); ++ CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, recipe.getResult()); ++ } ++ } else { ++ offer.increaseUses(); ++ this.setAmbientSoundTime(-getAmbientSoundInterval()); ++ this.rewardTradeXp(offer); + } +- ++ // Paper end + } + +- protected abstract void rewardTradeXp(MerchantOffer offer); ++ protected abstract void rewardTradeXp(MerchantOffer offer); public void rewardTradeXp(MerchantOffer merchantrecipe) { this.rewardTradeXp(merchantrecipe); } // Paper - OBFHELPER + + @Override + public boolean showProgressBar() { diff --git a/Remapped-Spigot-Server-Patches/0620-Implement-TargetHitEvent.patch b/Remapped-Spigot-Server-Patches/0620-Implement-TargetHitEvent.patch new file mode 100644 index 000000000..bc8c6c48b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0620-Implement-TargetHitEvent.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 25 Nov 2020 23:20:44 -0800 +Subject: [PATCH] Implement TargetHitEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/TargetBlock.java b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +index bbaf90b30b07c97321541b8fe15d47975a34161d..a7215c9b9f0f191bbfe95cb1185c99a8d21ff785 100644 +--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.TargetHitEvent; // Paper - Need to import because 'io' class exists in nms + import java.util.Random; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.core.BlockPos; +@@ -34,13 +35,17 @@ public class TargetBlock extends Block { + @Override + public void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { + int i = updateRedstoneOutput((LevelAccessor) world, state, hit, (Entity) projectile); +- Entity entity = projectile.getOwner(); ++ // Paper start ++ } ++ private static void awardTargetHitCriteria(Projectile iprojectile, BlockHitResult movingobjectpositionblock, int i) { ++ // Paper end ++ Entity entity = iprojectile.getOwner(); + + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + + entityplayer.awardStat(Stats.TARGET_HIT); +- CriteriaTriggers.TARGET_BLOCK_HIT.trigger(entityplayer, projectile, hit.getLocation(), i); ++ CriteriaTriggers.TARGET_BLOCK_HIT.trigger(entityplayer, iprojectile, movingobjectpositionblock.getLocation(), i); + } + + } +@@ -49,6 +54,20 @@ public class TargetBlock extends Block { + int i = getRedstoneStrength(movingobjectpositionblock, movingobjectpositionblock.getLocation()); + int j = entity instanceof AbstractArrow ? 20 : 8; + ++ // Paper start ++ if (entity instanceof Projectile) { ++ final Projectile projectile = (Projectile) entity; ++ final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, movingobjectpositionblock.getBlockPos()); ++ final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(movingobjectpositionblock.getDirection()); ++ final TargetHitEvent targetHitEvent = new TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i); ++ if (targetHitEvent.callEvent()) { ++ i = targetHitEvent.getSignalStrength(); ++ awardTargetHitCriteria(projectile, movingobjectpositionblock, i); ++ } else { ++ return i; ++ } ++ } ++ // Paper end + if (!world.getBlockTicks().a(movingobjectpositionblock.getBlockPos(), state.getBlock())) { + setOutputPower(world, state, i, movingobjectpositionblock.getBlockPos(), j); + } diff --git a/Remapped-Spigot-Server-Patches/0621-Additional-Block-Material-API-s.patch b/Remapped-Spigot-Server-Patches/0621-Additional-Block-Material-API-s.patch new file mode 100644 index 000000000..6aa789f4d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0621-Additional-Block-Material-API-s.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 30 Dec 2020 19:43:01 -0500 +Subject: [PATCH] Additional Block Material API's + +Faster version for isSolid() that utilizes NMS's state for isSolid instead of the slower +process to do this in the Bukkit API + +Adds API for buildable, replaceable, burnable too. + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 9defb202761296a825d035e27ddc51e17a311647..376b0497c28a35d7ea615397c87b2558b95c596a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -618,6 +618,25 @@ public class CraftBlock implements Block { + return getNMS().getMaterial().isLiquid(); + } + ++ // Paper start ++ @Override ++ public boolean isBuildable() { ++ return getNMS().getMaterial().isBuildable(); ++ } ++ @Override ++ public boolean isBurnable() { ++ return getNMS().getMaterial().isBurnable(); ++ } ++ @Override ++ public boolean isReplaceable() { ++ return getNMS().getMaterial().isReplaceable(); ++ } ++ @Override ++ public boolean isSolid() { ++ return getNMS().getMaterial().blocksMotion(); ++ } ++ // Paper end ++ + @Override + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(getNMS().getPistonPushReaction().ordinal()); diff --git a/Remapped-Spigot-Server-Patches/0622-Fix-harming-potion-dupe.patch b/Remapped-Spigot-Server-Patches/0622-Fix-harming-potion-dupe.patch new file mode 100644 index 000000000..e5c11d28d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0622-Fix-harming-potion-dupe.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> +Date: Thu, 23 Jul 2020 14:25:07 -0700 +Subject: [PATCH] Fix harming potion dupe + +EntityLiving#applyInstantEffect() immediately kills the player and drops their inventory. +Before this patch, instant effects would be applied before the potion ItemStack is removed and replaced with a glass bottle. This caused the potion ItemStack to be dropped before it was supposed to be removed from the inventory. It also caused the glass bottle to be put into a dead player's inventory. +This patch makes it so that instant effects are applied after the potion ItemStack is removed, and the glass bottle is only put into the player's inventory if the player is not dead. Otherwise, the glass bottle is dropped on the ground. + +diff --git a/src/main/java/net/minecraft/world/item/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java +index 36ccec8ad2f605d5bceae476e26c2fbfbdae39d8..ba2bc044b259b0a5ba590d4ae6fd36e5c415ddff 100644 +--- a/src/main/java/net/minecraft/world/item/PotionItem.java ++++ b/src/main/java/net/minecraft/world/item/PotionItem.java +@@ -15,6 +15,7 @@ import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.alchemy.Potion; + import net.minecraft.world.item.alchemy.PotionUtils; + import net.minecraft.world.item.alchemy.Potions; ++import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; + + public class PotionItem extends Item { +@@ -36,6 +37,7 @@ public class PotionItem extends Item { + CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) entityhuman, stack); + } + ++ List instantLater = new java.util.ArrayList<>(); // Paper - Fix harming potion dupe + if (!world.isClientSide) { + List list = PotionUtils.getMobEffects(stack); + Iterator iterator = list.iterator(); +@@ -44,7 +46,7 @@ public class PotionItem extends Item { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + + if (mobeffect.getMobEffect().isInstant()) { +- mobeffect.getMobEffect().applyInstantEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); ++ instantLater.add(mobeffect); // Paper - Fix harming potion dupe + } else { + user.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit + } +@@ -58,7 +60,20 @@ public class PotionItem extends Item { + } + } + ++ // Paper start - Fix harming potion dupe ++ for (MobEffectInstance mobeffect : instantLater) { ++ mobeffect.getMobEffect().applyInstantEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); ++ } ++ // Paper end ++ + if (entityhuman == null || !entityhuman.abilities.instabuild) { ++ // Paper start - Fix harming potion dupe ++ if (user.getHealth() <= 0 && !user.level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY)) { ++ user.dropItem(new ItemStack(Items.GLASS_BOTTLE), 0); ++ return ItemStack.NULL_ITEM; ++ } ++ // Paper end ++ + if (stack.isEmpty()) { + return new ItemStack(Items.GLASS_BOTTLE); + } diff --git a/Remapped-Spigot-Server-Patches/0623-Implement-API-to-get-Material-from-Boats-and-Minecar.patch b/Remapped-Spigot-Server-Patches/0623-Implement-API-to-get-Material-from-Boats-and-Minecar.patch new file mode 100644 index 000000000..f956ace9f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0623-Implement-API-to-get-Material-from-Boats-and-Minecar.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Thu, 31 Dec 2020 12:48:19 +1000 +Subject: [PATCH] Implement API to get Material from Boats and Minecarts + + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 4f82c6797fe9bbb1a29420ea15277be50e44808c..6a9c18540886979b2212bf7917a21753c9a9db3c 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -259,6 +259,7 @@ public class Boat extends Entity { + + } + ++ public final Item getBoatItem() { return this.getDropItem(); } // Paper - OBFHELPER + public Item getDropItem() { + switch (this.getBoatType()) { + case OAK: +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index de8e9e8d18fb9cc6f49d98ab0c57faffec61e5b5..5928ab97b91062963e5cca0a8ec50f2bc3a7ff96 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -1,7 +1,9 @@ + package org.bukkit.craftbukkit.entity; + ++import org.bukkit.Material; // Paper + import org.bukkit.TreeSpecies; + import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; // Paper + import org.bukkit.entity.Boat; + import org.bukkit.entity.EntityType; + +@@ -65,6 +67,13 @@ public class CraftBoat extends CraftVehicle implements Boat { + getHandle().landBoats = workOnLand; + } + ++ // Paper start ++ @Override ++ public Material getBoatMaterial() { ++ return CraftMagicNumbers.getMaterial(getHandle().getBoatItem()); ++ } ++ // Paper end ++ + @Override + public net.minecraft.world.entity.vehicle.Boat getHandle() { + return (net.minecraft.world.entity.vehicle.Boat) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index 55df92a4661e13a9085f325db0572a265a89948c..fb26b6125ad4090d87b2326add94ffaded82c8ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +@@ -1,8 +1,10 @@ + package org.bukkit.craftbukkit.entity; + + import net.minecraft.world.entity.vehicle.AbstractMinecart; ++import net.minecraft.world.item.Items; // Paper + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.Material; // Paper + import org.bukkit.block.data.BlockData; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.block.data.CraftBlockData; +@@ -68,6 +70,38 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + getHandle().setDerailedVelocityMod(derailed); + } + ++ // Paper start ++ @Override ++ public Material getMinecartMaterial() { ++ net.minecraft.world.item.Item minecartItem; ++ switch (getHandle().getMinecartType()) { ++ case CHEST: ++ minecartItem = Items.CHEST_MINECART; ++ break; ++ case FURNACE: ++ minecartItem = Items.FURNACE_MINECART; ++ break; ++ case TNT: ++ minecartItem = Items.TNT_MINECART; ++ break; ++ case HOPPER: ++ minecartItem = Items.HOPPER_MINECART; ++ break; ++ case COMMAND_BLOCK: ++ minecartItem = Items.COMMAND_BLOCK_MINECART; ++ break; ++ case RIDEABLE: ++ case SPAWNER: ++ minecartItem = Items.MINECART; ++ break; ++ default: ++ throw new IllegalStateException("Unexpected value: " + getHandle().getMinecartType()); ++ } ++ ++ return CraftMagicNumbers.getMaterial(minecartItem); ++ } ++ // Paper end ++ + @Override + public AbstractMinecart getHandle() { + return (AbstractMinecart) entity; diff --git a/Remapped-Spigot-Server-Patches/0624-Optimized-tick-ready-check.patch b/Remapped-Spigot-Server-Patches/0624-Optimized-tick-ready-check.patch new file mode 100644 index 000000000..3dd32bb45 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0624-Optimized-tick-ready-check.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas +Date: Sun, 27 Dec 2020 17:19:51 +0100 +Subject: [PATCH] Optimized tick ready check + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 066d5f7ee93351bff67c0d39ee9d940ac51515d8..b89cefc8890774dbc64fd6bddeb038d2ee36d485 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -854,13 +854,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + if (!tileentity.isRemoved() && tileentity.hasLevel()) { + BlockPos blockposition = tileentity.getBlockPos(); + +- if (this.getChunkSource().isTickingChunk(blockposition) && this.getWorldBorder().isWithinBounds(blockposition)) { ++ LevelChunk chunk; ChunkHolder playerChunk; if ((chunk = tileentity.getCurrentChunk()) != null && (playerChunk = chunk.playerChunk) != null && playerChunk.isTickingReady() && this.getWorldBorder().isInBounds(blockposition)) { // Paper - optimized tick ready check by inlining ChunkProviderServer.a(BlockPosition). Chunk lookup is no longer required and we can use the PlayerChunk directly available through the tile entity + try { + gameprofilerfiller.push(() -> { + return String.valueOf(BlockEntityType.getKey(tileentity.getType())); + }); + tileentity.tickTimer.startTiming(); // Spigot +- if (tileentity.getType().isValid(this.getBlockState(blockposition).getBlock())) { ++ if (tileentity.getType().isValid(chunk.getBlockState(blockposition).getBlock())) { // Paper - reuse the chunk from above, do not look it up again + ((TickableBlockEntity) tileentity).tick(); + } else { + tileentity.logInvalidState(); +@@ -893,9 +893,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.tickableBlockEntities.remove(tileTickPosition--); + // Spigot end + //this.tileEntityList.remove(tileentity); // Paper - remove unused list +- if (this.hasChunkAt(tileentity.getBlockPos())) { +- this.getChunkAt(tileentity.getBlockPos()).removeBlockEntity(tileentity.getBlockPos()); ++ // Paper - prevent double chunk lookups ++ LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getBlockPos())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again ++ chunk.removeBlockEntity(tileentity.getBlockPos()); + } ++ // Paper end + } + } + +@@ -914,8 +916,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // CraftBukkit end */ + +- if (this.hasChunkAt(tileentity1.getBlockPos())) { +- LevelChunk chunk = this.getChunkAt(tileentity1.getBlockPos()); ++ LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity1.getBlockPos())) != null) { // Paper - inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again ++ // Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition()); // Paper - already computed above + BlockState iblockdata = chunk.getBlockState(tileentity1.getBlockPos()); + + chunk.setBlockEntity(tileentity1.getBlockPos(), tileentity1); diff --git a/Remapped-Spigot-Server-Patches/0625-Cache-burn-durations.patch b/Remapped-Spigot-Server-Patches/0625-Cache-burn-durations.patch new file mode 100644 index 000000000..4163e2cc8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0625-Cache-burn-durations.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas +Date: Sun, 27 Dec 2020 16:47:00 +0100 +Subject: [PATCH] Cache burn durations + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 4126a36dbc7750108a883f0be14dcb0d2e6d7ae8..f47b46cebd43faa509b8139d2a51cc8f87615893 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.block.entity; + ++import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; +@@ -111,7 +112,15 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.recipeType = recipeType; + } + ++ private static Map cachedBurnDurations = null; // Paper - cache burn durations ++ ++ public static Map getBurnDurations() { return getFuel(); } // Paper - OBFHELPER + public static Map getFuel() { ++ // Paper start - cache burn durations ++ if(cachedBurnDurations != null) { ++ return cachedBurnDurations; ++ } ++ // Paper end + Map map = Maps.newLinkedHashMap(); + + add(map, (ItemLike) Items.LAVA_BUCKET, 20000); +@@ -174,7 +183,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + add(map, (ItemLike) Blocks.FLETCHING_TABLE, 300); + add(map, (ItemLike) Blocks.SMITHING_TABLE, 300); + add(map, (ItemLike) Blocks.COMPOSTER, 300); +- return map; ++ // Paper start - cache burn durations ++ cachedBurnDurations = ImmutableMap.copyOf(map); ++ return cachedBurnDurations; ++ // Paper end + } + + // CraftBukkit start - add fields and methods +@@ -428,7 +440,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + } else { + Item item = fuel.getItem(); + +- return (Integer) getFuel().getOrDefault(item, 0); ++ return getBurnDurations().getOrDefault(item, 0); // Paper - cache burn durations + } + } + +@@ -441,7 +453,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + // Paper end + + public static boolean isFuel(ItemStack stack) { +- return getFuel().containsKey(stack.getItem()); ++ return getBurnDurations().containsKey(stack.getItem()); // Paper - cache burn durations + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0626-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/Remapped-Spigot-Server-Patches/0626-Allow-disabling-mob-spawner-spawn-egg-transformation.patch new file mode 100644 index 000000000..efb245db2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0626-Allow-disabling-mob-spawner-spawn-egg-transformation.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Fri, 9 Oct 2020 20:30:12 -0400 +Subject: [PATCH] Allow disabling mob spawner spawn egg transformation + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index a3b3e3e04b7a5e3a351992e06870cc91fbd8adc8..b48067c71f9de18ba40e970e2832f6245984a218 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -722,4 +722,9 @@ public class PaperWorldConfig { + private void fixCuringExploit() { + fixCuringZombieVillagerDiscountExploit = getBoolean("game-mechanics.fix-curing-zombie-villager-discount-exploit", fixCuringZombieVillagerDiscountExploit); + } ++ ++ public boolean disableMobSpawnerSpawnEggTransformation = false; ++ private void disableMobSpawnerSpawnEggTransformation() { ++ disableMobSpawnerSpawnEggTransformation = getBoolean("game-mechanics.disable-mob-spawner-spawn-egg-transformation", disableMobSpawnerSpawnEggTransformation); ++ } + } +diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +index 9aae1435a271bb17d6355e4c75ccbce78cb9d449..7904a2176253eaf48b47e06dd2e19758a673873c 100644 +--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java ++++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +@@ -60,7 +60,7 @@ public class SpawnEggItem extends Item { + Direction enumdirection = context.getClickedFace(); + BlockState iblockdata = world.getBlockState(blockposition); + +- if (iblockdata.is(Blocks.SPAWNER)) { ++ if (!world.paperConfig.disableMobSpawnerSpawnEggTransformation && iblockdata.is(Blocks.SPAWNER)) { // Paper + BlockEntity tileentity = world.getBlockEntity(blockposition); + + if (tileentity instanceof SpawnerBlockEntity) { diff --git a/Remapped-Spigot-Server-Patches/0627-Implement-PlayerFlowerPotManipulateEvent.patch b/Remapped-Spigot-Server-Patches/0627-Implement-PlayerFlowerPotManipulateEvent.patch new file mode 100644 index 000000000..2c81edd9a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0627-Implement-PlayerFlowerPotManipulateEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Tue, 13 Aug 2019 19:45:06 -0700 +Subject: [PATCH] Implement PlayerFlowerPotManipulateEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +index ae74e70457f8f46ee71bf0902ade3468da272e81..a1d1f3416df8e313688fedad47dd264444d7c465 100644 +--- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +@@ -20,6 +20,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent; // Paper + + public class FlowerPotBlock extends Block { + +@@ -52,6 +53,27 @@ public class FlowerPotBlock extends Block { + boolean flag1 = this.content == Blocks.AIR; + + if (flag != flag1) { ++ // Paper start ++ org.bukkit.entity.Player player1 = (org.bukkit.entity.Player) player.getBukkitEntity(); ++ boolean placing = flag1; ++ org.bukkit.block.Block bukkitblock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack bukkititemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack); ++ org.bukkit.Material mat = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(content); ++ org.bukkit.inventory.ItemStack bukkititemstack1 = new org.bukkit.inventory.ItemStack(mat, 1); ++ org.bukkit.inventory.ItemStack whichitem = placing ? bukkititemstack : bukkititemstack1; ++ ++ PlayerFlowerPotManipulateEvent event = new PlayerFlowerPotManipulateEvent(player1, bukkitblock, whichitem, placing); ++ player1.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ // Update client ++ player1.sendBlockChange(bukkitblock.getLocation(), bukkitblock.getBlockData()); ++ player1.updateInventory(); ++ ++ return InteractionResult.PASS; ++ } ++ // Paper end ++ + if (flag1) { + world.setBlock(pos, block.defaultBlockState(), 3); + player.awardStat(Stats.POT_FLOWER); diff --git a/Remapped-Spigot-Server-Patches/0628-Fix-interact-event-not-being-called-in-adventure.patch b/Remapped-Spigot-Server-Patches/0628-Fix-interact-event-not-being-called-in-adventure.patch new file mode 100644 index 000000000..5e69fe3a8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0628-Fix-interact-event-not-being-called-in-adventure.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheMolkaPL +Date: Sun, 21 Jun 2020 17:21:46 +0200 +Subject: [PATCH] Fix interact event not being called in adventure + +Call PlayerInteractEvent when left-clicking on a block in adventure mode + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e50e42cce60c725cdd981d8927e379c5760d9200..cb6568b622abeb939a1195f4656accc8a1c3f1fc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1701,7 +1701,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + MutableComponent ichatmutablecomponent = (new TranslatableComponent("build.tooHigh", new Object[]{this.server.getMaxBuildHeight()})).withStyle(ChatFormatting.RED); + + this.player.connection.send(new ClientboundChatPacket(ichatmutablecomponent, ChatType.GAME_INFO, Util.NIL_UUID)); +- } else if (enuminteractionresult.shouldSwing()) { ++ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { + this.player.swing(enumhand, true); + } + } +@@ -2202,7 +2202,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + Vec3 vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); + HitResult movingobjectposition = this.player.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player)); + +- if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { ++ if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK || this.player.gameMode.getGameModeForPlayer() == GameType.ADVENTURE) { // Paper - call PlayerInteractEvent when left-clicking on a block in adventure mode + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.inventory.getSelected(), InteractionHand.MAIN_HAND); + } + diff --git a/Remapped-Spigot-Server-Patches/0629-Zombie-API-breaking-doors.patch b/Remapped-Spigot-Server-Patches/0629-Zombie-API-breaking-doors.patch new file mode 100644 index 000000000..63b6f1e73 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0629-Zombie-API-breaking-doors.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 18 Nov 2020 11:32:46 -0800 +Subject: [PATCH] Zombie API - breaking doors + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 4105c1763d25824aac35d305a793823c1604eee8..77634a1e8e7539000f7db0b96f4548137af1a819 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -137,10 +137,12 @@ public class Zombie extends Monster { + return (Boolean) this.getEntityData().get(Zombie.DATA_DROWNED_CONVERSION_ID); + } + ++ public boolean canBreakDoors() { return this.canBreakDoors(); } // Paper - OBFHELPER + public boolean canBreakDoors() { + return this.canBreakDoors; + } + ++ public void setCanBreakDoors(boolean canBreakDoors) { this.setCanBreakDoors(canBreakDoors); } // Paper - OBFHELPER + public void setCanBreakDoors(boolean canBreakDoors) { + if (this.supportsBreakDoorGoal() && GoalUtils.hasGroundPathNavigation(this)) { + if (this.canBreakDoors != canBreakDoors) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 86f65c07806a118c49e900c59be86c2bd2eb124c..44f21b3b1c2f2fbc1f3f53931349b9a9426e97f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -128,6 +128,16 @@ public class CraftZombie extends CraftMonster implements Zombie { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public boolean canBreakDoors() { ++ return getHandle().canBreakDoors(); ++ } ++ ++ @Override ++ public void setCanBreakDoors(boolean canBreakDoors) { ++ getHandle().setCanBreakDoors(canBreakDoors); ++ } + // Paper end + + @Override diff --git a/Remapped-Spigot-Server-Patches/0630-Fix-nerfed-slime-when-splitting.patch b/Remapped-Spigot-Server-Patches/0630-Fix-nerfed-slime-when-splitting.patch new file mode 100644 index 000000000..85d857662 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0630-Fix-nerfed-slime-when-splitting.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Aug 2020 08:39:06 -0700 +Subject: [PATCH] Fix nerfed slime when splitting + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 120ceb28ee3aee8a09cf67b45ac95d3d6613c133..2f04543c1c0a197f22fb26b2a38c7c79e6b4a63a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -242,6 +242,7 @@ public class Slime extends Mob implements Enemy { + entityslime.setPersistenceRequired(); + } + ++ entityslime.aware = this.aware; // Paper + entityslime.setCustomName(ichatbasecomponent); + entityslime.setNoAi(flag); + entityslime.setInvulnerable(this.isInvulnerable()); diff --git a/Remapped-Spigot-Server-Patches/0631-Add-EntityLoadCrossbowEvent.patch b/Remapped-Spigot-Server-Patches/0631-Add-EntityLoadCrossbowEvent.patch new file mode 100644 index 000000000..6eea72444 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0631-Add-EntityLoadCrossbowEvent.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 7 Oct 2020 12:04:01 -0400 +Subject: [PATCH] Add EntityLoadCrossbowEvent + + +diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java +index e1e58b7035e6dbafdad0a04cc5333464fc4febb8..32e927375daeaf16f4ea763bc9f7c4a244797bba 100644 +--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java ++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java +@@ -3,6 +3,8 @@ package net.minecraft.world.item; + import com.google.common.collect.Lists; + import com.mojang.math.Quaternion; + import com.mojang.math.Vector3f; ++import org.bukkit.inventory.EquipmentSlot; // Paper ++import io.papermc.paper.event.entity.EntityLoadCrossbowEvent; // Paper - EntityLoadCrossbowEvent namespace conflicts + import java.util.List; + import java.util.Random; + import java.util.function.Predicate; +@@ -73,7 +75,11 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + int j = this.getUseDuration(stack) - remainingUseTicks; + float f = getPowerForTime(j, stack); + +- if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) { ++ // Paper start - EntityLoadCrossbowEvent ++ if (f >= 1.0F && !isCharged(stack) /*&& a(entityliving, itemstack)*/) { ++ final EntityLoadCrossbowEvent event = new EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), user.getUsedItemHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND); ++ if (!event.callEvent() || !attemptProjectileLoad(user, stack, event.shouldConsumeItem())) return; ++ // Paper end + setCharged(stack, true); + SoundSource soundcategory = user instanceof Player ? SoundSource.PLAYERS : SoundSource.HOSTILE; + +@@ -82,11 +88,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + + } + +- private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile) { +- int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, projectile); ++ private static boolean attemptProjectileLoad(LivingEntity ent, ItemStack bow) { return tryLoadProjectiles(ent, bow); } // Paper - EntityLoadCrossbowEvent - OBFHELPER ++ private static boolean attemptProjectileLoad(LivingEntity ent, ItemStack bow, boolean consume) { return a(ent, bow, consume); } // Paper - EntityLoadCrossbowEvent - OBFHELPER ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile) { return a(shooter, projectile, true); };// Paper - add consume ++ private static boolean a(LivingEntity entityliving, ItemStack itemstack, boolean consume) { // Paper - add consume ++ int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, itemstack); + int j = i == 0 ? 1 : 3; +- boolean flag = shooter instanceof Player && ((Player) shooter).abilities.instabuild; +- ItemStack itemstack1 = shooter.getProjectile(projectile); ++ boolean flag = !consume || entityliving instanceof Player && ((Player) entityliving).abilities.instabuild; // Paper - add consme ++ ItemStack itemstack1 = entityliving.getProjectile(itemstack); + ItemStack itemstack2 = itemstack1.copy(); + + for (int k = 0; k < j; ++k) { +@@ -103,7 +112,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + // CraftBukkit end + } + +- if (!loadProjectile(shooter, projectile, itemstack1, k > 0, flag)) { ++ if (!loadProjectile(entityliving, itemstack, itemstack1, k > 0, flag)) { + return false; + } + } diff --git a/Remapped-Spigot-Server-Patches/0632-Guardian-beam-workaround.patch b/Remapped-Spigot-Server-Patches/0632-Guardian-beam-workaround.patch new file mode 100644 index 000000000..c16b90569 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0632-Guardian-beam-workaround.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabscap +Date: Sat, 19 Mar 2016 22:25:11 +0100 +Subject: [PATCH] Guardian beam workaround + +This patch is a workaround for MC-165595 + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +index a02f492c279f4e859b44d52e150a2990cbe8275a..4f177e7396db00db56f2106068fc02b9c2618407 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +@@ -6,7 +6,7 @@ import net.minecraft.network.protocol.Packet; + + public class ClientboundSetTimePacket implements Packet { + +- private long gameTime; ++ private long gameTime; private final void setWorldAge(final long age) { this.gameTime = age; } private final long getWorldAge() { return this.gameTime; } // Paper - OBFHELPER + private long dayTime; + + public ClientboundSetTimePacket() {} +@@ -21,6 +21,9 @@ public class ClientboundSetTimePacket implements Packet +Date: Sun, 20 Dec 2020 16:41:44 -0800 +Subject: [PATCH] Added WorldGameRuleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +index 87b968019fa10647522121c7b29094ed3e0dcf6d..7f124784dd7876cdb26f16e83deddf07dd9a198e 100644 +--- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +@@ -31,7 +31,7 @@ public class GameRuleCommand { + CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); + T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit + +- t0.setFromArgument(context, "value"); ++ t0.setValue(context, "value", key); // Paper + commandlistenerwrapper.sendSuccess(new TranslatableComponent("commands.gamerule.set", new Object[]{key.getId(), t0.toString()}), true); + return t0.getCommandResult(); + } +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 6c996d34ef34879db1d65c39adf99ce2d64e5499..1e6f299571a25729dbf8c5b0cd115c1e842a8a3c 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -25,6 +25,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerPlayer; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.event.world.WorldGameRuleChangeEvent; // Paper + + public class GameRules { + +@@ -177,8 +178,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = BoolArgumentType.getBool(context, name); ++ protected void a(CommandContext commandcontext, String s, GameRules.Key gameRuleKey) { // Paper start ++ WorldGameRuleChangeEvent event = new WorldGameRuleChangeEvent(commandcontext.getSource().getBukkitWorld(), commandcontext.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(commandcontext, s))); ++ if (!event.callEvent()) return; ++ this.value = Boolean.parseBoolean(event.getValue()); ++ // Paper end + } + + public boolean get() { +@@ -237,8 +241,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = IntegerArgumentType.getInteger(context, name); ++ protected void a(CommandContext commandcontext, String s, GameRules.Key gameRuleKey) { // Paper start ++ WorldGameRuleChangeEvent event = new WorldGameRuleChangeEvent(commandcontext.getSource().getBukkitWorld(), commandcontext.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(commandcontext, s))); ++ if (!event.callEvent()) return; ++ this.value = Integer.parseInt(event.getValue()); ++ // Paper end + } + + public int get() { +@@ -291,11 +298,13 @@ public class GameRules { + this.type = type; + } + +- protected abstract void updateFromArgument(CommandContext context, String name); ++ protected void updateValue(CommandContext commandcontext, String s, GameRules.Key gameRuleKey) { this.a(commandcontext, s, gameRuleKey); } // Paper - OBFHELPER ++ protected abstract void a(CommandContext commandcontext, String s, GameRules.Key gameRuleKey); // Paper + +- public void setFromArgument(CommandContext context, String name) { +- this.updateFromArgument(context, name); +- this.onChanged(((CommandSourceStack) context.getSource()).getServer()); ++ public void setValue(CommandContext commandcontext, String s, GameRules.Key gameRuleKey) { this.b(commandcontext, s, gameRuleKey); } // Paper - OBFHELPER ++ public void b(CommandContext commandcontext, String s, GameRules.Key gameRuleKey) { // Paper ++ this.updateValue(commandcontext, s, gameRuleKey); // Paper ++ this.onChanged(((CommandSourceStack) commandcontext.getSource()).getServer()); + } + + public void onChanged(@Nullable MinecraftServer server) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index aaf97c13babce3b0ffc639ef950d59d1eba1398a..f497b9e11a075a84ff0a2117eb79d0532e4a326f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2345,8 +2345,13 @@ public class CraftWorld implements World { + + if (!isGameRule(rule)) return false; + ++ // Paper start ++ GameRule gameRule = GameRule.getByName(rule); ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value); ++ if (!event.callEvent()) return false; ++ // Paper end + GameRules.Value handle = getHandle().getGameRules().getRule(getGameRulesNMS().get(rule)); +- handle.deserialize(value); ++ handle.deserialize(event.getValue().toString()); // Paper + handle.onChanged(getHandle().getServer()); + return true; + } +@@ -2381,8 +2386,12 @@ public class CraftWorld implements World { + + if (!isGameRule(rule.getName())) return false; + ++ // Paper start ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue)); ++ if (!event.callEvent()) return false; ++ // Paper end + GameRules.Value handle = getHandle().getGameRules().getRule(getGameRulesNMS().get(rule.getName())); +- handle.deserialize(newValue.toString()); ++ handle.deserialize(event.getValue().toString()); // Paper + handle.onChanged(getHandle().getServer()); + return true; + } diff --git a/Remapped-Spigot-Server-Patches/0634-Added-ServerResourcesReloadedEvent.patch b/Remapped-Spigot-Server-Patches/0634-Added-ServerResourcesReloadedEvent.patch new file mode 100644 index 000000000..02aef6be6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0634-Added-ServerResourcesReloadedEvent.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 20:04:01 -0800 +Subject: [PATCH] Added ServerResourcesReloadedEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index c83f2636ae93d92381e019d5b13ac82c5a1d30bf..892ca65d258b0745be95d7ef4886c49899b24d92 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2,9 +2,6 @@ package net.minecraft.server; + + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; +-import co.aikar.timings.Timings; +-import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +-import com.google.common.base.Stopwatch; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -181,6 +178,7 @@ import org.bukkit.event.server.ServerLoadEvent; + import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot + import io.papermc.paper.util.PaperJvmChecker; // Paper ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; // Paper + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements SnooperPopulator, CommandSource, AutoCloseable { + +@@ -1934,9 +1932,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection datapacks) { ++ return this.reloadServerResources(datapacks, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadServerResources(Collection collection, ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { +- Stream stream = datapacks.stream(); // CraftBukkit - decompile error ++ Stream stream = collection.stream(); // CraftBukkit - decompile error + PackRepository resourcepackrepository = this.packRepository; + + this.packRepository.getClass(); +@@ -1947,9 +1951,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop collection, CommandSourceStack commandlistenerwrapper) { +- commandlistenerwrapper.getServer().reloadResources(collection).exceptionally((throwable) -> { +- ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); +- commandlistenerwrapper.sendFailure(new TranslatableComponent("commands.reload.failure")); ++ commandlistenerwrapper.getServer().reloadServerResources(collection, ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper ++ CommandReload.LOGGER.warn("Failed to execute reload", throwable); ++ commandlistenerwrapper.sendFailureMessage(new ChatMessage("commands.reload.failure")); + return null; + }); + } +@@ -48,7 +49,7 @@ public class ReloadCommand { + WorldData savedata = minecraftserver.getWorldData(); + Collection collection = resourcepackrepository.getSelectedIds(); + Collection collection1 = discoverNewPacks(resourcepackrepository, savedata, collection); +- minecraftserver.reloadResources(collection1); ++ minecraftserver.reloadServerResources(collection1, ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper + } + // CraftBukkit end + diff --git a/Remapped-Spigot-Server-Patches/0635-Added-world-settings-for-mobs-picking-up-loot.patch b/Remapped-Spigot-Server-Patches/0635-Added-world-settings-for-mobs-picking-up-loot.patch new file mode 100644 index 000000000..ead34bbc6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0635-Added-world-settings-for-mobs-picking-up-loot.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 28 Nov 2020 18:43:52 -0800 +Subject: [PATCH] Added world settings for mobs picking up loot + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b48067c71f9de18ba40e970e2832f6245984a218..23a23e2ea133ce81d3dedc4ffd17435a995497ef 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -363,6 +363,14 @@ public class PaperWorldConfig { + log("Creeper lingering effect: " + disableCreeperLingeringEffect); + } + ++ public boolean zombiesAlwaysCanPickUpLoot; ++ public boolean skeletonsAlwaysCanPickUpLoot; ++ private void setMobsAlwaysCanPickUpLoot() { ++ zombiesAlwaysCanPickUpLoot = getBoolean("mobs-can-always-pick-up-loot.zombies", false); ++ skeletonsAlwaysCanPickUpLoot = getBoolean("mobs-can-always-pick-up-loot.skeletons", false); ++ log("Zombies can always pick up loot: " + zombiesAlwaysCanPickUpLoot + ". Skeletons can always pick up loot: " + skeletonsAlwaysCanPickUpLoot + "."); ++ } ++ + public int expMergeMaxValue; + private void expMergeMaxValue() { + expMergeMaxValue = getInt("experience-merge-max-value", -1); +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 76027a7c9615495af64102744e264d7ba7c9b87e..68e52e3a31e70569d1a92602aff4b7b81c594757 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -149,7 +149,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + this.populateDefaultEquipmentSlots(difficulty); + this.populateDefaultEquipmentEnchantments(difficulty); + this.reassessWeaponGoal(); +- this.setCanPickUpLoot(this.random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); ++ this.setCanPickUpLoot(this.level.paperConfig.skeletonsAlwaysCanPickUpLoot || this.random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper + if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { + LocalDate localdate = LocalDate.now(); + int i = localdate.get(ChronoField.DAY_OF_MONTH); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 77634a1e8e7539000f7db0b96f4548137af1a819..74fd175c4dc2d0d9832ee41efaf065b75a43f4b8 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -494,7 +494,7 @@ public class Zombie extends Monster { + Object object = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityTag); + float f = difficulty.getSpecialMultiplier(); + +- this.setCanPickUpLoot(this.random.nextFloat() < 0.55F * f); ++ this.setCanPickUpLoot(this.level.paperConfig.zombiesAlwaysCanPickUpLoot || this.random.nextFloat() < 0.55F * f); // Paper + if (object == null) { + object = new Zombie.ZombieGroupData(getSpawnAsBabyOdds(world.getRandom()), true); + } diff --git a/Remapped-Spigot-Server-Patches/0636-Implemented-BlockFailedDispenseEvent.patch b/Remapped-Spigot-Server-Patches/0636-Implemented-BlockFailedDispenseEvent.patch new file mode 100644 index 000000000..a7a4e4991 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0636-Implemented-BlockFailedDispenseEvent.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> +Date: Wed, 22 Apr 2020 09:40:38 +0200 +Subject: [PATCH] Implemented BlockFailedDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index bfb2e21ccbcc67d6c9b4b329db1949d7d938bd2e..2a4cb76bdfcf55ba222b4976359c1b8efb165009 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -81,6 +81,7 @@ public class DispenserBlock extends BaseEntityBlock { + int i = tileentitydispenser.getRandomSlot(); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(worldserver, pos)) // Paper - BlockFailedDispenseEvent is called here + worldserver.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index 154ec671e9d741e536464b794783da859e8447c1..492b19b94e2e2439f72ed9478d75641b0f50451a 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -45,6 +45,7 @@ public class DropperBlock extends DispenserBlock { + int i = tileentitydispenser.getRandomSlot(); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(worldserver, pos)) // Paper - BlockFailedDispenseEvent is called here + worldserver.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 34c7b1213b3f83ff1a1f2d606a9c25e57fea8ef3..8829ef03d0be16d8317aaf05bcd286b74f20656a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -4,6 +4,7 @@ import com.google.common.base.Function; + import com.google.common.base.Functions; + import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Either; ++import io.papermc.paper.event.block.BlockFailedDispenseEvent; + import java.net.InetAddress; + import java.util.ArrayList; + import java.util.Collections; +@@ -111,7 +112,6 @@ import org.bukkit.entity.ThrownPotion; + import org.bukkit.entity.Vehicle; + import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; +-import org.bukkit.entity.ExperienceOrb; // Paper + import org.bukkit.event.Cancellable; + import org.bukkit.event.Event; + import org.bukkit.event.Event.Result; +@@ -1784,4 +1784,12 @@ public class CraftEventFactory { + Bukkit.getPluginManager().callEvent(event); + return event; + } ++ ++ // Paper start ++ public static boolean handleBlockFailedDispenseEvent(ServerLevel worldserver, BlockPos blockposition) { ++ org.bukkit.block.Block block = worldserver.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ BlockFailedDispenseEvent event = new BlockFailedDispenseEvent(block); ++ return event.callEvent(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0637-Added-PlayerLecternPageChangeEvent.patch b/Remapped-Spigot-Server-Patches/0637-Added-PlayerLecternPageChangeEvent.patch new file mode 100644 index 000000000..b4eacbfa3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0637-Added-PlayerLecternPageChangeEvent.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 23 Nov 2020 12:58:51 -0800 +Subject: [PATCH] Added PlayerLecternPageChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index bc39e7464646d712b085251dc0277a5b1ec0a393..b5d79635cd8b0eb6b17962450b347010aeb52654 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -561,6 +561,7 @@ public abstract class AbstractContainerMenu { + this.getSlot(slot).set(stack); + } + ++ public void setData(int index, int value) { this.setData(index, value); } // Paper - OBFHELPER + public void setData(int id, int value) { + ((DataSlot) this.dataSlots.get(id)).set(value); + } +diff --git a/src/main/java/net/minecraft/world/inventory/LecternMenu.java b/src/main/java/net/minecraft/world/inventory/LecternMenu.java +index 29e8dbc6be57faf50a8ca68eed6bf2e203b7e87a..a7be91a9336065899c409526a890e55f37b98751 100644 +--- a/src/main/java/net/minecraft/world/inventory/LecternMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LecternMenu.java +@@ -11,6 +11,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + import org.bukkit.event.player.PlayerTakeLecternBookEvent; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerLecternPageChangeEvent; // Paper + + public class LecternMenu extends AbstractContainerMenu { + +@@ -58,6 +59,7 @@ public class LecternMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + int j; ++ PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper + + if (id >= 100) { + j = id - 100; +@@ -67,11 +69,25 @@ public class LecternMenu extends AbstractContainerMenu { + switch (id) { + case 1: + j = this.lecternData.get(0); +- this.setData(0, j - 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 2: + j = this.lecternData.get(0); +- this.setData(0, j + 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 3: + if (!player.mayBuild()) { diff --git a/Remapped-Spigot-Server-Patches/0638-Fire-event-on-GS4-query.patch b/Remapped-Spigot-Server-Patches/0638-Fire-event-on-GS4-query.patch new file mode 100644 index 000000000..a893ed5f5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0638-Fire-event-on-GS4-query.patch @@ -0,0 +1,265 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 17 Mar 2019 21:46:56 +0200 +Subject: [PATCH] Fire event on GS4 query + + +diff --git a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java +index d10de580430d754204b36de809376538a14591e6..f6f0539a8e6edbeb1c412cee753a282b24a38046 100644 +--- a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java ++++ b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java +@@ -18,15 +18,27 @@ public class NetworkDataOutputStream { + this.dataOutputStream.write(abyte, 0, abyte.length); + } + ++ public void writeString(String string) throws IOException { this.writeString(string); } // Paper - OBFHELPER + public void writeString(String s) throws IOException { + this.dataOutputStream.writeBytes(s); + this.dataOutputStream.write(0); + } ++ // Paper start - unchecked exception variant to use in Stream API ++ public void writeStringUnchecked(String string) { ++ try { ++ writeString(string); ++ } catch (IOException e) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(e); ++ } ++ } ++ // Paper end + ++ public void writeInt(int i) throws IOException { this.write(i); } // Paper - OBFHELPER + public void write(int i) throws IOException { + this.dataOutputStream.write(i); + } + ++ public void writeShort(short i) throws IOException { this.writeShort(i); } // Paper - OBFHELPER + public void writeShort(short short0) throws IOException { + this.dataOutputStream.writeShort(Short.reverseBytes(short0)); + } +diff --git a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java +index 170d047463154bd6851199f06fe343ccb1896213..5562a3caff328bb08857b4f06a79b1e52f390fdd 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java +@@ -16,6 +16,7 @@ import java.util.Random; + import javax.annotation.Nullable; + import net.minecraft.Util; + import net.minecraft.server.ServerInterface; ++import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.server.rcon.NetworkDataOutputStream; + import net.minecraft.server.rcon.PktUtils; + import org.apache.logging.log4j.LogManager; +@@ -26,18 +27,18 @@ public class QueryThreadGs4 extends GenericThread { + private static final Logger LOGGER = LogManager.getLogger(); + private long lastChallengeCheck; + private final int port; +- private final int serverPort; +- private final int maxPlayers; +- private final String serverName; +- private final String worldName; ++ private final int serverPort; private final int getServerPort() { return this.serverPort; } // Paper - OBFHELPER ++ private final int maxPlayers; private final int getMaxPlayers() { return this.maxPlayers; } // Paper - OBFHELPER ++ private final String serverName; private final String getMotd() { return this.serverName; } // Paper - OBFHELPER ++ private final String worldName; private final String getWorldName() { return this.worldName; } // Paper - OBFHELPER + private DatagramSocket socket; + private final byte[] buffer = new byte[1460]; +- private String hostIp; ++ private String hostIp; public final String getServerHost() { return this.hostIp; } // Paper - OBFHELPER + private String serverIp; + private final Map validChallenges; +- private final NetworkDataOutputStream rulesResponse; ++ private final NetworkDataOutputStream rulesResponse; private final NetworkDataOutputStream getCachedFullResponse() { return this.rulesResponse; } // Paper - OBFHELPER + private long lastRulesResponse; +- private final ServerInterface serverInterface; ++ private final ServerInterface serverInterface; private final ServerInterface getServer() { return this.serverInterface; } // Paper - OBFHELPER + + private QueryThreadGs4(ServerInterface server, int queryPort) { + super("Query Listener"); +@@ -107,13 +108,39 @@ public class QueryThreadGs4 extends GenericThread { + + remotestatusreply.write((int) 0); + remotestatusreply.writeBytes(this.getIdentBytes(packet.getSocketAddress())); +- remotestatusreply.writeString(this.serverName); ++ /* Paper start - GS4 Query event ++ remotestatusreply.a(this.i); ++ remotestatusreply.a("SMP"); ++ remotestatusreply.a(this.j); ++ remotestatusreply.a(Integer.toString(this.r.getPlayerCount())); ++ remotestatusreply.a(Integer.toString(this.h)); ++ remotestatusreply.a((short) this.g); ++ remotestatusreply.a(this.m); ++ */ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC; ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.getMotd()) ++ .map(this.getWorldName()) ++ .currentPlayers(this.getServer().getPlayerCount()) ++ .maxPlayers(this.getMaxPlayers()) ++ .port(this.getServerPort()) ++ .hostname(this.getServerHost()) ++ .gameVersion(this.getServer().getServerVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); ++ remotestatusreply.writeString(queryResponse.getMotd()); + remotestatusreply.writeString("SMP"); +- remotestatusreply.writeString(this.worldName); +- remotestatusreply.writeString(Integer.toString(this.serverInterface.getPlayerCount())); +- remotestatusreply.writeString(Integer.toString(this.maxPlayers)); +- remotestatusreply.writeShort((short) this.serverPort); +- remotestatusreply.writeString(this.hostIp); ++ remotestatusreply.writeString(queryResponse.getMap()); ++ remotestatusreply.writeString(Integer.toString(queryResponse.getCurrentPlayers())); ++ remotestatusreply.writeString(Integer.toString(queryResponse.getMaxPlayers())); ++ remotestatusreply.writeShort((short) queryResponse.getPort()); ++ remotestatusreply.writeString(queryResponse.getHostname()); ++ // Paper end + this.sendTo(remotestatusreply.toByteArray(), packet); + QueryThreadGs4.LOGGER.debug("Status [{}]", socketaddress); + } +@@ -150,41 +177,115 @@ public class QueryThreadGs4 extends GenericThread { + this.rulesResponse.writeString("splitnum"); + this.rulesResponse.write((int) 128); + this.rulesResponse.write((int) 0); +- this.rulesResponse.writeString("hostname"); +- this.rulesResponse.writeString(this.serverName); +- this.rulesResponse.writeString("gametype"); +- this.rulesResponse.writeString("SMP"); +- this.rulesResponse.writeString("game_id"); +- this.rulesResponse.writeString("MINECRAFT"); +- this.rulesResponse.writeString("version"); +- this.rulesResponse.writeString(this.serverInterface.getServerVersion()); +- this.rulesResponse.writeString("plugins"); +- this.rulesResponse.writeString(this.serverInterface.getPluginNames()); +- this.rulesResponse.writeString("map"); +- this.rulesResponse.writeString(this.worldName); +- this.rulesResponse.writeString("numplayers"); +- this.rulesResponse.writeString("" + this.serverInterface.getPlayerCount()); +- this.rulesResponse.writeString("maxplayers"); +- this.rulesResponse.writeString("" + this.maxPlayers); +- this.rulesResponse.writeString("hostport"); +- this.rulesResponse.writeString("" + this.serverPort); +- this.rulesResponse.writeString("hostip"); +- this.rulesResponse.writeString(this.hostIp); +- this.rulesResponse.write((int) 0); +- this.rulesResponse.write((int) 1); +- this.rulesResponse.writeString("player_"); +- this.rulesResponse.write((int) 0); +- String[] astring = this.serverInterface.getPlayerNames(); ++ /* Paper start - GS4 Query event ++ this.p.a("hostname"); ++ this.p.a(this.i); ++ this.p.a("gametype"); ++ this.p.a("SMP"); ++ this.p.a("game_id"); ++ this.p.a("MINECRAFT"); ++ this.p.a("version"); ++ this.p.a(this.r.getVersion()); ++ this.p.a("plugins"); ++ this.p.a(this.r.getPlugins()); ++ this.p.a("map"); ++ this.p.a(this.j); ++ this.p.a("numplayers"); ++ this.p.a("" + this.r.getPlayerCount()); ++ this.p.a("maxplayers"); ++ this.p.a("" + this.h); ++ this.p.a("hostport"); ++ this.p.a("" + this.g); ++ this.p.a("hostip"); ++ this.p.a(this.m); ++ this.p.a((int) 0); ++ this.p.a((int) 1); ++ this.p.a("player_"); ++ this.p.a((int) 0); ++ String[] astring = this.r.getPlayers(); + String[] astring1 = astring; + int j = astring.length; + + for (int k = 0; k < j; ++k) { + String s = astring1[k]; + +- this.rulesResponse.writeString(s); ++ this.p.a(s); + } + +- this.rulesResponse.write((int) 0); ++ this.p.a((int) 0); ++ */ ++ // Pack plugins ++ java.util.List plugins = java.util.Collections.emptyList(); ++ org.bukkit.plugin.Plugin[] bukkitPlugins; ++ if (((DedicatedServer) this.getServer()).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) { ++ plugins = java.util.stream.Stream.of(bukkitPlugins) ++ .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion())) ++ .collect(java.util.stream.Collectors.toList()); ++ } ++ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.getMotd()) ++ .map(this.getWorldName()) ++ .currentPlayers(this.getServer().getPlayerCount()) ++ .maxPlayers(this.getMaxPlayers()) ++ .port(this.getServerPort()) ++ .hostname(this.getServerHost()) ++ .plugins(plugins) ++ .players(this.getServer().getPlayerNames()) ++ .gameVersion(this.getServer().getServerVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL; ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); ++ this.getCachedFullResponse().writeString("hostname"); ++ this.getCachedFullResponse().writeString(queryResponse.getMotd()); ++ this.getCachedFullResponse().writeString("gametype"); ++ this.getCachedFullResponse().writeString("SMP"); ++ this.getCachedFullResponse().writeString("game_id"); ++ this.getCachedFullResponse().writeString("MINECRAFT"); ++ this.getCachedFullResponse().writeString("version"); ++ this.getCachedFullResponse().writeString(queryResponse.getGameVersion()); ++ this.getCachedFullResponse().writeString("plugins"); ++ java.lang.StringBuilder pluginsString = new java.lang.StringBuilder(); ++ pluginsString.append(queryResponse.getServerVersion()); ++ if (!queryResponse.getPlugins().isEmpty()) { ++ pluginsString.append(": "); ++ java.util.Iterator iter = queryResponse.getPlugins().iterator(); ++ while (iter.hasNext()) { ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next(); ++ pluginsString.append(info.getName()); ++ if (info.getVersion() != null) { ++ pluginsString.append(' ').append(info.getVersion().replace(";", ",")); ++ } ++ if (iter.hasNext()) { ++ pluginsString.append(';').append(' '); ++ } ++ } ++ } ++ this.getCachedFullResponse().writeString(pluginsString.toString()); ++ this.getCachedFullResponse().writeString("map"); ++ this.getCachedFullResponse().writeString(queryResponse.getMap()); ++ this.getCachedFullResponse().writeString("numplayers"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getCurrentPlayers())); ++ this.getCachedFullResponse().writeString("maxplayers"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getMaxPlayers())); ++ this.getCachedFullResponse().writeString("hostport"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getPort())); ++ this.getCachedFullResponse().writeString("hostip"); ++ this.getCachedFullResponse().writeString(queryResponse.getHostname()); ++ // The "meaningless data" start, copied from above ++ this.getCachedFullResponse().writeInt(0); ++ this.getCachedFullResponse().writeInt(1); ++ this.getCachedFullResponse().writeString("player_"); ++ this.getCachedFullResponse().writeInt(0); ++ // "Meaningless data" end ++ queryResponse.getPlayers().forEach(this.getCachedFullResponse()::writeStringUnchecked); ++ this.getCachedFullResponse().writeInt(0); ++ // Paper end + return this.rulesResponse.toByteArray(); + } + } diff --git a/Remapped-Spigot-Server-Patches/0639-Added-PlayerLoomPatternSelectEvent.patch b/Remapped-Spigot-Server-Patches/0639-Added-PlayerLoomPatternSelectEvent.patch new file mode 100644 index 000000000..3c2cf5afe --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0639-Added-PlayerLoomPatternSelectEvent.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 25 Nov 2020 16:33:27 -0800 +Subject: [PATCH] Added PlayerLoomPatternSelectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 3460fb2bb1451b8456a7fe42449ec4dbce641f40..0dc1b0b7181c0f93dcf6213c63baffcd4694d70c 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -20,6 +20,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryLoom; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerLoomPatternSelectEvent; // Paper + + public class LoomMenu extends AbstractContainerMenu { + +@@ -39,7 +40,7 @@ public class LoomMenu extends AbstractContainerMenu { + } + // CraftBukkit end + private final ContainerLevelAccess access; +- private final DataSlot selectedBannerPatternIndex; ++ private final DataSlot selectedBannerPatternIndex; public final DataSlot getSelectedBannerPattern() { return this.selectedBannerPatternIndex; }; // Paper - OBFHELPER + private Runnable slotUpdateListener; + private final Slot bannerSlot; + private final Slot dyeSlot; +@@ -158,7 +159,22 @@ public class LoomMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (id > 0 && id <= BannerPattern.AVAILABLE_PATTERNS) { +- this.selectedBannerPatternIndex.set(id); ++ // Paper start ++ int enumBannerPatternTypeOrdinal = id; ++ PlayerLoomPatternSelectEvent event = new PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.block.banner.PatternType.getByIdentifier(BannerPattern.values()[id].getIdentifier())); ++ if (!event.callEvent()) { ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ for (BannerPattern nms : BannerPattern.values()) { ++ if (event.getPatternType().getIdentifier().equals(nms.getIdentifier())) { ++ enumBannerPatternTypeOrdinal = nms.ordinal(); ++ break; ++ } ++ } ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ this.getSelectedBannerPattern().set(enumBannerPatternTypeOrdinal); ++ // Paper end + this.setupResultSlot(); + return true; + } else { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BannerPattern.java b/src/main/java/net/minecraft/world/level/block/entity/BannerPattern.java +index 9ea01d5888a21b0dedb555d118a4dc07af2b50fd..9ee3f8bb2294fc552735a64efbddf661d39602c7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BannerPattern.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BannerPattern.java +@@ -33,6 +33,7 @@ public enum BannerPattern { + this.hasPatternItem = flag; + } + ++ public String getIdentifier() { return this.getHashname(); } // Paper - OBFHELPER + public String getHashname() { + return this.hashname; + } diff --git a/Remapped-Spigot-Server-Patches/0640-Configurable-door-breaking-difficulty.patch b/Remapped-Spigot-Server-Patches/0640-Configurable-door-breaking-difficulty.patch new file mode 100644 index 000000000..c1c84edf4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0640-Configurable-door-breaking-difficulty.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 22:27:43 -0800 +Subject: [PATCH] Configurable door breaking difficulty + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 23a23e2ea133ce81d3dedc4ffd17435a995497ef..7ebc85264a2cbfb601dfe5472b561cac1a7cf8bf 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -5,7 +5,10 @@ import java.util.EnumMap; + import java.util.HashMap; + import java.util.List; + import java.util.Map; +- ++import java.util.stream.Collectors; ++import net.minecraft.world.Difficulty; ++import net.minecraft.world.entity.monster.Vindicator; ++import net.minecraft.world.entity.monster.Zombie; + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; + import org.bukkit.Material; +@@ -73,6 +76,11 @@ public class PaperWorldConfig { + return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); + } + ++ private > List getEnumList(String path, List def, Class type) { ++ config.addDefault("world-settings.default." + path, def.stream().map(Enum::name).collect(Collectors.toList())); ++ return ((List) (config.getList("world-settings." + worldName + "." + path, config.getList("world-settings.default." + path)))).stream().map(s -> Enum.valueOf(type, s)).collect(Collectors.toList()); ++ } ++ + public int cactusMaxHeight; + public int reedMaxHeight; + public int bambooMaxHeight; +@@ -735,4 +743,23 @@ public class PaperWorldConfig { + private void disableMobSpawnerSpawnEggTransformation() { + disableMobSpawnerSpawnEggTransformation = getBoolean("game-mechanics.disable-mob-spawner-spawn-egg-transformation", disableMobSpawnerSpawnEggTransformation); + } ++ ++ public List zombieBreakDoors; ++ public List vindicatorBreakDoors; ++ private void setupEntityBreakingDoors() { ++ zombieBreakDoors = getEnumList( ++ "door-breaking-difficulty.zombie", ++ Arrays.stream(Difficulty.values()) ++ .filter(Zombie.getDoorBreakingPredicate()) ++ .collect(Collectors.toList()), ++ Difficulty.class ++ ); ++ vindicatorBreakDoors = getEnumList( ++ "door-breaking-difficulty.vindicator", ++ Arrays.stream(Difficulty.values()) ++ .filter(Vindicator.getDoorBreakingPredicate()) ++ .collect(Collectors.toList()), ++ Difficulty.class ++ ); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +index 623de661f3b56062792e3a7dbc508637aa58aca5..48700094da6e97610ccc652593a9e229ba7b1003 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +@@ -48,6 +48,7 @@ import net.minecraft.world.level.ServerLevelAccessor; + + public class Vindicator extends AbstractIllager { + ++ public static final Predicate getDoorBreakingPredicate() { return DOOR_BREAKING_PREDICATE; } // Paper - OBFHELPER + private static final Predicate DOOR_BREAKING_PREDICATE = (enumdifficulty) -> { + return enumdifficulty == Difficulty.NORMAL || enumdifficulty == Difficulty.HARD; + }; +@@ -204,7 +205,7 @@ public class Vindicator extends AbstractIllager { + static class VindicatorBreakDoorGoal extends BreakDoorGoal { + + public VindicatorBreakDoorGoal(Mob mob) { +- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); ++ super(mob, 6, com.google.common.base.Predicates.in(mob.level.paperConfig.vindicatorBreakDoors)); // Paper + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 74fd175c4dc2d0d9832ee41efaf065b75a43f4b8..caa99a2737598bd74ede54f1c35ce4b99ce1e6d3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -83,6 +83,7 @@ public class Zombie extends Monster { + private static final EntityDataAccessor DATA_BABY_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor DATA_SPECIAL_TYPE_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.INT); + public static final EntityDataAccessor DATA_DROWNED_CONVERSION_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); ++ public static final Predicate getDoorBreakingPredicate() { return DOOR_BREAKING_PREDICATE; } // Paper - OBFHELPER + private static final Predicate DOOR_BREAKING_PREDICATE = (enumdifficulty) -> { + return enumdifficulty == Difficulty.HARD; + }; +@@ -95,7 +96,7 @@ public class Zombie extends Monster { + + public Zombie(EntityType type, Level world) { + super(type, world); +- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); ++ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig.zombieBreakDoors)); // Paper + } + + public Zombie(Level world) { diff --git a/Remapped-Spigot-Server-Patches/0641-Empty-commands-shall-not-be-dispatched.patch b/Remapped-Spigot-Server-Patches/0641-Empty-commands-shall-not-be-dispatched.patch new file mode 100644 index 000000000..a9abc78c3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0641-Empty-commands-shall-not-be-dispatched.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 6 Jan 2021 23:38:43 +0100 +Subject: [PATCH] Empty commands shall not be dispatched + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index c63033e3eb50423a7c32acfc0e705623cc4bec68..5ed78383ce247ceb24cda0335dbeae293958055c 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -223,6 +223,7 @@ public class Commands { + command = event.getCommand(); + + String[] args = command.split(" "); ++ if (args.length == 0) return 0; // Paper - empty commands shall not be dispatched + + String cmd = args[0]; + if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); diff --git a/Remapped-Spigot-Server-Patches/0642-Implement-API-to-expose-exact-interaction-point.patch b/Remapped-Spigot-Server-Patches/0642-Implement-API-to-expose-exact-interaction-point.patch new file mode 100644 index 000000000..4f115620e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0642-Implement-API-to-expose-exact-interaction-point.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Mon, 4 Jan 2021 16:40:27 +1000 +Subject: [PATCH] Implement API to expose exact interaction point + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 6269e37f2859417a80e6de16045f1c2325f9746f..37761176861027d0ee06f50d60584687fdac669b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -495,7 +495,7 @@ public class ServerPlayerGameMode { + cancelledBlock = true; + } + +- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand); ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); // Paper + firedInteract = true; + interactResult = event.useItemInHand() == Event.Result.DENY; + interactPosition = blockposition.immutable(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 8829ef03d0be16d8317aaf05bcd286b74f20656a..586d21eed8189adf696ca6d3642afebbe752d1b5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -58,7 +58,9 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++import net.minecraft.world.phys.Vec3; + import org.bukkit.Bukkit; ++import org.bukkit.Location; // Paper + import org.bukkit.Material; + import org.bukkit.NamespacedKey; + import org.bukkit.Server; +@@ -475,7 +477,13 @@ public class CraftEventFactory { + return callPlayerInteractEvent(who, action, position, direction, itemstack, false, hand); + } + ++ // Paper start - Add interactionPoint + public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand) { ++ return callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, hand, null); ++ } ++ ++ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 hitVec) { ++ // Paper end + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + +@@ -501,7 +509,10 @@ public class CraftEventFactory { + itemInHand = null; + } + +- PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); ++ // Paper start ++ Location interactionPoint = hitVec == null ? null : new Location(craftWorld, hitVec.x, hitVec.y, hitVec.z); ++ PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), interactionPoint); ++ // Paper end + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } diff --git a/Remapped-Spigot-Server-Patches/0643-Remove-stale-POIs.patch b/Remapped-Spigot-Server-Patches/0643-Remove-stale-POIs.patch new file mode 100644 index 000000000..561ae57f8 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0643-Remove-stale-POIs.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jan 2021 14:17:07 +0100 +Subject: [PATCH] Remove stale POIs + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index fe7b71fbb3963beafe93a5d86bebdd629c7ec8f2..9f1838d12b13d64f10871eb672ed2aec78d9936e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2071,6 +2071,11 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + }); + optional1.ifPresent((villageplacetype) -> { + this.getServer().execute(() -> { ++ // Paper start ++ if (!optional.isPresent() && this.getPoiStorage().test(blockposition1, com.google.common.base.Predicates.alwaysTrue())) { ++ this.getPoiStorage().remove(blockposition1); ++ } ++ // Paper end + this.getPoiManager().add(blockposition1, villageplacetype); + DebugPackets.sendPoiAddedPacket(this, blockposition1); + }); +@@ -2078,6 +2083,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + } + } + ++ public final PoiManager getPoiStorage() { return this.getPoiManager(); } // Paper - OBFHELPER + public PoiManager getPoiManager() { + return this.getChunkSource().getPoiManager(); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index b9d32e3322c2cce1aca2a90df71b6175a6f8c548..25b26a78a55f98687ed22e986b54d5e9d47a16ea 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -54,6 +54,7 @@ public class PoiManager extends SectionStorage { + ((PoiSection) this.getOrCreate(SectionPos.of(pos).asLong())).add(pos, type); + } + ++ public void remove(BlockPos blockposition) { this.remove(blockposition); } // Paper - OBFHELPER + public void remove(BlockPos pos) { + ((PoiSection) this.getOrCreate(SectionPos.of(pos).asLong())).remove(pos); + } +@@ -138,6 +139,7 @@ public class PoiManager extends SectionStorage { + return ((PoiSection) this.getOrCreate(SectionPos.of(pos).asLong())).release(pos); + } + ++ public final boolean test(BlockPos blockposition, Predicate predicate) { return this.exists(blockposition, predicate); } // Paper - OBFHELPER + public boolean exists(BlockPos pos, Predicate predicate) { + return (Boolean) this.getOrLoad(SectionPos.of(pos).asLong()).map((villageplacesection) -> { + return villageplacesection.exists(pos, predicate); diff --git a/Remapped-Spigot-Server-Patches/0644-Fix-villager-boat-exploit.patch b/Remapped-Spigot-Server-Patches/0644-Fix-villager-boat-exploit.patch new file mode 100644 index 000000000..bc4c9d15d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0644-Fix-villager-boat-exploit.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Mon, 11 Jan 2021 12:43:51 -0800 +Subject: [PATCH] Fix villager boat exploit + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7e07fd0c8dec9f9cdeda65dfa0ccf42b4dde010e..9d1116f601b79dabf7a0d9e5ecf5c2a0306f9aa4 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -78,6 +78,7 @@ import net.minecraft.util.Mth; + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.entity.npc.AbstractVillager; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; +@@ -617,6 +618,15 @@ public abstract class PlayerList { + + for (Iterator iterator = entity.getIndirectPassengers().iterator(); iterator.hasNext(); entity1.removed = true) { + entity1 = (Entity) iterator.next(); ++ // Paper start ++ if (entity1 instanceof AbstractVillager) { ++ final AbstractVillager villager = (AbstractVillager) entity1; ++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ // Paper end + worldserver.despawn(entity1); + } + diff --git a/Remapped-Spigot-Server-Patches/0645-Entity-load-save-limit-per-chunk.patch b/Remapped-Spigot-Server-Patches/0645-Entity-load-save-limit-per-chunk.patch new file mode 100644 index 000000000..a9cb53ca6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0645-Entity-load-save-limit-per-chunk.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 18 Nov 2020 20:52:25 -0800 +Subject: [PATCH] Entity load/save limit per chunk + +Adds a config option to limit the number of entities saved and loaded +to a chunk. The default values of -1 disable the limit. Although +defaults are only included for certain entites, this allows setting +limits for any entity type. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 7ebc85264a2cbfb601dfe5472b561cac1a7cf8bf..486e5438254348db68017228af131cba7defd637 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -7,6 +7,7 @@ import java.util.List; + import java.util.Map; + import java.util.stream.Collectors; + import net.minecraft.world.Difficulty; ++import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.monster.Vindicator; + import net.minecraft.world.entity.monster.Zombie; + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; +@@ -762,4 +763,18 @@ public class PaperWorldConfig { + Difficulty.class + ); + } ++ ++ public Map, Integer> entityPerChunkSaveLimits = new HashMap<>(); ++ private void entityPerChunkSaveLimits() { ++ getInt("entity-per-chunk-save-limit.experience_orb", -1); ++ getInt("entity-per-chunk-save-limit.snowball", -1); ++ getInt("entity-per-chunk-save-limit.ender_pearl", -1); ++ getInt("entity-per-chunk-save-limit.arrow", -1); ++ EntityType.getEntityNameList().forEach(name -> { ++ final EntityType type = EntityType.getByName(name.getPath()).orElseThrow(() -> new IllegalStateException("Unknown Entity Type: " + name.toString())); ++ final String path = ".entity-per-chunk-save-limit." + name.getPath(); ++ final int value = config.getInt("world-settings." + worldName + path, config.getInt("world-settings.default" + path, -1)); // get without setting defaults ++ if (value != -1) entityPerChunkSaveLimits.put(type, value); ++ }); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index f6a814f9305813eaafa56baa0327e0111cd4e38c..30f80f8549c3236d6bfe594e323e4ca6e702005d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -539,11 +539,22 @@ public class ChunkSerializer { + + chunk.setLastSaveHadEntities(false); + ++ // Paper start ++ final Map, Integer> savedEntityCounts = Maps.newHashMap(); + for (int j = 0; j < chunk.getEntitySlices().length; ++j) { + Iterator iterator1 = chunk.getEntitySlices()[j].iterator(); + + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); ++ final EntityType entityType = entity.getType(); ++ final int saveLimit = worldserver.paperConfig.entityPerChunkSaveLimits.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (savedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ continue; ++ } ++ savedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + CompoundTag nbttagcompound4 = new CompoundTag(); + // Paper start + if (asyncsavedata == null && !entity.removed && (int) Math.floor(entity.getX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk.getPos().z) { +@@ -674,10 +685,21 @@ public class ChunkSerializer { + ListTag nbttaglist = tag.getList("Entities", 10); + Level world = chunk.getLevel(); + ++ // Paper start ++ final Map, Integer> loadedEntityCounts = Maps.newHashMap(); + for (int i = 0; i < nbttaglist.size(); ++i) { + CompoundTag nbttagcompound1 = nbttaglist.getCompound(i); + + EntityType.loadEntityRecursive(nbttagcompound1, world, (entity) -> { ++ final EntityType entityType = entity.getType(); ++ final int saveLimit = world.paperConfig.entityPerChunkSaveLimits.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ return null; ++ } ++ loadedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + chunk.addEntity(entity); + return entity; + }); diff --git a/Remapped-Spigot-Server-Patches/0646-Add-sendOpLevel-API.patch b/Remapped-Spigot-Server-Patches/0646-Add-sendOpLevel-API.patch new file mode 100644 index 000000000..82ff3e57c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0646-Add-sendOpLevel-API.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Tue, 29 Dec 2020 15:03:03 +0100 +Subject: [PATCH] Add sendOpLevel API + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 9d1116f601b79dabf7a0d9e5ecf5c2a0306f9aa4..c962b6fc0c65dc5e2ea636220727bca63bf4b740 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1134,22 +1134,29 @@ public abstract class PlayerList { + } + + private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { +- if (player.connection != null) { ++ // Paper start - add recalculatePermissions parameter ++ this.sendPlayerOperatorStatus(player, permissionLevel, true); ++ } ++ public void sendPlayerOperatorStatus(ServerPlayer entityplayer, int i, boolean recalculatePermissions) { ++ // Paper end ++ if (entityplayer.connection != null) { + byte b0; + +- if (permissionLevel <= 0) { ++ if (i <= 0) { + b0 = 24; +- } else if (permissionLevel >= 4) { ++ } else if (i >= 4) { + b0 = 28; + } else { +- b0 = (byte) (24 + permissionLevel); ++ b0 = (byte) (24 + i); + } + +- player.connection.send(new ClientboundEntityEventPacket(player, b0)); ++ entityplayer.connection.send(new ClientboundEntityEventPacket(entityplayer, b0)); + } + +- player.getBukkitEntity().recalculatePermissions(); // CraftBukkit +- this.server.getCommands().sendCommands(player); ++ if (recalculatePermissions) { // Paper ++ entityplayer.getBukkitEntity().recalculatePermissions(); // CraftBukkit ++ this.server.getCommands().sendCommands(entityplayer); ++ } // Paper + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 40380fff222cc1f3340cf6a6c4afbe60aaa5d3a6..d2d179cdef8129653983b01d94928ba83f64f644 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2296,6 +2296,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + ? (org.bukkit.entity.Firework) entity.getBukkitEntity() + : null; + } ++ ++ @Override ++ public void sendOpLevel(byte level) { ++ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); ++ ++ this.getHandle().getServer().getPlayerList().sendPlayerOperatorStatus(this.getHandle(), level, false); ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0647-Add-StructureLocateEvent.patch b/Remapped-Spigot-Server-Patches/0647-Add-StructureLocateEvent.patch new file mode 100644 index 000000000..6aaa2cdea --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0647-Add-StructureLocateEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Wed, 16 Sep 2020 01:12:29 -0700 +Subject: [PATCH] Add StructureLocateEvent + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index 860af116dbc7dd9d691ff27d28a2d10dbec83df4..29310d96eb562ead1e568a97b6f3019e43ca0a88 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.chunk; + + import com.google.common.collect.Lists; + import com.mojang.serialization.Codec; ++import io.papermc.paper.event.world.StructureLocateEvent; // Paper - Add import due to naming conflict. + import java.util.BitSet; + import java.util.Iterator; + import java.util.List; +@@ -160,6 +161,22 @@ public abstract class ChunkGenerator { + + @Nullable + public BlockPos findNearestMapFeature(ServerLevel world, StructureFeature feature, BlockPos center, int radius, boolean skipExistingChunks) { ++ // Paper start ++ org.bukkit.World world1 = world.getWorld(); ++ org.bukkit.Location originLocation = new org.bukkit.Location(world1, center.getX(), center.getY(), center.getZ()); ++ StructureLocateEvent event = new StructureLocateEvent(world1, originLocation, org.bukkit.StructureType.getStructureTypes().get(feature.getFeatureName()), radius, skipExistingChunks); ++ if(!event.callEvent()) return null; ++ // If event call set a final location, skip structure finding and just return set result. ++ if(event.getResult() != null) return new BlockPos(event.getResult().getBlockX(), event.getResult().getBlockY(), event.getResult().getBlockZ()); ++ // Get origin location (re)defined by event call. ++ center = new BlockPos(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ()); ++ // Get world (re)defined by event call. ++ world = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); ++ // Get radius and whether to find unexplored structures (re)defined by event call. ++ radius = event.getRadius(); ++ skipExistingChunks = event.shouldFindUnexplored(); ++ feature = StructureFeature.STRUCTURES_REGISTRY.get(event.getType().getName()); ++ // Paper end + if (!this.biomeSource.canGenerateStructure(feature)) { + return null; + } else if (feature == StructureFeature.STRONGHOLD) { diff --git a/Remapped-Spigot-Server-Patches/0648-Collision-option-for-requiring-a-player-participant.patch b/Remapped-Spigot-Server-Patches/0648-Collision-option-for-requiring-a-player-participant.patch new file mode 100644 index 000000000..33e77f9b2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0648-Collision-option-for-requiring-a-player-participant.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:48:37 +0100 +Subject: [PATCH] Collision option for requiring a player participant + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 486e5438254348db68017228af131cba7defd637..3dd228ae8071a747f2cd7b2b46a2215183f72cd0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -717,6 +717,18 @@ public class PaperWorldConfig { + } + } + ++ public boolean onlyPlayersCollide = false; ++ public boolean allowVehicleCollisions = true; ++ private void onlyPlayersCollide() { ++ onlyPlayersCollide = getBoolean("only-players-collide", onlyPlayersCollide); ++ allowVehicleCollisions = getBoolean("allow-vehicle-collisions", allowVehicleCollisions); ++ if (onlyPlayersCollide && !allowVehicleCollisions) { ++ log("Collisions will only work if a player is one of the two entities colliding."); ++ } else if (onlyPlayersCollide) { ++ log("Collisions will only work if a player OR a vehicle is one of the two entities colliding."); ++ } ++ } ++ + public int wanderingTraderSpawnMinuteTicks = 1200; + public int wanderingTraderSpawnDayTicks = 24000; + public int wanderingTraderSpawnChanceFailureIncrement = 25; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ec98f5f59ca2b4cb58eb00ed8cdfa364f8bacd88..87c719caf796f54296ff7e412548062e02af270e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1465,6 +1465,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public void push(Entity entity) { + if (!this.isPassengerOfSameVehicle(entity)) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (this.level.paperConfig.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 9cbde70787d8044f0edeb3d459231dd7fbb79584..25df3ef6b96bec39847a732394af8eccdb4d5d45 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -21,6 +21,7 @@ import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.Tag; + import net.minecraft.util.Mth; +@@ -766,6 +767,7 @@ public abstract class AbstractMinecart extends Entity { + public void push(Entity entity) { + if (!this.level.isClientSide) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (!this.level.paperConfig.allowVehicleCollisions && this.level.paperConfig.onlyPlayersCollide && !(entity instanceof ServerPlayer)) return; // Paper + if (!this.hasPassenger(entity)) { + // CraftBukkit start + VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 6a9c18540886979b2212bf7917a21753c9a9db3c..e7ac3bff190c899397d6576fabbf4966878ea7e5 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -14,6 +14,7 @@ import net.minecraft.network.protocol.game.ServerboundPaddleBoatPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.tags.FluidTags; +@@ -229,6 +230,7 @@ public class Boat extends Entity { + + @Override + public void push(Entity entity) { ++ if (!this.level.paperConfig.allowVehicleCollisions && this.level.paperConfig.onlyPlayersCollide && !(entity instanceof ServerPlayer)) return; // Paper + if (entity instanceof Boat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { + // CraftBukkit start diff --git a/Remapped-Spigot-Server-Patches/0649-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch b/Remapped-Spigot-Server-Patches/0649-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch new file mode 100644 index 000000000..ed43990f0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0649-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 16 Jan 2021 14:30:12 -0500 +Subject: [PATCH] Remove ProjectileHitEvent call when fireballs dead + +The duplicate ProjectileHitEvent in EntityFireball was removed. The +event was always called before the duplicate call. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +index 872ff430547276e2a41a48aa07ae63b87ab39e5d..cba1b361162456cf297d88439f76586a2f61fc45 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +@@ -86,7 +86,7 @@ public abstract class AbstractHurtingProjectile extends Projectile { + + // CraftBukkit start - Fire ProjectileHitEvent + if (this.removed) { +- CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); ++ // CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); // Paper - this is an undesired duplicate event + } + // CraftBukkit end + } diff --git a/Remapped-Spigot-Server-Patches/0650-Return-chat-component-with-empty-text-instead-of-thr.patch b/Remapped-Spigot-Server-Patches/0650-Return-chat-component-with-empty-text-instead-of-thr.patch new file mode 100644 index 000000000..8f0e61039 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0650-Return-chat-component-with-empty-text-instead-of-thr.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CDFN +Date: Tue, 7 Jul 2020 17:53:23 +0200 +Subject: [PATCH] Return chat component with empty text instead of throwing + exception + + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index b5d79635cd8b0eb6b17962450b347010aeb52654..d2f762371f82d54bcec8b1a0a02d0866e55fd174 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -12,6 +12,7 @@ import net.minecraft.ReportedException; + import net.minecraft.core.NonNullList; + import net.minecraft.core.Registry; + import net.minecraft.network.chat.Component; ++import net.minecraft.network.chat.TextComponent; + import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; +@@ -60,7 +61,12 @@ public abstract class AbstractContainerMenu { + } + private Component title; + public final Component getTitle() { +- Preconditions.checkState(this.title != null, "Title not set"); ++ // Paper start - return chat component with empty text instead of throwing error ++ // Preconditions.checkState(this.title != null, "Title not set"); ++ if(this.title == null){ ++ return new TextComponent(""); ++ } ++ // Paper end + return this.title; + } + public final void setTitle(Component title) { diff --git a/Remapped-Spigot-Server-Patches/0651-Make-schedule-command-per-world.patch b/Remapped-Spigot-Server-Patches/0651-Make-schedule-command-per-world.patch new file mode 100644 index 000000000..24fcb7773 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0651-Make-schedule-command-per-world.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:52:44 -0800 +Subject: [PATCH] Make schedule command per-world + + +diff --git a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +index 122a790285734ecd95fe6817043a84cf1752dbe8..173ab97b01b2191d9d731a28f2690eb4f539880b 100644 +--- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +@@ -32,7 +32,7 @@ public class ScheduleCommand { + return new TranslatableComponent("commands.schedule.cleared.failure", new Object[]{object}); + }); + private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { +- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); ++ return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getLevel().worldDataServer.overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper + }; + + public static void register(CommandDispatcher dispatcher) { +@@ -55,7 +55,7 @@ public class ScheduleCommand { + } else { + long j = source.getLevel().getGameTime() + (long) i; + ResourceLocation minecraftkey = (ResourceLocation) pair.getFirst(); +- TimerQueue customfunctioncallbacktimerqueue = source.getServer().getWorldData().overworldData().getScheduledEvents(); ++ TimerQueue customfunctioncallbacktimerqueue = source.getLevel().worldDataServer.overworldData().getScheduledEvents(); // Paper + + ((Either) pair.getSecond()).ifLeft((customfunction) -> { + String s = minecraftkey.toString(); +@@ -81,7 +81,7 @@ public class ScheduleCommand { + } + + private static int remove(CommandSourceStack commandlistenerwrapper, String s) throws CommandSyntaxException { +- int i = commandlistenerwrapper.getServer().getWorldData().overworldData().getScheduledEvents().remove(s); ++ int i = commandlistenerwrapper.getLevel().worldDataServer.overworldData().getScheduledEvents().remove(s); // Paper + + if (i == 0) { + throw ScheduleCommand.ERROR_CANT_REMOVE.create(s); diff --git a/Remapped-Spigot-Server-Patches/0652-Configurable-max-leash-distance.patch b/Remapped-Spigot-Server-Patches/0652-Configurable-max-leash-distance.patch new file mode 100644 index 000000000..42d46d765 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0652-Configurable-max-leash-distance.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 21:04:03 -0800 +Subject: [PATCH] Configurable max leash distance + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3dd228ae8071a747f2cd7b2b46a2215183f72cd0..c4ca7ed5b251a2a3d64297351ef32541a4243c35 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -166,6 +166,12 @@ public class PaperWorldConfig { + } + } + ++ public float maxLeashDistance = 10f; ++ private void maxLeashDistance() { ++ maxLeashDistance = getFloat("max-leash-distance", maxLeashDistance); ++ log("Max leash distance: " + maxLeashDistance); ++ } ++ + public boolean disableEndCredits; + private void disableEndCredits() { + disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 7c82d453388a27b69207d051dec316fc14715e2b..a884940cc576704951d42c6b0d00f5a319297c29 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -46,7 +46,7 @@ public abstract class PathfinderMob extends Mob { + float f = this.distanceTo(entity); + + if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { +- if (f > 10.0F) { ++ if (f > entity.level.paperConfig.maxLeashDistance) { // Paper + this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.dropLeash(true, true); + } +@@ -55,7 +55,7 @@ public abstract class PathfinderMob extends Mob { + } + + this.onLeashDistance(f); +- if (f > 10.0F) { ++ if (f > entity.level.paperConfig.maxLeashDistance) { // Paper + this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.dropLeash(true, true); + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); diff --git a/Remapped-Spigot-Server-Patches/0653-Implement-BlockPreDispenseEvent.patch b/Remapped-Spigot-Server-Patches/0653-Implement-BlockPreDispenseEvent.patch new file mode 100644 index 000000000..fea75084e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0653-Implement-BlockPreDispenseEvent.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Sun, 17 Jan 2021 13:16:09 +1000 +Subject: [PATCH] Implement BlockPreDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index 2a4cb76bdfcf55ba222b4976359c1b8efb165009..08ce586f8d024b57a20031868ca2a3058bc500ee 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -88,6 +88,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); + + if (idispensebehavior != DispenseItemBehavior.NOOP) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(worldserver, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here + eventFired = false; // CraftBukkit - reset event status + tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 586d21eed8189adf696ca6d3642afebbe752d1b5..34c0216baa69206aca51821aec421484b18cb04c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -59,6 +59,7 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++import io.papermc.paper.event.block.BlockPreDispenseEvent; // Paper + import org.bukkit.Bukkit; + import org.bukkit.Location; // Paper + import org.bukkit.Material; +@@ -1802,5 +1803,11 @@ public class CraftEventFactory { + BlockFailedDispenseEvent event = new BlockFailedDispenseEvent(block); + return event.callEvent(); + } ++ ++ public static boolean handleBlockPreDispenseEvent(ServerLevel worldserver, BlockPos blockposition, ItemStack itemStack, int slot) { ++ org.bukkit.block.Block block = worldserver.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ BlockPreDispenseEvent event = new BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); ++ return event.callEvent(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0654-Added-Vanilla-Entity-Tags.patch b/Remapped-Spigot-Server-Patches/0654-Added-Vanilla-Entity-Tags.patch new file mode 100644 index 000000000..fcf1ba26d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0654-Added-Vanilla-Entity-Tags.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 20:03:35 -0800 +Subject: [PATCH] Added Vanilla Entity Tags + + +diff --git a/src/main/java/io/papermc/paper/CraftEntityTag.java b/src/main/java/io/papermc/paper/CraftEntityTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..687edf189871fc989174248dbf070bcba161f1a8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/CraftEntityTag.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper; ++ ++import org.bukkit.craftbukkit.tag.CraftTag; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.entity.EntityType; ++ ++import java.util.Collections; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.tags.TagCollection; ++ ++public class CraftEntityTag extends CraftTag, EntityType> { ++ ++ public CraftEntityTag(TagCollection> registry, ResourceLocation tag) { ++ super(registry, tag); ++ } ++ ++ @Override ++ public boolean isTagged(EntityType item) { ++ return getHandle().isTagged(CraftMagicNumbers.getEntityTypes(item)); ++ } ++ ++ @Override ++ public Set getValues() { ++ return Collections.unmodifiableSet(getHandle().getTagged().stream().map(CraftMagicNumbers::getEntityType).collect(Collectors.toSet())); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 046fbc646d2818bb2c7e08ff22093523e8246523..3d7cc98710bb925743e6fe8de1f154096334d46c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2221,6 +2221,11 @@ public final class CraftServer implements Server { + Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type"); + + return (org.bukkit.Tag) new CraftFluidTag(console.getTags().getFluids(), key); ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_ENTITIES: ++ Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity namespace must have entitytype type"); ++ return (org.bukkit.Tag) new io.papermc.paper.CraftEntityTag(console.getTags().getEntityTypes(), key); ++ // Paper end + default: + throw new IllegalArgumentException(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ac94fd569bd4c79e30adef148e09e395ba8c1812..25a29d997f163ce2b11330d66a691601f514a9cb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -116,8 +116,17 @@ public final class CraftMagicNumbers implements UnsafeValues { + private static final Map MATERIAL_ITEM = new HashMap<>(); + private static final Map MATERIAL_BLOCK = new HashMap<>(); + private static final Map MATERIAL_FLUID = new HashMap<>(); ++ // Paper start ++ private static final Map> ENTITY_TYPE_ENTITY_TYPES = new HashMap<>(); ++ private static final Map, org.bukkit.entity.EntityType> ENTITY_TYPES_ENTITY_TYPE = new HashMap<>(); + + static { ++ for (org.bukkit.entity.EntityType type : org.bukkit.entity.EntityType.values()) { ++ if (type == org.bukkit.entity.EntityType.UNKNOWN) continue; ++ ENTITY_TYPE_ENTITY_TYPES.put(type, net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey()))); ++ ENTITY_TYPES_ENTITY_TYPE.put(net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey())), type); ++ } ++ // Paper end + for (Block block : net.minecraft.core.Registry.BLOCK) { + BLOCK_MATERIAL.put(block, Material.getMaterial(net.minecraft.core.Registry.BLOCK.getKey(block).getPath().toUpperCase(Locale.ROOT))); + } +@@ -183,6 +192,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + public static ResourceLocation key(Material mat) { + return CraftNamespacedKey.toMinecraft(mat.getKey()); + } ++ // Paper start ++ public static net.minecraft.world.entity.EntityType getEntityTypes(org.bukkit.entity.EntityType type) { ++ return ENTITY_TYPE_ENTITY_TYPES.get(type); ++ } ++ public static org.bukkit.entity.EntityType getEntityType(net.minecraft.world.entity.EntityType entityTypes) { ++ return ENTITY_TYPES_ENTITY_TYPE.get(entityTypes); ++ } ++ // Paper end + // ======================================================================== + // Paper start + @Override diff --git a/Remapped-Spigot-Server-Patches/0655-added-Wither-API.patch b/Remapped-Spigot-Server-Patches/0655-added-Wither-API.patch new file mode 100644 index 000000000..175023cea --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0655-added-Wither-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Jul 2020 15:39:19 -0700 +Subject: [PATCH] added Wither API + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 1f330d852eb9b3a36570542e10a88ae065798714..fd91c80cd6337b5fa41d6060ecdb44b8fa68a16a 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -80,6 +80,11 @@ public class WitherBoss extends Monster implements RangedAttackMob { + return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); + }; + private static final TargetingConditions TARGETING_CONDITIONS = (new TargetingConditions()).range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); ++ // Paper start ++ private boolean canPortal = false; ++ ++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } ++ // Paper end + + public WitherBoss(EntityType type, Level world) { + super(type, world); +@@ -578,6 +583,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id); + } + ++ public final boolean isPowered() { return this.isPowered(); } // Paper - OBFHELPER + public boolean isPowered() { + return this.getHealth() <= this.getMaxHealth() / 2.0F; + } +@@ -594,7 +600,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + + @Override + public boolean canChangeDimensions() { +- return false; ++ return super.canChangeDimensions() && canPortal; // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index fdcd680b972da54f9cdb41dff5563e42bd12d8e3..a09f46c586416b77dda40067fe1639a9250af3f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -38,4 +38,31 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + public BossBar getBossBar() { + return bossBar; + } ++ ++ // Paper start ++ @Override ++ public boolean isCharged() { ++ return getHandle().isPowered(); ++ } ++ ++ @Override ++ public int getInvulnerableTicks() { ++ return getHandle().getInvulnerableTicks(); ++ } ++ ++ @Override ++ public void setInvulnerableTicks(int ticks) { ++ getHandle().setInvulnerableTicks(ticks); ++ } ++ ++ @Override ++ public boolean canTravelThroughPortals() { ++ return getHandle().canChangeDimensions(); ++ } ++ ++ @Override ++ public void setCanTravelThroughPortals(boolean value) { ++ getHandle().setCanTravelThroughPortals(value); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0656-Added-firing-of-PlayerChangeBeaconEffectEvent.patch b/Remapped-Spigot-Server-Patches/0656-Added-firing-of-PlayerChangeBeaconEffectEvent.patch new file mode 100644 index 000000000..e2d21ff09 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0656-Added-firing-of-PlayerChangeBeaconEffectEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 15:14:51 -0600 +Subject: [PATCH] Added firing of PlayerChangeBeaconEffectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index 1371bfe4a4b5bb065de4d2118b2b32f4ee0b78d9..20069eeece4e03827ed4b3b4e2b713c43b23a366 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -11,6 +11,10 @@ import net.minecraft.world.level.block.Blocks; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + // CraftBukkit end + ++// Paper start ++import io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent; ++// Paper end ++ + public class BeaconMenu extends AbstractContainerMenu { + + private final Container beacon; +@@ -137,9 +141,15 @@ public class BeaconMenu extends AbstractContainerMenu { + + public void updateEffects(int primaryEffectId, int secondaryEffectId) { + if (this.paymentSlot.hasItem()) { +- this.beaconData.set(1, primaryEffectId); +- this.beaconData.set(2, secondaryEffectId); ++ // Paper start ++ PlayerChangeBeaconEffectEvent event = new PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), org.bukkit.potion.PotionEffectType.getById(primaryEffectId), org.bukkit.potion.PotionEffectType.getById(secondaryEffectId), this.access.getLocation().getBlock()); ++ if (event.callEvent()) { ++ this.beaconData.set(1, event.getPrimary() == null ? 0 : event.getPrimary().getId()); ++ this.beaconData.set(2, event.getSecondary() == null ? 0 : event.getSecondary().getId()); ++ if (!event.willConsumeItem()) return; + this.paymentSlot.a(1); ++ } ++ // Paper end + } + + } diff --git a/Remapped-Spigot-Server-Patches/0657-Fix-console-spam-when-removing-chests-in-water.patch b/Remapped-Spigot-Server-Patches/0657-Fix-console-spam-when-removing-chests-in-water.patch new file mode 100644 index 000000000..239c089fb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0657-Fix-console-spam-when-removing-chests-in-water.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Thu, 19 Nov 2020 02:07:10 +0000 +Subject: [PATCH] Fix console spam when removing chests in water + + +diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +index 6b95cd2e2af66eef324dfcc8f7642da2f9e39d4e..d061548b5490f441b91a2dd90e7668a05f7f2112 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java +@@ -239,7 +239,7 @@ public class ChestBlock extends AbstractChestBlock implements + @Override + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { + if (!state.is(newState.getBlock())) { +- BlockEntity tileentity = world.getBlockEntity(pos); ++ BlockEntity tileentity = world.getTileEntity(pos, false); // Paper - Don't validate TE - Fix console spam when removing chests in water + + if (tileentity instanceof Container) { + Containers.dropContents(world, pos, (Container) tileentity); diff --git a/Remapped-Spigot-Server-Patches/0658-Add-toggle-for-always-placing-the-dragon-egg.patch b/Remapped-Spigot-Server-Patches/0658-Add-toggle-for-always-placing-the-dragon-egg.patch new file mode 100644 index 000000000..2dafd0617 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0658-Add-toggle-for-always-placing-the-dragon-egg.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Nov 2020 11:47:24 +0000 +Subject: [PATCH] Add toggle for always placing the dragon egg + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c4ca7ed5b251a2a3d64297351ef32541a4243c35..40939de88b1a8169dbfc7a0cd288c2fe9b706426 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -795,4 +795,9 @@ public class PaperWorldConfig { + if (value != -1) entityPerChunkSaveLimits.put(type, value); + }); + } ++ ++ public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false; ++ private void enderDragonsDeathAlwaysPlacesDragonEgg() { ++ enderDragonsDeathAlwaysPlacesDragonEgg = getBoolean("ender-dragons-death-always-places-dragon-egg", enderDragonsDeathAlwaysPlacesDragonEgg); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 84447e9845edad2d228b94184b35b4afb453a14b..e2f784b771b12bd646d519938c33b1c86cc2686d 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -396,7 +396,7 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); +- if (!this.previouslyKilled) { ++ if (this.level.paperConfig.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg + this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); + } + diff --git a/Remapped-Spigot-Server-Patches/0659-Added-PlayerStonecutterRecipeSelectEvent.patch b/Remapped-Spigot-Server-Patches/0659-Added-PlayerStonecutterRecipeSelectEvent.patch new file mode 100644 index 000000000..aab6a1ebc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0659-Added-PlayerStonecutterRecipeSelectEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 27 Nov 2020 17:14:27 -0800 +Subject: [PATCH] Added PlayerStonecutterRecipeSelectEvent + +Co-Authored-By: MiniDigger + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index d2f762371f82d54bcec8b1a0a02d0866e55fd174..b5eeb2749237d589eafdfbea073bfe90e609600b 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -117,7 +117,7 @@ public abstract class AbstractContainerMenu { + return slot; + } + +- protected DataSlot addDataSlot(DataSlot containerproperty) { ++ protected DataSlot addDataSlot(DataSlot containerproperty) { return addDataSlot(containerproperty); } protected DataSlot addDataSlot(DataSlot containerproperty) { // Paper - OBFHELPER + this.dataSlots.add(containerproperty); + return containerproperty; + } +diff --git a/src/main/java/net/minecraft/world/inventory/DataSlot.java b/src/main/java/net/minecraft/world/inventory/DataSlot.java +index 56d99e39f8cfe46a780bd17a0f99c3cbbe01c719..e851d6c8e5ad58091a58d489a48cd3ec379ce0da 100644 +--- a/src/main/java/net/minecraft/world/inventory/DataSlot.java ++++ b/src/main/java/net/minecraft/world/inventory/DataSlot.java +@@ -20,7 +20,7 @@ public abstract class DataSlot { + }; + } + +- public static DataSlot shared(final int[] array, final int index) { ++ public static DataSlot shared(final int[] aint, final int i) { return shared(aint, i); } public static DataSlot shared(final int[] array, final int index) { // Paper - OBFHELPER + return new DataSlot() { + @Override + public int get() { +@@ -54,7 +54,7 @@ public abstract class DataSlot { + + public abstract void set(int value); + +- public boolean checkAndClearUpdateFlag() { ++ public boolean checkAndClearUpdateFlag() { return checkAndClearUpdateFlag(); } public boolean checkAndClearUpdateFlag() { // Paper - OBFHELPER + int i = this.get(); + boolean flag = i != this.prevValue; + +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index 072bac443e7c54ac2b92e1d93b757bdacf230fbb..beb02f953719170d1668ada1c09d073d84bb7baf 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -21,13 +21,14 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; // Paper + + public class StonecutterMenu extends AbstractContainerMenu { + + private final ContainerLevelAccess access; + private final DataSlot selectedRecipeIndex; + private final Level level; +- private List recipes; ++ private List recipes; public final List getRecipes() { return this.recipes; } // Paper - OBFHELPER + private ItemStack input; + private long lastSoundTime; + final Slot inputSlot; +@@ -57,7 +58,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + + public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.STONECUTTER, syncId); +- this.selectedRecipeIndex = DataSlot.standalone(); ++ this.selectedRecipeIndex = addDataSlot(DataSlot.shared(new int[1], 0)); // Paper - allow replication + this.recipes = Lists.newArrayList(); + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { +@@ -135,13 +136,36 @@ public class StonecutterMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (this.isValidRecipeIndex(id)) { +- this.selectedRecipeIndex.set(id); ++ // Paper start ++ int recipeIndex = id; ++ this.selectedRecipeIndex.set(recipeIndex); ++ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed ++ if (this.isValidRecipeIndex(id)) { ++ PlayerStonecutterRecipeSelectEvent event = new PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), (org.bukkit.inventory.StonecutterInventory) getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) this.getRecipes().get(id).toBukkitRecipe()); ++ if (!event.callEvent()) { ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ int newRecipeIndex; ++ if (!this.getRecipes().get(recipeIndex).getId().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { // If the recipe did NOT stay the same ++ for (newRecipeIndex = 0; newRecipeIndex < this.getRecipes().size(); newRecipeIndex++) { ++ if (this.getRecipes().get(newRecipeIndex).getId().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { ++ recipeIndex = newRecipeIndex; ++ break; ++ } ++ } ++ } ++ } ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it ++ // Paper end + this.setupResultSlot(); + } + + return true; + } + ++ private boolean isValidRecipeIndex(int index) { return this.isValidRecipeIndex(index); } // Paper - OBFHELPER + private boolean isValidRecipeIndex(int i) { + return i >= 0 && i < this.recipes.size(); + } diff --git a/Remapped-Spigot-Server-Patches/0660-Add-dropLeash-variable-to-EntityUnleashEvent.patch b/Remapped-Spigot-Server-Patches/0660-Add-dropLeash-variable-to-EntityUnleashEvent.patch new file mode 100644 index 000000000..33a094ba1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0660-Add-dropLeash-variable-to-EntityUnleashEvent.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Fri, 29 Jan 2021 15:13:11 +0100 +Subject: [PATCH] Add dropLeash variable to EntityUnleashEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 87dd6c012bf1ca6a1e8df44dc0957c4c67d02adc..ea34306858116e5626383af408529091836c2752 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -86,6 +86,7 @@ import org.bukkit.event.entity.EntityTargetEvent; + import org.bukkit.event.entity.EntityTransformEvent; + import org.bukkit.event.entity.EntityUnleashEvent; + import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++import org.bukkit.event.player.PlayerUnleashEntityEvent; // Paper + // CraftBukkit end + + public abstract class Mob extends LivingEntity { +@@ -1205,12 +1206,15 @@ public abstract class Mob extends LivingEntity { + return InteractionResult.PASS; + } else if (this.getLeashHolder() == player) { + // CraftBukkit start - fire PlayerUnleashEntityEvent +- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player).isCancelled()) { ++ // Paper start - drop leash variable ++ PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, !player.abilities.instabuild); ++ if (event.isCancelled()) { ++ // Paper end + ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder())); + return InteractionResult.PASS; + } + // CraftBukkit end +- this.dropLeash(true, !player.abilities.instabuild); ++ this.dropLeash(true, event.isDropLeash()); // Paper - drop leash variable + return InteractionResult.sidedSuccess(this.level.isClientSide); + } else { + InteractionResult enuminteractionresult = this.checkAndHandleImportantInteractions(player, hand); +@@ -1364,8 +1368,11 @@ public abstract class Mob extends LivingEntity { + + if (this.leashHolder != null) { + if (!this.isAlive() || !this.leashHolder.isAlive()) { +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + } +@@ -1433,8 +1440,11 @@ public abstract class Mob extends LivingEntity { + boolean flag1 = super.startRiding(entity, force); + + if (flag1 && this.isLeashed()) { +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + return flag1; +@@ -1636,7 +1646,10 @@ public abstract class Mob extends LivingEntity { + @Override + protected void removeAfterChangingDimensions() { + super.removeAfterChangingDimensions(); +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, false); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index a884940cc576704951d42c6b0d00f5a319297c29..d16a7bab5495d58ea9e6811d4b507667cfa3d264 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -47,8 +47,11 @@ public abstract class PathfinderMob extends Mob { + + if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { + if (f > entity.level.paperConfig.maxLeashDistance) { // Paper +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + return; +@@ -56,8 +59,11 @@ public abstract class PathfinderMob extends Mob { + + this.onLeashDistance(f); + if (f > entity.level.paperConfig.maxLeashDistance) { // Paper +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); + } else if (f > 6.0F) { + double d0 = (entity.getX() - this.getX()) / (double) f; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +index 465548de7e32028a2aed4b6e9543e1bd9b73700b..1f54c020cc2b1928b2e7edda9ddf7b9d61e6424b 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -28,6 +28,8 @@ import net.minecraft.world.phys.AABB; + import org.bukkit.craftbukkit.event.CraftEventFactory; + // CraftBukkit end + ++import org.bukkit.event.player.PlayerUnleashEntityEvent; // Paper ++ + public class LeashFenceKnotEntity extends HangingEntity { + + public LeashFenceKnotEntity(EntityType type, Level world) { +@@ -123,11 +125,14 @@ public class LeashFenceKnotEntity extends HangingEntity { + entityinsentient = (Mob) iterator.next(); + if (entityinsentient.isLeashed() && entityinsentient.getLeashHolder() == this) { + // CraftBukkit start +- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player).isCancelled()) { ++ // Paper start - drop leash variable ++ PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player, !player.abilities.instabuild); ++ if (event.isCancelled()) { ++ // Paper end + die = false; + continue; + } +- entityinsentient.dropLeash(true, !player.abilities.instabuild); // false -> survival mode boolean ++ entityinsentient.dropLeash(true, event.isDropLeash()); // false -> survival mode boolean // Paper - drop leash variable + // CraftBukkit end + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 34c0216baa69206aca51821aec421484b18cb04c..ea7c30ef17fc66c1fb55d5909f94651c98b181be 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1460,8 +1460,10 @@ public class CraftEventFactory { + return itemInHand; + } + +- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player) { +- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity()); ++ // Paper start - drop leash variable ++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, boolean dropLeash) { ++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), dropLeash); ++ // Paper end + entity.level.getCraftServer().getPluginManager().callEvent(event); + return event; + } diff --git a/Remapped-Spigot-Server-Patches/0661-Skip-distance-map-update-when-spawning-disabled.patch b/Remapped-Spigot-Server-Patches/0661-Skip-distance-map-update-when-spawning-disabled.patch new file mode 100644 index 000000000..fac284074 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0661-Skip-distance-map-update-when-spawning-disabled.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Beech Horn +Date: Fri, 14 Feb 2020 19:39:59 +0000 +Subject: [PATCH] Skip distance map update when spawning disabled. + + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 7a615a18f1f297adfe7e046407a019d8933e9ed9..8e27559a12ada05e0530c7fe5b0bfbc4422ccbd6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -825,7 +825,7 @@ public class ServerChunkCache extends ChunkSource { + int l = this.distanceManager.getNaturalSpawnChunkCount(); + // Paper start - per player mob spawning + NaturalSpawner.SpawnState spawnercreature_d; // moved down +- if (this.chunkMap.playerMobDistanceMap != null) { ++ if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't update when animals and monsters are disabled + // update distance map + this.level.timings.playerMobDistanceMapUpdate.startTiming(); + this.chunkMap.playerMobDistanceMap.update(this.level.players, this.chunkMap.viewDistance); diff --git a/Remapped-Spigot-Server-Patches/0662-Reset-shield-blocking-on-dimension-change.patch b/Remapped-Spigot-Server-Patches/0662-Reset-shield-blocking-on-dimension-change.patch new file mode 100644 index 000000000..4349f8f4d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0662-Reset-shield-blocking-on-dimension-change.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Sun, 24 Jan 2021 08:55:19 -0800 +Subject: [PATCH] Reset shield blocking on dimension change + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ff831ca0cbc0cabbf78178c609ccf70d78da7980..314f168c9d17ab3654c9dda07e48839570f0d332 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1115,6 +1115,11 @@ public class ServerPlayer extends Player implements ContainerListener { + this.level.getCraftServer().getPluginManager().callEvent(changeEvent); + // CraftBukkit end + } ++ // Paper start ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end + + return this; + } diff --git a/Remapped-Spigot-Server-Patches/0663-add-DragonEggFormEvent.patch b/Remapped-Spigot-Server-Patches/0663-add-DragonEggFormEvent.patch new file mode 100644 index 000000000..17cbf7f9a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0663-add-DragonEggFormEvent.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Mon, 25 Jan 2021 14:53:57 +0100 +Subject: [PATCH] add DragonEggFormEvent + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index e2f784b771b12bd646d519938c33b1c86cc2686d..f32e2c71929a73258e4eb521c160c247690744d2 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -57,6 +57,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfigur + import net.minecraft.world.phys.AABB; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.event.block.DragonEggFormEvent; // Paper - DragonEggFormEvent + + public class EndDragonFight { + +@@ -396,9 +397,24 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); ++ // Paper start - DragonEggFormEvent ++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getPosition()); ++ org.bukkit.craftbukkit.block.CraftBlock eggBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = new org.bukkit.craftbukkit.block.CraftBlockState(eggBlock); ++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); ++ DragonEggFormEvent eggEvent = new DragonEggFormEvent(eggBlock, eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - DragonEggFormEvent + if (this.level.paperConfig.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg +- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); ++ // Paper start - DragonEggFormEvent ++ //this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); ++ } else { ++ eggEvent.setCancelled(true); ++ } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); + } ++ // Paper end - DragonEggFormEvent + + this.previouslyKilled = true; + this.dragonKilled = true; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/EndPodiumFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/EndPodiumFeature.java +index cd24e9db1e9a490117716d4883376bb6b59c7c67..e085607f4033476e80b7dcd7b026449c12a47cf6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/EndPodiumFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/EndPodiumFeature.java +@@ -14,7 +14,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConf + + public class EndPodiumFeature extends Feature { + +- public static final BlockPos END_PODIUM_LOCATION = BlockPos.ZERO; ++ public static final BlockPos END_PODIUM_LOCATION = BlockPos.ZERO; public static BlockPos getPosition() { return END_PODIUM_LOCATION; } // Paper - OBFHELPER + private final boolean active; + + public EndPodiumFeature(boolean open) { +@@ -22,43 +22,43 @@ public class EndPodiumFeature extends Feature { + this.active = open; + } + +- public boolean place(WorldGenLevel world, ChunkGenerator chunkGenerator, Random random, BlockPos pos, NoneFeatureConfiguration config) { +- Iterator iterator = BlockPos.betweenClosed(new BlockPos(pos.getX() - 4, pos.getY() - 1, pos.getZ() - 4), new BlockPos(pos.getX() + 4, pos.getY() + 32, pos.getZ() + 4)).iterator(); ++ public boolean generate(WorldGenLevel generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPos blockposition, NoneFeatureConfiguration worldgenfeatureemptyconfiguration) { // Paper - decompile fix ++ Iterator iterator = BlockPos.betweenClosed(new BlockPos(blockposition.getX() - 4, blockposition.getY() - 1, blockposition.getZ() - 4), new BlockPos(blockposition.getX() + 4, blockposition.getY() + 32, blockposition.getZ() + 4)).iterator(); + + while (iterator.hasNext()) { + BlockPos blockposition1 = (BlockPos) iterator.next(); +- boolean flag = blockposition1.closerThan((Vec3i) pos, 2.5D); ++ boolean flag = blockposition1.closerThan((Vec3i) blockposition, 2.5D); + +- if (flag || blockposition1.closerThan((Vec3i) pos, 3.5D)) { +- if (blockposition1.getY() < pos.getY()) { ++ if (flag || blockposition1.closerThan((Vec3i) blockposition, 3.5D)) { ++ if (blockposition1.getY() < blockposition.getY()) { + if (flag) { +- this.setBlock(world, blockposition1, Blocks.BEDROCK.defaultBlockState()); +- } else if (blockposition1.getY() < pos.getY()) { +- this.setBlock(world, blockposition1, Blocks.END_STONE.defaultBlockState()); ++ this.setBlock(generatoraccessseed, blockposition1, Blocks.BEDROCK.defaultBlockState()); ++ } else if (blockposition1.getY() < blockposition.getY()) { ++ this.setBlock(generatoraccessseed, blockposition1, Blocks.END_STONE.defaultBlockState()); + } +- } else if (blockposition1.getY() > pos.getY()) { +- this.setBlock(world, blockposition1, Blocks.AIR.defaultBlockState()); ++ } else if (blockposition1.getY() > blockposition.getY()) { ++ this.setBlock(generatoraccessseed, blockposition1, Blocks.AIR.defaultBlockState()); + } else if (!flag) { +- this.setBlock(world, blockposition1, Blocks.BEDROCK.defaultBlockState()); ++ this.setBlock(generatoraccessseed, blockposition1, Blocks.BEDROCK.defaultBlockState()); + } else if (this.active) { +- this.setBlock(world, new BlockPos(blockposition1), Blocks.END_PORTAL.defaultBlockState()); ++ this.setBlock(generatoraccessseed, new BlockPos(blockposition1), Blocks.END_PORTAL.defaultBlockState()); + } else { +- this.setBlock(world, new BlockPos(blockposition1), Blocks.AIR.defaultBlockState()); ++ this.setBlock(generatoraccessseed, new BlockPos(blockposition1), Blocks.AIR.defaultBlockState()); + } + } + } + + for (int i = 0; i < 4; ++i) { +- this.setBlock(world, pos.above(i), Blocks.BEDROCK.defaultBlockState()); ++ this.setBlock(generatoraccessseed, blockposition.above(i), Blocks.BEDROCK.defaultBlockState()); + } + +- BlockPos blockposition2 = pos.above(2); ++ BlockPos blockposition2 = blockposition.above(2); + Iterator iterator1 = Direction.Plane.HORIZONTAL.iterator(); + + while (iterator1.hasNext()) { + Direction enumdirection = (Direction) iterator1.next(); + +- this.setBlock(world, blockposition2.relative(enumdirection), (BlockState) Blocks.WALL_TORCH.defaultBlockState().setValue(WallTorchBlock.FACING, enumdirection)); ++ this.setBlock(generatoraccessseed, blockposition2.relative(enumdirection), (BlockState) Blocks.WALL_TORCH.defaultBlockState().setValue(WallTorchBlock.FACING, enumdirection)); + } + + return true; diff --git a/Remapped-Spigot-Server-Patches/0664-EntityMoveEvent.patch b/Remapped-Spigot-Server-Patches/0664-EntityMoveEvent.patch new file mode 100644 index 000000000..eec865bf5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0664-EntityMoveEvent.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 892ca65d258b0745be95d7ef4886c49899b24d92..bc44811f26076871848ba8f5c582ab26b1fd7170 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -13,6 +13,7 @@ import io.netty.buffer.ByteBuf; + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; + import io.papermc.paper.adventure.PaperAdventure; // Paper ++import io.papermc.paper.event.entity.EntityMoveEvent; + import it.unimi.dsi.fastutil.longs.LongIterator; + import java.awt.image.BufferedImage; + import java.io.BufferedWriter; +@@ -1458,6 +1459,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper ++ worldserver.hasEntityMoveEvent = EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + HopperBlockEntity.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.profiler.push(() -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 9f1838d12b13d64f10871eb672ed2aec78d9936e..338b4c382fb8ea349ce81f2009e96de1df7ac5e2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -207,6 +207,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper ++ public boolean hasEntityMoveEvent = false; // Paper + private static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 02ddb84c563b3149c4f1b0e24899ce8a21ad61bb..8bc74878919ab7cf6a50d425da61f1b8a8b0ee44 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -10,6 +10,7 @@ import com.mojang.datafixers.util.Pair; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.DynamicOps; ++import io.papermc.paper.event.entity.EntityMoveEvent; + import java.util.Collection; + import java.util.ConcurrentModificationException; + import java.util.Iterator; +@@ -2909,6 +2910,20 @@ public abstract class LivingEntity extends Entity { + + this.pushEntities(); + this.level.getProfiler().pop(); ++ // Paper start ++ if (((ServerLevel) level).hasEntityMoveEvent) { ++ if (xo != getX() || yo != getY() || zo != getZ() || yRotO != yRot || xRotO != xRot) { ++ Location from = new Location(level.getWorld(), xo, yo, zo, yRotO, xRotO); ++ Location to = new Location (level.getWorld(), getX(), getY(), getZ(), yRot, xRot); ++ EntityMoveEvent event = new EntityMoveEvent(getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end + if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurt(DamageSource.DROWN, 1.0F); + } diff --git a/Remapped-Spigot-Server-Patches/0665-added-option-to-disable-pathfinding-updates-on-block.patch b/Remapped-Spigot-Server-Patches/0665-added-option-to-disable-pathfinding-updates-on-block.patch new file mode 100644 index 000000000..ca84c216d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0665-added-option-to-disable-pathfinding-updates-on-block.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Mon, 25 Jan 2021 14:37:57 +0100 +Subject: [PATCH] added option to disable pathfinding updates on block changes + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 40939de88b1a8169dbfc7a0cd288c2fe9b706426..bbb1d0ed9e76f414dc7d73b4f7786891425f55cd 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -800,4 +800,9 @@ public class PaperWorldConfig { + private void enderDragonsDeathAlwaysPlacesDragonEgg() { + enderDragonsDeathAlwaysPlacesDragonEgg = getBoolean("ender-dragons-death-always-places-dragon-egg", enderDragonsDeathAlwaysPlacesDragonEgg); + } ++ ++ public boolean updatePathfindingOnBlockUpdate = true; ++ private void setUpdatePathfindingOnBlockUpdate() { ++ updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 338b4c382fb8ea349ce81f2009e96de1df7ac5e2..a7553a856b9c99bee8f75d514b97cfab952bfd33 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1678,6 +1678,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { + this.getChunkSource().blockChanged(pos); ++ if(this.paperConfig.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape voxelshape = oldState.getCollisionShape(this, pos); + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + +@@ -1706,6 +1707,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + this.tickingEntities = wasTicking; // Paper + } ++ } // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0666-Inline-shift-direction-fields.patch b/Remapped-Spigot-Server-Patches/0666-Inline-shift-direction-fields.patch new file mode 100644 index 000000000..bf3e677ed --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0666-Inline-shift-direction-fields.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 18 Jan 2021 20:45:25 -0500 +Subject: [PATCH] Inline shift direction fields + +Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the +critical section for much of the server, including the lighting engine. + +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 3ebc62fe93a0cd0048e07b0343fc724f2c056010..51217f7e5288162b8e76c8717506b393cd262537 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -53,6 +53,11 @@ public enum Direction implements StringRepresentable { + }, (enumdirection, enumdirection1) -> { + throw new IllegalArgumentException("Duplicate keys"); + }, Long2ObjectOpenHashMap::new)); ++ // Paper start ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end + + private Direction(int i, int j, int k, String s, Direction.AxisDirection enumdirection_enumaxisdirection, Direction.Axis enumdirection_enumaxis, Vec3i baseblockposition) { + this.data3d = i; +@@ -62,6 +67,11 @@ public enum Direction implements StringRepresentable { + this.axis = enumdirection_enumaxis; + this.axisDirection = enumdirection_enumaxisdirection; + this.normal = baseblockposition; ++ // Paper start ++ this.adjX = baseblockposition.getX(); ++ this.adjY = baseblockposition.getY(); ++ this.adjZ = baseblockposition.getZ(); ++ // Paper end + } + + public static Direction[] orderedByNearest(Entity entity) { +@@ -137,15 +147,15 @@ public enum Direction implements StringRepresentable { + } + + public int getStepX() { +- return this.normal.getX(); ++ return this.adjX; // Paper + } + + public int getStepY() { +- return this.normal.getY(); ++ return this.adjY; // Paper + } + + public int getStepZ() { +- return this.normal.getZ(); ++ return this.adjZ; // Paper + } + + public String getName() { diff --git a/Remapped-Spigot-Server-Patches/0667-Allow-adding-items-to-BlockDropItemEvent.patch b/Remapped-Spigot-Server-Patches/0667-Allow-adding-items-to-BlockDropItemEvent.patch new file mode 100644 index 000000000..973fb6de6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0667-Allow-adding-items-to-BlockDropItemEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 20 Jan 2021 14:23:37 -0600 +Subject: [PATCH] Allow adding items to BlockDropItemEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index ea7c30ef17fc66c1fb55d5909f94651c98b181be..5145968c9c6ccabfb15b91102f82e8a3a2d3cf82 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -389,13 +389,30 @@ public class CraftEventFactory { + } + + public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { +- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); ++ // Paper start ++ List list = new ArrayList<>(); ++ for (ItemEntity item : items) { ++ list.add((Item) item.getBukkitEntity()); ++ } ++ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); ++ // Paper end + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (ItemEntity item : items) { +- item.level.addFreshEntity(item); ++ // Paper start ++ for (Item bukkit : list) { ++ if (!bukkit.isValid()) { ++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); ++ item.level.addFreshEntity(item); ++ } ++ } ++ } else { ++ for (Item bukkit : list) { ++ if (bukkit.isValid()) { ++ bukkit.remove(); ++ } + } ++ // Paper end + } + } + diff --git a/Remapped-Spigot-Server-Patches/0668-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/Remapped-Spigot-Server-Patches/0668-Add-getMainThreadExecutor-to-BukkitScheduler.patch new file mode 100644 index 000000000..f2acd9e21 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0668-Add-getMainThreadExecutor-to-BukkitScheduler.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aleksander Jagiello +Date: Sun, 24 Jan 2021 22:17:54 +0100 +Subject: [PATCH] Add getMainThreadExecutor to BukkitScheduler + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 13e461ffb2ee2e7d0440c0f60809ea99629b843c..0be39dac4b9dd69d7d73d86d64cf1e33e4086e81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -635,4 +635,15 @@ public class CraftScheduler implements BukkitScheduler { + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); + } ++ ++ // Paper start - add getMainThreadExecutor ++ @Override ++ public Executor getMainThreadExecutor(Plugin plugin) { ++ Validate.notNull(plugin, "Plugin cannot be null"); ++ return command -> { ++ Validate.notNull(command, "Command cannot be null"); ++ this.runTask(plugin, command); ++ }; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0669-living-entity-allow-attribute-registration.patch b/Remapped-Spigot-Server-Patches/0669-living-entity-allow-attribute-registration.patch new file mode 100644 index 000000000..78a227087 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0669-living-entity-allow-attribute-registration.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Sat, 24 Oct 2020 16:37:44 +0200 +Subject: [PATCH] living entity allow attribute registration + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index a501f334ce0bcc606dd2bb186cf7195102cd6c09..8acd102c0778e4e546e5191b6098eacbd15bd9f9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger; + public class AttributeMap { + + private static final Logger LOGGER = LogManager.getLogger(); +- private final Map attributes = Maps.newHashMap(); ++ private final Map attributes = Maps.newHashMap(); private final Map attributeMap = attributes; // Paper - OBFHELPER + private final Set dirtyAttributes = Sets.newHashSet(); + private final AttributeSupplier supplier; + +@@ -135,4 +135,12 @@ public class AttributeMap { + } + + } ++ ++ // Paper - start ++ public void registerAttribute(Attribute attributeBase) { ++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); ++ attributeMap.put(attributeBase, attributeModifiable); ++ } ++ // Paper - end ++ + } +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index 320fd6780af2fa99e4e4f4193cbc9338d492dc6d..a57b16679889f5b20c74712651f94d6796b8c661 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -38,6 +38,14 @@ public class CraftAttributeMap implements Attributable { + return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); + } + ++ // Paper start ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ Preconditions.checkArgument(attribute != null, "attribute"); ++ handle.registerAttribute(CraftAttributeMap.toMinecraft(attribute)); ++ } ++ // Paper end ++ + public static net.minecraft.world.entity.ai.attributes.Attribute toMinecraft(Attribute attribute) { + return net.minecraft.core.Registry.ATTRIBUTE.get(CraftNamespacedKey.toMinecraft(attribute.getKey())); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index a8d21382d5859edfd12e01a48924ce780790b4b7..eefb6bd580ea176c3a242695ab4af46e7c61b492 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -663,6 +663,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().craftAttributes.getAttribute(attribute); + } + ++ // Paper start ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ getHandle().craftAttributes.registerAttribute(attribute); ++ } ++ // Paper end ++ + @Override + public void setAI(boolean ai) { + if (this.getHandle() instanceof Mob) { diff --git a/Remapped-Spigot-Server-Patches/0670-fix-dead-slime-setSize-invincibility.patch b/Remapped-Spigot-Server-Patches/0670-fix-dead-slime-setSize-invincibility.patch new file mode 100644 index 000000000..72df11355 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0670-fix-dead-slime-setSize-invincibility.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Fri, 5 Feb 2021 22:12:13 +0100 +Subject: [PATCH] fix dead slime setSize invincibility + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index 340036135588d06e43cbd229dd3a6613b04bb9ab..d1bb7e1f7f7837774512e0af0c8b855d34d5a85b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -17,7 +17,7 @@ public class CraftSlime extends CraftMob implements Slime { + + @Override + public void setSize(int size) { +- getHandle().setSize(size, true); ++ getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0671-Merchant-getRecipes-should-return-an-immutable-list.patch b/Remapped-Spigot-Server-Patches/0671-Merchant-getRecipes-should-return-an-immutable-list.patch new file mode 100644 index 000000000..c4baf4a04 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0671-Merchant-getRecipes-should-return-an-immutable-list.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 10 Feb 2021 14:53:36 -0800 +Subject: [PATCH] Merchant#getRecipes should return an immutable list + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java +index 3437ec2c7c1a84debb7d7b7c90283d7e25208604..b4bcbd6329c67ea3deeeb1bf38233ebd15e922b7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java +@@ -24,7 +24,7 @@ public class CraftMerchant implements Merchant { + + @Override + public List getRecipes() { +- return Collections.unmodifiableList(Lists.transform(merchant.getOffers(), new Function() { ++ return com.google.common.collect.ImmutableList.copyOf(Lists.transform(merchant.getOffers(), new Function() { // Paper - javadoc says 'an immutable list of trades' - not 'an unmodifiable view of a list of trades'. fixes issue with setRecipes(getRecipes()) + @Override + public MerchantRecipe apply(net.minecraft.world.item.trading.MerchantOffer recipe) { + return recipe.asBukkit(); diff --git a/Remapped-Spigot-Server-Patches/0672-misc-debugging-dumps.patch b/Remapped-Spigot-Server-Patches/0672-misc-debugging-dumps.patch new file mode 100644 index 000000000..f56af1f8b --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0672-misc-debugging-dumps.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 18 Feb 2021 20:23:28 +0000 +Subject: [PATCH] misc debugging dumps + + +diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2d5494d2813b773e60ddba6790b750a9a08f21f8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/TraceUtil.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Bukkit; ++ ++public final class TraceUtil { ++ ++ public static void dumpTraceForThread(Thread thread, String reason) { ++ Bukkit.getLogger().warning(thread.getName() + ": " + reason); ++ StackTraceElement[] trace = thread.getStackTrace(); ++ for (StackTraceElement traceElement : trace) { ++ Bukkit.getLogger().warning("\tat " + traceElement); ++ } ++ } ++ ++ public static void dumpTraceForThread(String reason) { ++ new Throwable(reason).printStackTrace(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index bc44811f26076871848ba8f5c582ab26b1fd7170..9b654fed2a00740cef84cf72258abfc7aeafc0c2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -14,6 +14,7 @@ import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; + import io.papermc.paper.adventure.PaperAdventure; // Paper + import io.papermc.paper.event.entity.EntityMoveEvent; ++import io.papermc.paper.util.TraceUtil; + import it.unimi.dsi.fastutil.longs.LongIterator; + import java.awt.image.BufferedImage; + import java.io.BufferedWriter; +@@ -855,6 +856,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 20 Feb 2021 13:09:59 -0500 +Subject: [PATCH] Add support for hex color codes in console + +Converts upstream's hex color code legacy format into actual hex color codes in the console. + +diff --git a/src/main/java/io/papermc/paper/console/HexFormattingConverter.java b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4315961b7a465fb4872a4d67e7c26d4b4ed1fb9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java +@@ -0,0 +1,178 @@ ++package io.papermc.paper.console; ++ ++import net.minecrell.terminalconsole.TerminalConsoleAppender; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.config.Configuration; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.layout.PatternLayout; ++import org.apache.logging.log4j.core.pattern.*; ++import org.apache.logging.log4j.util.PerformanceSensitive; ++import org.apache.logging.log4j.util.PropertiesUtil; ++ ++import java.util.List; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++import static net.minecrell.terminalconsole.MinecraftFormattingConverter.KEEP_FORMATTING_PROPERTY; ++ ++/** ++ * Modified version of ++ * TerminalConsoleAppender's MinecraftFormattingConverter to support hex color codes using the md_5 &x&r&r&g&g&b&b format. ++ */ ++@Plugin(name = "paperMinecraftFormatting", category = PatternConverter.CATEGORY) ++@ConverterKeys({ "paperMinecraftFormatting" }) ++@PerformanceSensitive("allocation") ++public final class HexFormattingConverter extends LogEventPatternConverter { ++ ++ private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY); ++ ++ private static final String ANSI_RESET = "\u001B[m"; ++ ++ private static final char COLOR_CHAR = '§'; ++ private static final String LOOKUP = "0123456789abcdefklmnor"; ++ ++ private static final String RGB_ANSI = "\u001B[38;2;%d;%d;%dm"; ++ private static final Pattern NAMED_PATTERN = Pattern.compile(COLOR_CHAR + "[0-9a-fk-orA-FK-OR]"); ++ private static final Pattern RGB_PATTERN = Pattern.compile(COLOR_CHAR + "x(" + COLOR_CHAR + "[0-9a-fA-F]){6}"); ++ ++ private static final String[] ansiCodes = new String[] { ++ "\u001B[0;30m", // Black §0 ++ "\u001B[0;34m", // Dark Blue §1 ++ "\u001B[0;32m", // Dark Green §2 ++ "\u001B[0;36m", // Dark Aqua §3 ++ "\u001B[0;31m", // Dark Red §4 ++ "\u001B[0;35m", // Dark Purple §5 ++ "\u001B[0;33m", // Gold §6 ++ "\u001B[0;37m", // Gray §7 ++ "\u001B[0;30;1m", // Dark Gray §8 ++ "\u001B[0;34;1m", // Blue §9 ++ "\u001B[0;32;1m", // Green §a ++ "\u001B[0;36;1m", // Aqua §b ++ "\u001B[0;31;1m", // Red §c ++ "\u001B[0;35;1m", // Light Purple §d ++ "\u001B[0;33;1m", // Yellow §e ++ "\u001B[0;37;1m", // White §f ++ "\u001B[5m", // Obfuscated §k ++ "\u001B[21m", // Bold §l ++ "\u001B[9m", // Strikethrough §m ++ "\u001B[4m", // Underline §n ++ "\u001B[3m", // Italic §o ++ ANSI_RESET, // Reset §r ++ }; ++ ++ private final boolean ansi; ++ private final List formatters; ++ ++ /** ++ * Construct the converter. ++ * ++ * @param formatters The pattern formatters to generate the text to manipulate ++ * @param strip If true, the converter will strip all formatting codes ++ */ ++ protected HexFormattingConverter(List formatters, boolean strip) { ++ super("paperMinecraftFormatting", null); ++ this.formatters = formatters; ++ this.ansi = !strip; ++ } ++ ++ @Override ++ public void format(LogEvent event, StringBuilder toAppendTo) { ++ int start = toAppendTo.length(); ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0, size = formatters.size(); i < size; i++) { ++ formatters.get(i).format(event, toAppendTo); ++ } ++ ++ if (KEEP_FORMATTING || toAppendTo.length() == start) { ++ // Skip replacement if disabled or if the content is empty ++ return; ++ } ++ ++ boolean useAnsi = ansi && TerminalConsoleAppender.isAnsiSupported(); ++ String content = toAppendTo.substring(start); ++ content = useAnsi ? convertRGBColors(content) : stripRGBColors(content); ++ format(content, toAppendTo, start, useAnsi); ++ } ++ ++ private static String convertRGBColors(String input) { ++ Matcher matcher = RGB_PATTERN.matcher(input); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ String s = matcher.group().replace(String.valueOf(COLOR_CHAR), "").replace('x', '#'); ++ int hex = Integer.decode(s); ++ int red = (hex >> 16) & 0xFF; ++ int green = (hex >> 8) & 0xFF; ++ int blue = hex & 0xFF; ++ String replacement = String.format(RGB_ANSI, red, green, blue); ++ matcher.appendReplacement(buffer, replacement); ++ } ++ matcher.appendTail(buffer); ++ return buffer.toString(); ++ } ++ ++ private static String stripRGBColors(String input) { ++ Matcher matcher = RGB_PATTERN.matcher(input); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ matcher.appendReplacement(buffer, ""); ++ } ++ matcher.appendTail(buffer); ++ return buffer.toString(); ++ } ++ ++ static void format(String content, StringBuilder result, int start, boolean ansi) { ++ int next = content.indexOf(COLOR_CHAR); ++ int last = content.length() - 1; ++ if (next == -1 || next == last) { ++ result.setLength(start); ++ result.append(content); ++ if (ansi) { ++ result.append(ANSI_RESET); ++ } ++ return; ++ } ++ ++ Matcher matcher = NAMED_PATTERN.matcher(content); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ int format = LOOKUP.indexOf(Character.toLowerCase(matcher.group().charAt(1))); ++ if (format != -1) { ++ matcher.appendReplacement(buffer, ansi ? ansiCodes[format] : ""); ++ } ++ } ++ matcher.appendTail(buffer); ++ ++ result.setLength(start); ++ result.append(buffer.toString()); ++ if (ansi) { ++ result.append(ANSI_RESET); ++ } ++ } ++ ++ /** ++ * Gets a new instance of the {@link HexFormattingConverter} with the ++ * specified options. ++ * ++ * @param config The current configuration ++ * @param options The pattern options ++ * @return The new instance ++ * ++ * @see HexFormattingConverter ++ */ ++ public static HexFormattingConverter newInstance(Configuration config, String[] options) { ++ if (options.length < 1 || options.length > 2) { ++ LOGGER.error("Incorrect number of options on paperMinecraftFormatting. Expected at least 1, max 2 received " + options.length); ++ return null; ++ } ++ if (options[0] == null) { ++ LOGGER.error("No pattern supplied on paperMinecraftFormatting"); ++ return null; ++ } ++ ++ PatternParser parser = PatternLayout.createPatternParser(config); ++ List formatters = parser.parse(options[0]); ++ boolean strip = options.length > 1 && "strip".equals(options[1]); ++ return new HexFormattingConverter(formatters, strip); ++ } ++ ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 8af159abd3d0cc94cf155fec5b384c42f69551bf..67da1aa7a21622fb231d19dede3775a282a4a12e 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -6,21 +6,21 @@ + + + +- ++ + + + ++ pattern="%highlightError{[%d{HH:mm:ss} %level]: %paperMinecraftFormatting{%msg}%n%xEx{full}}" /> + + + + + +- ++ + + + ++ pattern="[%d{HH:mm:ss}] [%t/%level]: %paperMinecraftFormatting{%msg}{strip}%n%xEx{full}" /> + + + diff --git a/Remapped-Spigot-Server-Patches/0674-Clear-SyncLoadInfo.patch b/Remapped-Spigot-Server-Patches/0674-Clear-SyncLoadInfo.patch new file mode 100644 index 000000000..08d721287 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0674-Clear-SyncLoadInfo.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Fri, 26 Feb 2021 16:10:53 -0600 +Subject: [PATCH] Clear SyncLoadInfo + +This patch merely adds the extra argument "clear" after /paper syncloadinfo to clear currently stored syncload info. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 6fad9329213e4e8a3ef9ce7fb568ad22484a11f3..a6b2b69a5a79fb8cea81e55018ee7f57c8820e56 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -295,6 +295,13 @@ public class PaperCommand extends Command { + sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); + return; + } ++ ++ if (args.length > 1 && args[1].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(ChatColor.GRAY + "Sync load data cleared."); ++ return; ++ } ++ + File file = new File(new File(new File("."), "debug"), + "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); + file.getParentFile().mkdirs(); +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +index 524f33371b9de1d4dd6972fe59ffbe1804d7c5f3..0bb4aaa546939b67a5d22865190f30478a9337c1 100644 +--- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -26,6 +26,10 @@ public class SyncLoadFinder { + public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); + } + ++ public static void clear() { ++ SYNC_LOADS.clear(); ++ } ++ + public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { + if (!ENABLED) { + return; diff --git a/Remapped-Spigot-Server-Patches/0675-Expose-Tracked-Players.patch b/Remapped-Spigot-Server-Patches/0675-Expose-Tracked-Players.patch new file mode 100644 index 000000000..7997af8fe --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0675-Expose-Tracked-Players.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Fri, 26 Feb 2021 16:24:25 -0600 +Subject: [PATCH] Expose Tracked Players + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 87c719caf796f54296ff7e412548062e02af270e..ec30f886585d407fbd122e05107ebca44895c585 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -170,7 +170,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + +- ChunkMap.TrackedEntity tracker; // Paper ++ public ChunkMap.TrackedEntity tracker; // Paper package private -> public + public boolean collisionLoadChunks = false; // Paper + public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index d2d179cdef8129653983b01d94928ba83f64f644..ec8c7499662c0a810f1337ebc0fa24d2f3ca79e7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -16,6 +16,7 @@ import java.net.InetSocketAddress; + import java.net.SocketAddress; + import java.util.ArrayList; + import java.util.Collection; ++import java.util.Collections; // Paper + import java.util.HashMap; + import java.util.HashSet; + import java.util.LinkedHashMap; +@@ -2305,6 +2306,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public Set getTrackedPlayers() { ++ if (entity.tracker == null) { ++ return Collections.emptySet(); ++ } ++ ++ Set set = new HashSet<>(entity.tracker.seenBy.size()); ++ for (ServerPlayer entityPlayer : entity.tracker.seenBy) { ++ set.add(entityPlayer.getBukkitEntity().getPlayer()); ++ } ++ return set; ++ } ++ // Paper end ++ + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { diff --git a/Remapped-Spigot-Server-Patches/0676-Remove-streams-from-SensorNearest.patch b/Remapped-Spigot-Server-Patches/0676-Remove-streams-from-SensorNearest.patch new file mode 100644 index 000000000..f305e8593 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0676-Remove-streams-from-SensorNearest.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Wed, 3 Mar 2021 12:48:48 +0100 +Subject: [PATCH] Remove streams from SensorNearest + +The behavioural nearby sensors are validated every tick on the entities +that registered the respective sensors and are therefore a good subject +to performance improvements. + +More specifically this commit replaces the Stream#filter usage with +ArrayList#removeIf as the removeIf method on an array list is heavily +optimized towards a single internal array re-allocation without any +further overhead on the removeIf call. + +The only negative of this change is the rather agressive diff these +patches introduce as the methods are basically being reimplemented +compared to the previous stream-based implementation. + +See: https://nipafx.dev/java-stream-performance/ + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index 91295b8501b1e9d60bf9a7e954ea7fbce9cdea7f..0cd5a19beeb6103dec454b9071cc2e40adf2d006 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -27,18 +27,16 @@ public class NearestItemSensor extends Sensor { + List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(8.0D, 4.0D, 8.0D), (entityitem) -> { + return true; + }); +- +- entity.getClass(); ++ // Paper start - remove streams in favour of lists + list.sort(Comparator.comparingDouble(entity::distanceToSqr)); +- Stream stream = list.stream().filter((entityitem) -> { +- return entity.wantsToPickUp(entityitem.getItem()); +- }).filter((entityitem) -> { +- return entityitem.closerThan((Entity) entity, 9.0D); +- }); +- +- entity.getClass(); +- Optional optional = stream.filter(entity::hasLineOfSight).findFirst(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); ++ ItemEntity nearest = null; ++ for (ItemEntity entityItem : list) { ++ if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 9.0D) && entity.canSee(entityItem)) { ++ nearest = entityItem; ++ break; ++ } ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +index bf38e8b465ae0f50e34b94e0d7830dfdc1be1d59..fa827377ef0ef7cb280d1d54e156e45579899e6c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +@@ -26,10 +26,12 @@ public class NearestLivingEntitySensor extends Sensor { + list.sort(Comparator.comparingDouble(entity::distanceToSqr)); + Brain behaviorcontroller = entity.getBrain(); + +- behaviorcontroller.setMemory(MemoryModuleType.MOBS, (Object) list); +- behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, list.stream().filter((entityliving1) -> { +- return doTick(entity, entityliving1); +- }).collect(Collectors.toList())); ++ behaviorcontroller.setMemory(MemoryModuleType.MOBS, list); // Paper - decompile error ++ // Paper start - remove streams in favour of lists ++ List visibleMobs = new java.util.ArrayList<>(list); ++ visibleMobs.removeIf(otherEntityLiving -> !Sensor.a(entity, otherEntityLiving)); ++ behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, visibleMobs); ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 41a9db2cc4af26baa7072b3c4cebc5357ff43301..fe7414293f144656a938de42524841592c9f40d4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -26,22 +26,26 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, LivingEntity entity) { +- Stream stream = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((entityplayer) -> { +- return entity.closerThan((Entity) entityplayer, 16.0D); +- }); ++ // Paper start - remove streams in favour of lists ++ List players = new java.util.ArrayList<>(world.players()); ++ players.removeIf(player -> !EntitySelector.notSpectator().test(player) || !entity.closerThan(player, 16.0D)); // Paper - removeIf only re-allocates once compared to iterator ++ players.sort(Comparator.comparingDouble(entity::distanceToSqr)); + +- entity.getClass(); +- List list = (List) stream.sorted(Comparator.comparingDouble(entity::h)).collect(Collectors.toList()); + Brain behaviorcontroller = entity.getBrain(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, (Object) list); +- List list1 = (List) list.stream().filter((entityhuman) -> { +- return doTick(entity, (LivingEntity) entityhuman); +- }).collect(Collectors.toList()); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, (Object) (list1.isEmpty() ? null : (Player) list1.get(0))); +- Optional optional = list1.stream().filter(EntitySelector.ATTACK_ALLOWED).findFirst(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, optional); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); ++ ++ Player nearest = null, nearestTargetable = null; ++ for (Player player : players) { ++ if (Sensor.a(entity, player)) { ++ if (nearest == null) nearest = player; ++ if (EntitySelector.canAITarget().test(player)) { ++ nearestTargetable = player; ++ break; // Both variables are assigned, no reason to loop further ++ } ++ } ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, nearestTargetable); ++ // Paper end + } + } diff --git a/Remapped-Spigot-Server-Patches/0677-do-not-create-unnecessary-copies-of-passenger-list.patch b/Remapped-Spigot-Server-Patches/0677-do-not-create-unnecessary-copies-of-passenger-list.patch new file mode 100644 index 000000000..e8d0daacf --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0677-do-not-create-unnecessary-copies-of-passenger-list.patch @@ -0,0 +1,239 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Sun, 13 Dec 2020 13:42:55 +0100 +Subject: [PATCH] do not create unnecessary copies of passenger list + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java +index a6ecb82d14ccab5d8229689a2a6cb67c579b1f71..cded79352dff0978e0d633eae9d9020b4dec1d4b 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java +@@ -15,7 +15,7 @@ public class ClientboundSetPassengersPacket implements Packet list = entity.getPassengers(); ++ List list = entity.passengers; // Paper - do not create a copy of the list + + this.passengers = new int[list.size()]; + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index f4dd30c8b3326db72d3b3068ee2291de6f15de7c..c17e827a976f509c8294df65335f12139cd36a9f 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -2312,7 +2312,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + list.add(entity); + } + +- if (!entity.getPassengers().isEmpty()) { ++ if (!entity.passengers.isEmpty()) { // Paper - do not copy list + list1.add(entity); + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 75e2274578c2c28de3d786372df0b4102337a2cc..e703233db7879c73378b3a06b2e89f7fcea97979 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -102,10 +102,10 @@ public class ServerEntity { + + public final void tick() { this.sendChanges(); } // Paper - OBFHELPER + public void sendChanges() { +- List list = this.entity.getPassengers(); ++ List list = this.entity.passengers; // Paper - do not copy list + + if (!list.equals(this.lastPassengers)) { +- this.lastPassengers = list; ++ this.lastPassengers = com.google.common.collect.ImmutableList.copyOf(list); // Paper - only copy list if something has changed + this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit + } + +@@ -375,7 +375,7 @@ public class ServerEntity { + } + } + +- if (!this.entity.getPassengers().isEmpty()) { ++ if (!this.entity.passengers.isEmpty()) { // Paper - do not create copy of list + consumer.accept(new ClientboundSetPassengersPacket(this.entity)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ec30f886585d407fbd122e05107ebca44895c585..d055b362459e5b4658aa220e16118ee6174c0de4 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2233,7 +2233,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + protected boolean canAddPassenger(Entity passenger) { +- return this.getPassengers().size() < 1; ++ return this.passengers.size() < 1; // Paper - do not copy list + } + + public final float getCollisionBorderSize() { return getPickRadius(); } // Paper - OBFHELPER +@@ -2329,7 +2329,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean isVehicle() { +- return !this.getPassengers().isEmpty(); ++ return !this.passengers.isEmpty(); // Paper - do not copy list + } + + public boolean rideableUnderWater() { +@@ -3141,7 +3141,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean hasPassenger(Entity passenger) { +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + Entity entity1; + +@@ -3157,7 +3157,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + public boolean hasPassenger(Class clazz) { +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + Entity entity; + +@@ -3174,7 +3174,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + + public Collection getIndirectPassengers() { + Set set = Sets.newHashSet(); +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); +@@ -3200,7 +3200,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + private void fillIndirectPassengers(boolean playersOnly, Set output) { + Entity entity; + +- for (Iterator iterator = this.getPassengers().iterator(); iterator.hasNext(); entity.fillIndirectPassengers(playersOnly, output)) { ++ for (Iterator iterator = this.passengers.iterator(); iterator.hasNext(); entity.fillIndirectPassengers(playersOnly, output)) { // Paper - do not copy list + entity = (Entity) iterator.next(); + if (!playersOnly || ServerPlayer.class.isAssignableFrom(entity.getClass())) { + output.add(entity); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +index 3d919e878908e19d598d70011c44cf980676f4f8..debf53a8bf6f062a237160a7b7e0a251a9756ef6 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +@@ -52,7 +52,7 @@ public class RunAroundLikeCrazyGoal extends Goal { + @Override + public void tick() { + if (!this.horse.isTamed() && this.horse.getRandom().nextInt(50) == 0) { +- Entity entity = (Entity) this.horse.getPassengers().get(0); ++ Entity entity = this.horse.passengers.isEmpty() ? null : this.horse.passengers.get(0); // Paper - do not copy list, fixed array out of bounds exception as well + + if (entity == null) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Pig.java b/src/main/java/net/minecraft/world/entity/animal/Pig.java +index e512a38ccbba93266f0234e3b2fcf7f62693039b..7a60c0b2c301e8cb768c39ad20f273a5921428cb 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Pig.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Pig.java +@@ -85,7 +85,7 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { + @Nullable + @Override + public Entity getControllingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index b298bcfb665b1036cd21445cec1518069eb08f06..5901e92a749af50166c517bda575d541554756f5 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -971,7 +971,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + @Nullable + @Override + public Entity getControllingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index e50d72c98f2ee3cd3349d2df9a0cdc47b733f7cd..ccc9d941b28ee090436a5958e1b48589d48d9d6f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -134,7 +134,7 @@ public class Ravager extends Raider { + @Nullable + @Override + public Entity getControllingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 25df3ef6b96bec39847a732394af8eccdb4d5d45..23421c4964c67a963a55ce08595c8de112a2ba6e 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -560,7 +560,7 @@ public abstract class AbstractMinecart extends Entity { + + vec3d1 = new Vec3(d8 * d4 / d6, vec3d1.y, d8 * d5 / d6); + this.setDeltaMovement(vec3d1); +- Entity entity = this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ Entity entity = this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + + if (entity instanceof Player) { + Vec3 vec3d2 = entity.getDeltaMovement(); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index e7ac3bff190c899397d6576fabbf4966878ea7e5..37f0e359ec858eebfa15d01f23a9ce0103816c8b 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -316,7 +316,7 @@ public class Boat extends Entity { + super.tick(); + this.tickLerp(); + if (this.isControlledByLocalInstance()) { +- if (this.getPassengers().isEmpty() || !(this.getPassengers().get(0) instanceof Player)) { ++ if (this.passengers.isEmpty() || !(this.passengers.get(0) instanceof Player)) { // Paper - do not copy list + this.setPaddleState(false, false); + } + +@@ -379,7 +379,7 @@ public class Boat extends Entity { + Entity entity = (Entity) list.get(j); + + if (!entity.hasPassenger(this)) { +- if (flag && this.getPassengers().size() < 2 && !entity.isPassenger() && entity.getBbWidth() < this.getBbWidth() && entity instanceof LivingEntity && !(entity instanceof WaterAnimal) && !(entity instanceof Player)) { ++ if (flag && this.passengers.size() < 2 && !entity.isPassenger() && entity.getBbWidth() < this.getBbWidth() && entity instanceof LivingEntity && !(entity instanceof WaterAnimal) && !(entity instanceof Player)) { // Paper - do not copy passenger list + entity.startRiding(this); + } else { + this.push(entity); +@@ -726,8 +726,8 @@ public class Boat extends Entity { + float f = 0.0F; + float f1 = (float) ((this.removed ? 0.009999999776482582D : this.getPassengersRidingOffset()) + passenger.getMyRidingOffset()); + +- if (this.getPassengers().size() > 1) { +- int i = this.getPassengers().indexOf(passenger); ++ if (this.passengers.size() > 1) { // Paper - do not copy list ++ int i = this.passengers.indexOf(passenger); // Paper - do not copy list + + if (i == 0) { + f = 0.2F; +@@ -746,7 +746,7 @@ public class Boat extends Entity { + passenger.yRot += this.deltaRotation; + passenger.setYHeadRot(passenger.getYHeadRot() + this.deltaRotation); + this.clampRotation(passenger); +- if (passenger instanceof Animal && this.getPassengers().size() > 1) { ++ if (passenger instanceof Animal && this.passengers.size() > 1) { // Paper - do not copy list + int j = passenger.getId() % 2 == 0 ? 90 : 270; + + passenger.setYBodyRot(((Animal) passenger).yBodyRot + (float) j); +@@ -906,13 +906,13 @@ public class Boat extends Entity { + + @Override + protected boolean canAddPassenger(Entity passenger) { +- return this.getPassengers().size() < 2 && !this.isEyeInFluid((Tag) FluidTags.WATER); ++ return this.passengers.size() < 2 && !this.isEyeInFluid((Tag) FluidTags.WATER); // Paper - do not copy list + } + + @Nullable + @Override + public Entity getControllingPassenger() { +- List list = this.getPassengers(); ++ List list = this.passengers; // Paper - do not copy list + + return list.isEmpty() ? null : (Entity) list.get(0); + } diff --git a/Remapped-Spigot-Server-Patches/0678-MC-29274-Fix-Wither-hostility-towards-players.patch b/Remapped-Spigot-Server-Patches/0678-MC-29274-Fix-Wither-hostility-towards-players.patch new file mode 100644 index 000000000..ec43d966c --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0678-MC-29274-Fix-Wither-hostility-towards-players.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheShermanTanker +Date: Thu, 1 Oct 2020 01:11:03 +0800 +Subject: [PATCH] MC-29274: Fix Wither hostility towards players + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index bbb1d0ed9e76f414dc7d73b4f7786891425f55cd..eb367b8feda8219a97a547c3ef6ab82d278d2f25 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -805,4 +805,10 @@ public class PaperWorldConfig { + private void setUpdatePathfindingOnBlockUpdate() { + updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate); + } ++ ++ public boolean fixWitherTargetingBug = false; ++ private void witherSettings() { ++ fixWitherTargetingBug = getBoolean("fix-wither-targeting-bug", false); ++ log("Withers properly target players: " + fixWitherTargetingBug); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index fd91c80cd6337b5fa41d6060ecdb44b8fa68a16a..b364b442d4cb1f3351850140b85c62c30c888bed 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -102,6 +102,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); ++ if(this.level.paperConfig.fixWitherTargetingBug) this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 0, false, false, null)); // Paper - Fix MC-29274 + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Mob.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR)); + } + diff --git a/Remapped-Spigot-Server-Patches/0679-Throw-proper-exception-on-empty-JsonList-file.patch b/Remapped-Spigot-Server-Patches/0679-Throw-proper-exception-on-empty-JsonList-file.patch new file mode 100644 index 000000000..cdc493b6d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0679-Throw-proper-exception-on-empty-JsonList-file.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 1 Nov 2020 16:43:11 +0100 +Subject: [PATCH] Throw proper exception on empty JsonList file + + +diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index e2982a8ac5448110378bc92247952332bdffe12c..71de59cadcbd214b9e3a91a6051f918d9b421b16 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -189,6 +189,7 @@ public abstract class StoredUserList> { + + try { + JsonArray jsonarray = (JsonArray) StoredUserList.GSON.fromJson(bufferedreader, JsonArray.class); ++ com.google.common.base.Preconditions.checkState(jsonarray != null, "The file \"" + this.file.getName() + "\" is either empty or corrupt"); // Paper + + this.map.clear(); + Iterator iterator = jsonarray.iterator(); diff --git a/Remapped-Spigot-Server-Patches/0680-Improve-ServerGUI.patch b/Remapped-Spigot-Server-Patches/0680-Improve-ServerGUI.patch new file mode 100644 index 000000000..46ebed8c6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0680-Improve-ServerGUI.patch @@ -0,0 +1,400 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> +Date: Sat, 3 Oct 2020 08:27:40 +0200 +Subject: [PATCH] Improve ServerGUI + +- Added logo to server frame +- Show tps in the server stats + +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +index c0923ec75ecced2e0a1c0d3ec2c046d69af3e9a9..a3e17c1cb54938908d72d3e86e43f4655f1db194 100644 +--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -57,9 +57,18 @@ public class RAMDetails extends JList { + public void update() { + GraphData data = RAMGraph.DATA.peekLast(); + Vector vector = new Vector<>(); ++ ++ double[] tps = new double[] {server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int g = 0; g < tps.length; g++) { ++ tpsAvg[g] = format( tps[g] ); ++ } + vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); + vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); + vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.getTickTimes())) + " ms"); ++ vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); ++ + setListData(vector); + } + +@@ -70,4 +79,9 @@ public class RAMDetails extends JList { + } + return ((double) total / (double) tickTimes.length) * 1.0E-6D; + } ++ ++ private static String format(double tps) ++ { ++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ } + } +diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +index 2567c588a1dcf732800e6cf87352b020c7bb84d6..ad912fae191777256dd88f6c863ec92f8b6a9c13 100644 +--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +@@ -31,6 +31,11 @@ import net.minecraft.DefaultUncaughtExceptionHandler; + import net.minecraft.server.dedicated.DedicatedServer; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++// Paper start ++import java.io.IOException; ++import java.util.Objects; ++import javax.imageio.ImageIO; ++// Paper end + + public class MinecraftServerGui extends JComponent { + +@@ -56,6 +61,15 @@ public class MinecraftServerGui extends JComponent { + jframe.pack(); + jframe.setLocationRelativeTo((Component) null); + jframe.setVisible(true); ++ jframe.setName("Minecraft server"); // Paper ++ ++ // Paper start - Add logo as frame image ++ try { ++ jframe.setIconImage(ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); ++ } catch (IOException ignore) { ++ } ++ // Paper end ++ + jframe.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowevent) { + if (!servergui.isClosing.getAndSet(true)) { +diff --git a/src/main/java/net/minecraft/server/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java +index 09414d04208a843f8d337569b53f61b34e64ed92..d2583c762fe655dd1d7bed1061e41cd08ac0c092 100644 +--- a/src/main/java/net/minecraft/server/gui/StatsComponent.java ++++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java +@@ -18,7 +18,7 @@ public class StatsComponent extends JComponent { + }); + private final int[] values = new int[256]; + private int vp; +- private final String[] msgs = new String[11]; ++ private final String[] msgs = new String[12]; public String[] getStatEntries() { return this.msgs; } // Paper - change size, OBFHELPER + private final MinecraftServer server; + private final Timer timer; + +@@ -37,8 +37,18 @@ public class StatsComponent extends JComponent { + private void tick() { + long i = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + ++ // Paper start - Add tps entry ++ double[] tps = org.bukkit.Bukkit.getTPS(); ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int g = 0; g < tps.length; g++) { ++ tpsAvg[g] = format( tps[g] ); ++ } + this.msgs[0] = "Memory use: " + i / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; + this.msgs[1] = "Avg tick: " + StatsComponent.DECIMAL_FORMAT.format(this.getAverage(this.server.tickTimes) * 1.0E-6D) + " ms"; ++ getStatEntries()[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); ++ // Paper end ++ + this.values[this.vp++ & 255] = (int) (i * 100L / Runtime.getRuntime().maxMemory()); + this.repaint(); + } +@@ -85,4 +95,10 @@ public class StatsComponent extends JComponent { + public void close() { + this.timer.stop(); + } ++ ++ // Paper - start Add tps entry ++ private static String format(double tps) { ++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ } ++ // Paper end + } +diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png +new file mode 100644 +index 0000000000000000000000000000000000000000..a7d785f60c884ee4ee487cc364402d66c3dc2ecc +GIT binary patch +literal 14310 +zcmXY21yoy2uugDycPmm{N^yd_Q`}vP7YOd|PH`>8wLo!q*HRn`6f5rV?*HD)IX5{c +zxpy-=`|Zxo_svGBD$Agwkf4A-AaprdNp;|J21vifLD%hMrT$lZeEex|7Xe0mqFAZArC{!qp)zJmbab@utqA`6 +z61Z~|e!k$IbXNT?PvGuuzT7G514$8e!}lsR>%nURMm+~pde``@(!O=ISt0%B93;Ez +za-qRi4n0Q>zQ2#2^_y08QOl3jT*!Ir5@<8VrFx(6f9sP|H8ttjftN;wrX>jP4BcG1;MfU5x^L`zc09u!bDBt#+ll=7@ +zB;}A$BKgu}V?#qfHvm`~pt%wG2y{MOc%B!8I`p|pc +zO#?sq!Zd&j8UPmvY4RQnfo>!6{a}GFV!}g@qu<3Wu$07X(O`vikNW$~q!ngF23Ls2 +z53p8js<-B_Qd?xX6rtq43Mdz(jOg2QXx#Wng_9^1^^~KqFNq{Kvb@Ap9}bf&xFA-C +z5+#cQ`#v$A=kd0O=agATcleBaxXf_(dnqbQz|cL9R&&Ni1omTs+6~YApmk)MCghxj +z1}mq&IU>1nEiF=q=PI`%jQbyRd=hVI83Sm{E-4uTc#w;NNwEW)C(C`xvWzY_%`_MmO +zD&g-sEaE)}6(&g)y-N&rNy;5@+{M`}!{60Y8wMgF5;HmO#B~hG`W$;7xLG*yF((rq +zxP6I#r#o`B3FppK{v(q1!C+YLFSfySDcHyoW!}EfzuCB1B|C5+oP}dtocnwkcNy1EZ6#5JX4=ePl&cu~0tMnt&79+I4%PaK>VqFx;r!QdNmnxlEqdU-QR%Nmu{aWP +zJxwXvt5fFTCOVgB)Zq +z%H0U=9q7Y0lu&1kc4zYT3*lHA@XJfoK>3WFM&WWf2u6^+wCm8##D$x@Gkw+t^HoO( +z4pxDRqg;$5S=t^k22H5^V3V0Qfy%Ogl8I%LD$52=7)J>Ki9Ej1HyEi_ujELlz8$-+?cdD1Zxi02kW0 +zaY=caFq4~s^R?zxcc3Z0X|az}Aww<{P$>6rk+5Di5J7$kWor0{Q&>+DWSBH^Gf`SP +zT{4}IOFh-hB7xwBdewq%de)q6QvxorV(()2>@j8i!kj)=^hN +zl_N{$9xTHHA;V&Zx#tX&1pOO;v^NiOP#_UK@J;;lp+OOhOOO2mlMdxM;Qv-mWG+^vzox|8t`w| +z=gPlM3)y6G*hfV1WwuMe>bO-vP9g`h5BqgO9x{ROBD;aPl>XDmvt(3PUxt|4RFRpK +z5OEtRz{(Oa_W_!Z4XHf#h;Z-~71XM7wlF*L!-#h_Uy2tGuy-rAZ)4{qE~feNkp}qf +zgvBtLkFPI~I7%C=OHZfPZz$j>L9)rb;l +z@J^dxncy52;wmHg=wC3|Xn6jPYCR7xc}~D0wNjoYxmoRh_zh=6@8coM1UQIa_z*1)cZPw4v40qoZQp-uy#DLv=oP +zX9b3vzFA2r8}|_AO8W1(OMG__0{1AUD&Z%&7-(>s+Z-X6Sv}G5QguIbZ3mYa--?09 +z;wNw?n=yAag4%m#w$$-YZ{(ZJUcwHfzu&!gykNjG)e}!=q8xy2_KS=ULsQwv45NK! +zVqqD8#S{vRjg4(Q6HM_F&tihNIQns<%DVjE$cv33ET>Dvc^#{z&#u&&9RgXO?ZLuebczKv#;! +zCS|2lIa37Bp#3RWj0$V3=I2>o40{(J^LD|EUH?!2;Z&HS*>7*V%{v1)wHaUP85mcX +z%q!K}Ntr*IzJD%++btJ;VQO*OjJL1t{GvR3cy@OC-~pe^bV?N`z0QKCr?Tom)4u%A +z3mi2k&eIgh0^rGI#Di+&3lrsy-r+}zwBkDQtswtPbkj!Y^l`{f!# +zLseC0M;DiifDa!({-G4{W$Wxsgv*(NX%HMyXhArVwY105dUHg?+=@6Sy8n@slS76x +zU7%PI8ToKm#qahfR;7kn#|t@9y(0EkooWBDqA1(mpO)>BBz))giBi8xVHlj#dR9U8 +zRo%`iBdlj8%_tRn^qa%T>{nsLLwTNld&WHLyfbPzv2W62m6q=Nsdxnk +z#{P==5!Lidx3bcr_qlUl%BX!xjywA?jv>FU^mJDa0zQT9Kw8RRHq>7B +zb~DXw0(oqBrOQunsm2ghWV2i1VmN{F?)U;0%*j{FEUxazAJ3)KSWomuhklkDi?5h*MTLDS5ma_Nk1sNZYzZ#$maGRyiXBzjG@(G__fuyBl(^A>s&{jF+J%5| +zv#7nD1XK806#_U_4#N2ANAxznk%;U$Y$z#{K*O07mADqx6LjACqwP<`HFV#C6Q*wx +z8JVP_qGF}V7B?^8)f*2F5AON7v$L~Kr?2}oPai_kG!_6MI(U`LS~+Mo*CSyrw>pPE +zllqxy +z^&rnDn4XA@AUY7~`1lwTCrm8KlVRqX&!kZFH&;i9@=R}UDxNSh*)Iq2U+#9}@ag1t +z%KUOEw0DXT)>hQoLTprY^z=BC=8NAyi3pZWT7A`?;rI<3%65Nqb93%pJ=!+dNtB>W +z7f3O-e-S7ZBgBntcyt~wOG_p$AU2zlGH8=%TEm+z8kLYReEMTkIo#2YiA=iKWrH); +zS%uT3xAyyY=!U)0Evpgx{{38MPR2nN<3913M<0O#YCO=TSt^4IzV3^D%2zC>t_OO} +z_h~AVOk+IIi$Ov;-g93a4j@WaekCC#HFm2_Vu9s)8-GbYtr{LgrxnSIN^PW9)!jYX +z?%-yssA~&R3F)C)wj5i|@!atCx?Qy%P1QEGSZm;iUNai`-F(8a%y+_a>CMzx$XEKx +z>sW|JbN36s+Y{4SZsrspH%UH=+Q6J`c&_-JLGL&5|$XUA1vFOC+rgoc&xT{dFT&pMaEBKwyD;plX0>2nla;jTlQ{!fn2M=Ak*=K*g% +zBm0-$ly1~}CT-5gv){jex9)7&b8u!a+vYHXU>=NF2>g3+_rN{(LUMGwRWKk49sS$v +zazyX8zZ1hwZ|U*5{fK@i@hRl*U%Q2cg+!iIfb)6W%S5F{91qinEZE%~4Gl>rBw9S< +zMP5$exl1jESyt}d~jo?hf`z^32b!}UGtJH+w9(0UrI#~Ei*ii&6z(AVE?(}k_A +zE9Z@mj7HF-ch46I0ipe3gapRj{=zk_J1E^b_JwdrhKi4ytBuwP)m>e$@9v`A{1N{h +zwUN6H=_W+h(a?rGaQ%%LP5C4)XiZ*`1uUwgqWvk`LyDD!Ps#Q5oI($KDJ%8n5kBi- +zghsLx`~mf<>WT)6-cJBbp|htk1NfkZ@e#B4@l?UH7!MDMpO?1NETGk_Eg{z!N3!D< +zWg8gtgS%b(0Bg7dw9u35xq)1vNdnM8iu7Eje*u?#sZ~%^q*HDaZC?5z4ZzhSA%ndS +z4&$M&7(|(9nWY%QShCnuN0 +z`n9&UeypypUgx;R+x;XM#8uDM{p`9~j<49)^dotHJVO*A@HL&g7F={FP#trj@{dzm +zeQUiqRWJ&pkKkA1O-|vOf8O1UQ$$0lIExffio|}F@ROV#MXcPH$ +z?$$kxAF@B#KT}u;R@SVyIO>1sw1!i?C(_013w9@?8$bKaLQi34zC$g*^}F&(%NEO6 +zQzD-^6}HQMnGJ{h$J*)HjSxjblWegsW&rLC8Ov_r_20jLjUS$Ptnm|p9fK%r0j+4; +z57^mjL&lISh8>DC;eB$B69$h4XxE3qU4T&zUpDeV@4g>or%D-x@qhie>6mqD959ck74(h?S0BA0}YQ18d?hr6}%}y{%ZNJ^-(?=Op~; +z#2-UNh)jH9>RXmvPJ(Y!8(uhyW|sFpyvv)AaNeljHj^Fx+RC +z!`@c->W1C^FUKHmG2w_atkdsMnzY+l!CV8havQ8-Gu)<8t{#V*2Pwp4h?ayXsi5Z> +zo!guta>TA~iv#iJpQkN>#)QF%As@2WgU&V_Y^qm#E*O}M_ijJfFWq}ts)-l4>D)kCqJJ@MG2$69ph0jzwI8ry1u8D@CyinC$oT?7S*Z}Eg +zYs}PWLqr4u@)w}#!{cMx;KxO6W2H6~3k$laJjAt+C{0mmCRnfs=OJYbh}HMh&e`#> +zj;jrpjqKCh41OK{FOS`@_sPP$iCm46G^EMNk8(l-1f>!gEV+4vMVRZ#8infUenP+k +zL^tBOHF^=)k&U-Tw{gfijqQ&^ +z-RHHII5yp}2|o8pTsf6x7$teW9Em!~iy2DN?D@|U)g%I6VG%JBO$|~;c~1Q^3|x`1 +z6HRbq1#~Ke)wWpALcc&@P;m+*sGavR0{aOx3=IwUE3YPWAwV45pzD$~02inxi7(6X +z$zk683M=_r#M*+6fQ)&FK0y|lm7JLwS)K=t&ZJk!U_-y%_o@fhr{s37MUEQOF*M)3 +zB$;4>Zx;Xk*(hwFjb>1iJ1f*D#nyWL{=>{2|9*^vCNN!%bF8Oe<`xz#s;jFz?;I}4M3lL;!fy_;J-E96Of+;sG%K=fZdR)99pJ}fM( +zq%(s8UrsEL{NrdF`!#RY+VjFyPpE_vtqPMM!MQ+QnE)+_g9Z^{4^;k&Sa^=w*yuxB_*Z!U%!3{_9Qr)Jfz4IeS#io4oj_Kqhq`HCUub|Ke!v$1-$v=kc+O#rlCej?%dhY +zxxKUTsFPG1nfoFp3%7@gh9S?vM0N27#*fpJyaX;Vy{!pt*}!9_mX9uC#J5RyjknW2Dm3dCvZYU +zSW?0kvI9!o2un}*%`AYhr^CQT1aZF=-Nt^atn@Kt%b2!hT(pK!|MclbBv3-<+6{>_ +z8toMfWc9rpOk(8|KW>Z-k>Fr(xc_+q9ocf`8!_n}XYUrW?Ax|*_|=5m*4F0V+46wJ +z1IGS^Z5t=0Zj86J2MfJc +zUq#WKCfhoB<;P2&&`*_G4^_0uqDR20m!>T8ay_rxSzA&9_v5##g6tzXTkx+KRfz32 +z9vvpp?+YxHTxDthCBu7)&Q052y4s9*$M4_2w-OdPyK?F-EBoUuSsIk@@(!gA*A_!0 +z2eu1y;-Q$Ut(M>8FCOtw?vZR-%*ly^x)<95vK@P0tJoZws@+M*NGhg_NU`!}DZnWBHQz%*@6))$BWN;EM0xAF+B4Mph#S??J?K+&viwPmes*n^HGDL9iBf +zCk|mDu46wwughN!isu&G((DO>Ws`(VLY?^#w=RONxUgFGby--Y=5NJ|(>qXOS`;lZhmXyMEyBdVM@jJh71E-})~`?t4w8^Kwy) +z<+KACjs!F^TS-;FT24_iWF+=l(nR}j7U#;Vd +z)IT3=b&}A}1PUKFa6DKfgHkJci!~7u?a%k9h7Rri^{y`|;;xNDoQbV}+oJ=LdApL}|77o@C= +z;~aed)XpbrMtt1x3gHPWxbliQH4nKBCew{9 +z*-_PTyn~`1VrwKcc4ZrhI^!MsZ{D0O0%O2!SHHi^Dfyr9*x*DGFKwc()b;q6nM*M7 +zvA$x_?$BMJJHN5HIn9Ps{_7-sn79~BZegaa5V;s(BA<5BnU?^AeJHXtd)cIj_UCjA +zW|N@MjV~vrJz{sE0Dzv}tXxUDQAXm)1(kX7C_ZVFX%!TlZ850i(P1A0BxaJu)#LcH +zoxMFRzxoxw$bM=B6gpuMD#vcsa^00?%=D+T9-dQqV*=zD|)W!3BLun2&^n)~$ +z2_^{i9~sGXOAsF_S=k&4mWJ@`mD+G%MiPTlhuomboeFNwHb(< +zVpVR!mwf;JmpO3JL|B%L-!;@7TG}+`HZA;-{VIlQGY|T=f|!9!S=!c?sq5|KeEQ*~ +zm!1xeZcJPbSsfjU9e>K|=Ni<+YgrIG!|5@|Z>4bjx+`1j^O-{QK8XARf +zUG$nLRiTEtt;)9F30rvw>nj)@vCF{$d7>o2n>}~Y2^^C79l@s`uXRZOcuy>^%2@t- +zRGv={pKlDXFUgvG_^DWGR==il1rIzn{$p4r(FVOQxZi!_*Ksfl2hR{Aj>01RbFAM= +zpr0wzMwlOwlkt4|JLK)$>VL+{4nv>^`yMa)T;(9f*B(9;{T+)_=M4dN>M&&hS-#(G +z)-sW(WxVkHR)`x#g)25Lu7qnN;~Q-bvKDZ=;^fyLy@okDpvt&ZU{!U)WVtmnp +zAN-CzM{jPFWep9NAKDDq@=kynkGi_GQ@Z2y_Wn)xc_q3-&+9`qdGy_{PF-2c^$)%x +zd0sonEJhtG*2|P*Q-f_3`Akk96HzBz2 +z!5tnJaCcA2hGQrSw*{F)epvfYX?7toP=O0dN +zizY2w`>O@4Vqff!dBhQ^><#TjMP}loM9ProiD-Og@$V=*zQ|Avg0D!+96lr^u(1fl +z3J52PHoJYDdvdiIW?q?JIC*r?88VruLx#bp0lys39v$(c6uC*j}2IFFh +zViOX|K+DH18cd9%Rgjs$*sXuoW<>p^Fv-7CV|zpgTUnj812pyyX-nhA4TZ^UyYY9; +z?}BOarTT1q;0xSTjV_DPWE11?Y2+wSA*ybzebDoy8JwhznKa6SvYxE$WswX7Z6pG$ +zsA2GgHFFL3^zA@XTYK{a+6$Q8di%@1-|q9U15y+~R-L7Kwx8*xr(FP{g*JDPa`e((jSl#~?Rx=3ne(nLfeP9k0grubJK +zU4euzZqt~$Cl%k^{-!e6YQZi|D3#+MUS}VsYZ)0S>y@)kyqRI?A_esvAu-{`1Uq@! +zC+b`wnMK&<_mitl+k@e*$*{&S>vayX*>D>Q5sw2FZ?l(8ff%(8lo<^mBMrwQXOXe+ +z*7sZdWzBTIwZO$y^F)qZL1XbOMY<@M_a56y{({Vg@YN<_y}toq41V%~w=+4ZQvg)X +zVw~l$z-sId^nKU%dlk7W(mG}eS&KV2BdYqNJnX-p=YrG&&`_m0fzA_|iKD${5?oL* +zdS$heR@%Q+(3!!T&k;tIN|v2j=UI))rgkvyC7MTTrKP3g>Fma@_R0`GE5(tL%sS$7 +zG41ag%(Y(xZ5cjlk=R~(3XC+$25r*Fo=G5OhGgR}i!nDoG?^sult?Eo*x$x6CH-3L@LtZ0dfq!Bbbw-S}RwlN%lpH8c=4l2qH +z1wRszHSPh~=esnWvXD8B{D4<}?}6cA+@Ob1760Is6`g!zl@WL(L&={LA}SxAt0>Tw +z%b7i^&yNKM;(vGcNwuxAK{g|S3Y1&pH_6U1G +z3M4zx5FU=O;=l_?VzQ-~bx~xN1axPgYI0am3d25BjYmfSTX7Q}==Vcryl6@Se0(Jv +zxKW_o%H`jdnC7QXlkFbCsACHN1Dx=0gf<~@PW-&<=`1Hd)@#ypH7%OpalDj-P=ts+3^~yWs~TV}BD20HjkW6zc1L +z0#HzMkn3JV%7N-18_@tgE82*YnmEzxirriDSx#_|<|q1vL{k}7>^mRzO(ueTSN2~H +zG}kxp)Qn!&)><3|e>62+GXSpQKcemfqU!&BHZ5Ca;DT<63bBM&uV1BDS?MM$M;x8w>gShAPMxJM^BbMZn}Unm{OC9^4x3%% +zlmX8!km-u$N4fQXQ>jRe`7)3+RFGjhz +z18zf(Fo2<>YV^7LJO^UTZ2Ivd#mpN}o?7pBV&q=f%ID>haV7M8R3jsF*@a%iwIy>| +zsZ!-y{!%&j7`B?W8TcF4NH-RHH1xZ{;7BsA<#APu!;cND)te)FhoXz$BIU}2&^7WP +zT}TX>ZO58$VNPuh6JV7~s(W$vAj`^%AtUamex3YdVl3~4+pqk?G)qUibNMrj0*M25 +zY>5Ac|Dnv6xBQmV#$3JA?&HTN(lYl~J}@$l{*TY^kORrCB)3dDO}^^v!dcLf^CHty +zanjllIQeSLmpuG+h&ae`r*v!C*0A&W^a&q>93?BAXzG7n +z2*3TGPIcN`-_hY9&oaiv#fiv~>}7`T`4=pInEqWX*3e8+yPm^9h-tr&ts55$l+388 +zW)~F}2JH!}VLbQ>?6~H@&k`MnSsTeVj0TRVP4jGbP*!!CwM6`Z11c)yI2w$+R0zxo +zT|obYS1&&`{>>Z9(jnVU&=yI*%PGe*f78ie*_9oap?sd7fx7{r^WT>=XHF +zl`f{=UJEn2?tRw`Fem?eRE6#*nOes(ebRcmaK3~a3{a3EyE1zXSF0p7I_iDJ&%;3V +zU;AS}e?*mH#Yh2P9E3QBigIqu2iXf=@t)2+I~f*_E^JtEP1@IR{CBfTj%T}E3e#n% +zUa{@vU?D$l4DEANwkkK@ruP4ta)E*e^KLGg%$PizyPmHvKNMWtuJQ6sPXY=(1m#>W +z7V?9E!Vj}>a|KfQx5ESpH+q6$@gAp-P#~lbz`aj1_?xinN>3o8b2-Z3w>UZ3QZ}W0 +zWg-!>p>AADDcU^4;0*L4UFgB0QLlXd^y1E&4>txV!T|!`RwjZGl`;-4ZgFf>luHIy +zZ8d8Rh{I3r!g-ht6mAZxMB6VxRqnA0UY`h|mJZy2 +z17BazT$jMKFL3J6Ue_HL1^)4s%$Jj~Qx~1HG#tS@kwL(KP_ZI3dWz0SH(sqj#-*TNGsIWqPj>cj?!GyWvfdEiNOu4$>MIqL=F&Cc0{g*~L5 +zA1wt)=_zMFUkCT5$l!G{1-Y9QtGQ#qm5E(3fYPms_EP*sSVI)bfXN|uNO`BqVuCvd +zv)z8IGRgtM1<_trndVhQ^xA)wn~*W~#d*X@E=W)jcQWI8+?kdzHe;DZ`%+JE%gE}m +z6H=FO8rJxM{N90S=Gi!Mel)TyanxPa;E}C?hJl@e9UWad->;S|v;axgFjrY$z3(rV{MiJ}3M)t;Q?P5wZy0e3G{dcDO7n}3slDXLMrB$;#*W@Qv)D$=?Xs$F(8eTcyGIQ~IWgD%Gn&E>F9y#o>cR-7spE;Rur<_E~Pu)e0I +z#&y1|@8D~8c55<|KMf;&x;hg!A%VOZ38_+uk`jH4#=b9M&xcpxV-7cMN{jXVRnKSe +zlKJJ%=VBV{$DNeI1QkiA;DfdVT?$;O#22z6v6bTK9)fjrfIh!Hq__l~KzuNqT{&kA +zKs@YV6^1ZLGjTgR%(=NHS-DvWnnP)NM#qbHINqmQdCE5??co$3nuikqgm=s7*#Kd*+j_weKrZjMeLeHEoiJm>zuDRU` +zh~ggr^knneWU!Nn}AQt=0Id6Hk; +z4bJqse|V$H`stT?NS0yreYvaZ9YF!fw+N}{3#yXRU!C7?exl35BDC%+!jDMGT^DN# +zN9FGd#5t#;$h}5UgQ?q-Gr15>C6=nLUszle9<+_!!oi_m@_L^-R>_Qty7_g|C%m|5 +z-7^5X5V_ARi?h9_LW%2vByD3X_IvUktqBv{%SYXO1&;e&O#Ll_cfC`Wv1u+l_#RI< +zQ5Kly0;P`%TXaQN(heOg~>V&L{d+ZDA%eq-UKo#1)$rkjSm=nzAE2r +z5--RyKhxfXoGVU3^ab{5XGlyL1+26foG)4HZvN +zG@&I3h0fnK5lIjcrg*XxPy1(gK3_TN`&VYnxP;C|j$~0rT$0f|*#=OzM^NbE-1T5D +z%Csnt)n!sx3N#b(8G&+G3W~Q_B#StA6jZZ=p#wuu`DrAMXm{T@#S;ku4Dme@{Njmk +zCtrh3z6O>o)~o{&Htx+6kn*)$NNBH-biu^aYtWUq +z(G>4rCEKr#tO>!x8A@%W@6g)Xs%2Hq!y#Mbb@9R2@GDWi&!{jhZvzQ1D9nMuPoOS+ +z+cj{9nx5X{jJOIavbFf)Kz5Jnbe5Bu#(XE-z$j&iaP%c9W59OoT0~|N#D*(N2kz={ +zs(|)nH!_+_g1)#ZH2xk>ZTG#6WN#qa3BxZM{NWxq`*#$H255k6Ky?hw*hSA6`c_fl +zT@Ua%E5Ez3;~`kQFmrC#$Nlvc_Uy3#yzhd-6UYuuIwgIBZZC-`dwOBJbfurL(FfhH +z{YkjE+9OrOveY`{t{sGw&51YO1@{iO4)Ki=!Z5#q=m_Hi)_j0`>?;t2j);vv%BUif +z;wpTZdLQLsGvZ()DCdxYudn^Pt;BZ}Rin$4F8h{R`HxT2z`uc&aMXIQOvwgA5%{&) +zFW52MiN!$!EXgx}Px~e1!EMp;#&kY65oDho95j~!qD%YJr`+aK4jCJ4UJ^;q>w@Lf +zvDfg|M`S^@DGxu+7aR3Cx#;%?advj&1~L-m +zJqCP9&TW3migV*`Z$#)Qa>3>Jf)g9D6Ki28P@iX(uso)hic8Dp1F< +zeF;(n8Po8A*~^T{De(J)Z2nqLl@Vv3yoSlGwq0aeOg4ymI(KIkTeur-=J-yp9z?qe)it6gq-wl@I +z0D-_I{|T<5kwD9uH3yf1GWXp5*8eOgJf*q0IRoK|+r{}Fug&0WpNDKMTC@(Xc)9K8 +zy`lByMn!1fnY)1KYP(0Je1)c~WilUuh<&Q8^OE?L9Q^xK*Y@M$`6D6TDCZ^@l8{|} +zxmmNw)mng$hYBii+&ZqedxWT0dnV#LG4zC%+kzcK+-??vEHT>Q-T8zu|s_1IbA#OV)^+1pg1OmmZn` + +literal 0 +HcmV?d00001 + diff --git a/Remapped-Spigot-Server-Patches/0681-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch b/Remapped-Spigot-Server-Patches/0681-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch new file mode 100644 index 000000000..1099f41d5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0681-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Tue, 2 Feb 2021 09:17:59 +0100 +Subject: [PATCH] stop firing pressure plate EntityInteractEvent for ignored + entities + + +diff --git a/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java +index f12bf33aa8cc8043052aa1048087f61d9a6d4d52..ae5b052b80665bfba126f5ca5dcd78608cb27d48 100644 +--- a/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java +@@ -81,6 +81,7 @@ public class PressurePlateBlock extends BasePressurePlateBlock { + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); ++ if (entity.isIgnoringBlockTriggers()) continue; // Paper - don't call event for ignored entities + + // CraftBukkit start - Call interact event when turning on a pressure plate + if (this.getSignalForState(world.getBlockState(pos)) == 0) { diff --git a/Remapped-Spigot-Server-Patches/0682-fix-converting-txt-to-json-file.patch b/Remapped-Spigot-Server-Patches/0682-fix-converting-txt-to-json-file.patch new file mode 100644 index 000000000..950227fe7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0682-fix-converting-txt-to-json-file.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:49:15 -0800 +Subject: [PATCH] fix converting txt to json file + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +index 603b4f841bcc276997d130f1545c4cf550dcac2d..2eafb7e27d06a975cee48cc18c7596d610483d16 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList { + + this.setViewDistance(dedicatedserverproperties.viewDistance); + super.setUsingWhiteList((Boolean) dedicatedserverproperties.whiteList.get()); ++ // Paper start - moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end + this.loadUserBanList(); + this.saveUserBanList(); + this.loadIpBanList(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 95f9863bbccaa23d08c409792314df4f2397a317..c2947313cc0eda3247fb4b20ddd1d0b86c37c50a 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -195,6 +195,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + org.spigotmc.SpigotConfig.init((java.io.File) options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ // Paper start - moved up to right after PlayerList creation but before file load/save ++ if (this.convertOldUsers()) { ++ this.getProfileCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end + // Paper start + try { + com.destroystokyo.paper.PaperConfig.init((java.io.File) options.valueOf("paper-settings")); +@@ -257,10 +263,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertOldUsers()) { +- this.getProfileCache().b(false); // Paper +- } +- + if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index 941b7e356c377fd8ad4e27409cd74c0046878396..f23ca6bebf2c0b7e02dc6aa51e384cee4e3d12c3 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -243,6 +243,7 @@ public class GameProfileCache { + return arraylist; + } + ++ public void save(boolean asyncSave) { b(asyncSave); } // Paper - OBFHELPER + public void b(boolean asyncSave) { // Paper + JsonArray jsonarray = new JsonArray(); + DateFormat dateformat = createDateFormat(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c962b6fc0c65dc5e2ea636220727bca63bf4b740..dd121ec8f779b3786eeb7fe85519cf9e472f5adf 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -171,6 +171,7 @@ public abstract class PlayerList { + this.maxPlayers = maxPlayers; + this.playerIo = saveHandler; + } ++ abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player) { + ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper diff --git a/Remapped-Spigot-Server-Patches/0683-Add-worldborder-events.patch b/Remapped-Spigot-Server-Patches/0683-Add-worldborder-events.patch new file mode 100644 index 000000000..33286aca0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0683-Add-worldborder-events.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 22:40:34 -0800 +Subject: [PATCH] Add worldborder events + + +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index aaa6251838483de5c46913534413151b5cb1d3fe..4ad686f4fcd3a23c4230faa03946db1f338bc904 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -14,6 +14,9 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent; // Paper ++import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent; // Paper ++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent; // Paper + + public class WorldBorder { + +@@ -102,15 +105,19 @@ public class WorldBorder { + } + + public void setCenter(double x, double z) { +- this.centerX = x; +- this.centerZ = z; ++ // Paper start ++ WorldBorderCenterChangeEvent event = new WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); ++ if (!event.callEvent()) return; ++ this.centerX = event.getNewCenter().getX(); ++ this.centerZ = event.getNewCenter().getZ(); ++ // Paper end + this.extent.onCenterChange(); + Iterator iterator = this.getListeners().iterator(); + + while (iterator.hasNext()) { + BorderChangeListener iworldborderlistener = (BorderChangeListener) iterator.next(); + +- iworldborderlistener.onBorderCenterSet(this, x, z); ++ iworldborderlistener.onBorderCenterSet(this, event.getNewCenter().getX(), event.getNewCenter().getZ()); // Paper + } + + } +@@ -128,25 +135,43 @@ public class WorldBorder { + } + + public void setSize(double size) { +- this.extent = new WorldBorder.StaticBorderExtent(size); ++ // Paper start ++ WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); ++ if (!event.callEvent()) return; ++ if (event.getType() == WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition ++ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); ++ return; ++ } ++ this.extent = new WorldBorder.StaticBorderExtent(event.getNewSize()); ++ // Paper end + Iterator iterator = this.getListeners().iterator(); + + while (iterator.hasNext()) { + BorderChangeListener iworldborderlistener = (BorderChangeListener) iterator.next(); + +- iworldborderlistener.onBorderSizeSet(this, size); ++ iworldborderlistener.onBorderSizeSet(this, event.getNewSize()); // Paper + } + + } + + public void lerpSizeBetween(double fromSize, double toSize, long time) { +- this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); ++ // Paper start ++ WorldBorderBoundsChangeEvent.Type type; ++ if (fromSize == toSize) { // new size = old size ++ type = WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. ++ } else { ++ type = WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; ++ } ++ WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); ++ if (!event.callEvent()) return; ++ this.extent = (WorldBorder.BorderExtent) (fromSize == event.getNewSize() ? new WorldBorder.StaticBorderExtent(event.getNewSize()) : new WorldBorder.MovingBorderExtent(fromSize, event.getNewSize(), event.getDuration())); ++ // Paper end + Iterator iterator = this.getListeners().iterator(); + + while (iterator.hasNext()) { + BorderChangeListener iworldborderlistener = (BorderChangeListener) iterator.next(); + +- iworldborderlistener.onBorderSizeLerping(this, fromSize, toSize, time); ++ iworldborderlistener.onBorderSizeLerping(this, fromSize, event.getNewSize(), event.getDuration()); // Paper + } + + } +@@ -434,11 +459,11 @@ public class WorldBorder { + + class MovingBorderExtent implements WorldBorder.BorderExtent { + +- private final double from; +- private final double to; ++ private final double from; public final double getOldSize() { return this.from; } // Paper - OBFHELPER ++ private final double to; public final double getNewSize() { return this.to; } // Paper - OBFHELPER + private final long lerpEnd; + private final long lerpBegin; +- private final double lerpDuration; ++ private final double lerpDuration; public final double getDuration() { return this.lerpDuration; } // Paper - OBFHELPER + + private MovingBorderExtent(double d0, double d1, long i) { + this.from = d0; +@@ -493,6 +518,7 @@ public class WorldBorder { + + @Override + public WorldBorder.BorderExtent update() { ++ if (this.getLerpTimeRemaining() <= 0L) new WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), getOldSize(), getNewSize(), getDuration()).callEvent(); // Paper + return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); + } + +@@ -514,6 +540,7 @@ public class WorldBorder { + + double getSize(); + ++ default long getLerpTimeRemaining() { return getLerpRemainingTime(); } // Paper - OBFHELPER + long getLerpRemainingTime(); + + double getLerpTarget(); diff --git a/Remapped-Spigot-Server-Patches/0684-added-PlayerNameEntityEvent.patch b/Remapped-Spigot-Server-Patches/0684-added-PlayerNameEntityEvent.patch new file mode 100644 index 000000000..64f85dbc1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0684-added-PlayerNameEntityEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Jul 2020 00:33:54 -0700 +Subject: [PATCH] added PlayerNameEntityEvent + + +diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java +index 5e38077c3de0a40f3cfd856bf2e48f7061e39a9d..5c575798c20f15d28350f767ecf15bfc042ebc8c 100644 +--- a/src/main/java/net/minecraft/world/item/NameTagItem.java ++++ b/src/main/java/net/minecraft/world/item/NameTagItem.java +@@ -1,5 +1,10 @@ + package net.minecraft.world.item; + ++// Paper start ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.event.player.PlayerNameEntityEvent; ++// Paper end ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.entity.LivingEntity; +@@ -16,11 +21,15 @@ public class NameTagItem extends Item { + public InteractionResult interactLivingEntity(ItemStack stack, Player user, LivingEntity entity, InteractionHand hand) { + if (stack.hasCustomHoverName() && !(entity instanceof Player)) { + if (!user.level.isClientSide && entity.isAlive()) { +- entity.setCustomName(stack.getHoverName()); +- if (entity instanceof Mob) { +- ((Mob) entity).setPersistenceRequired(); ++ // Paper start ++ PlayerNameEntityEvent event = new PlayerNameEntityEvent(((ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), PaperAdventure.asAdventure(stack.getHoverName()), true); ++ if (!event.callEvent()) return InteractionResult.PASS; ++ LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); ++ newEntityLiving.setCustomName(event.getName() != null ? PaperAdventure.asVanilla(event.getName()) : null); ++ if (event.isPersistent() && newEntityLiving instanceof Mob) { ++ ((Mob) newEntityLiving).setPersistenceRequired(); + } +- ++ // Paper end + stack.shrink(1); + } + diff --git a/Remapped-Spigot-Server-Patches/0685-Prevent-grindstones-from-overstacking-items.patch b/Remapped-Spigot-Server-Patches/0685-Prevent-grindstones-from-overstacking-items.patch new file mode 100644 index 000000000..9f050a4e9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0685-Prevent-grindstones-from-overstacking-items.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 16 Feb 2021 21:37:51 -0600 +Subject: [PATCH] Prevent grindstones from overstacking items + + +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 329a6d70d53c13cd554c64996f2ddc489bdc1e94..445d408963538fbc01d61902805b2e35c861e4ce 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -195,13 +195,13 @@ public class GrindstoneMenu extends AbstractContainerMenu { + i = Math.max(item.getMaxDamage() - l, 0); + itemstack2 = this.mergeEnchants(itemstack, itemstack1); + if (!itemstack2.isDamageableItem()) { +- if (!ItemStack.matches(itemstack, itemstack1)) { ++ if (!ItemStack.matches(itemstack, itemstack1) || itemstack2.getMaxStackSize() == 1) { // Paper - add max stack size check + this.resultSlots.setItem(0, ItemStack.EMPTY); + this.broadcastChanges(); + return; + } + +- b0 = 2; ++ b0 = 2; // Paper - the problem line for above change, causing over-stacking + } + } else { + boolean flag3 = !itemstack.isEmpty(); diff --git a/Remapped-Spigot-Server-Patches/0686-Add-recipe-to-cook-events.patch b/Remapped-Spigot-Server-Patches/0686-Add-recipe-to-cook-events.patch new file mode 100644 index 000000000..e43e7804a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0686-Add-recipe-to-cook-events.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> +Date: Wed, 6 Jan 2021 12:04:03 -0800 +Subject: [PATCH] Add recipe to cook events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index f47b46cebd43faa509b8139d2a51cc8f87615893..2dcabfc765cbf6341546a7e2c48156fd921fcc82 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -393,7 +393,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.level.getWorld().getBlockAt(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()), source, result); ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.level.getWorld().getBlockAt(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()), source, result, (org.bukkit.inventory.CookingRecipe) recipe.toBukkitRecipe()); // Paper + this.level.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); + + if (furnaceSmeltEvent.isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 6c38361d744eae763b6c131ad314485f5a88fcfc..39b4782df965c785be7946d6964e0b7a4381ff74 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -73,7 +73,10 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable, Ticka + + if (this.cookingProgress[i] >= this.cookingTime[i]) { + SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack}); +- ItemStack itemstack1 = (ItemStack) this.level.getRecipeManager().getRecipeFor(RecipeType.CAMPFIRE_COOKING, inventorysubcontainer, this.level).map((recipecampfire) -> { ++ // Paper start ++ Optional recipe = this.level.getRecipeManager().getRecipeFor(RecipeType.CAMPFIRE_COOKING, inventorysubcontainer, this.level); ++ ItemStack itemstack1 = (ItemStack) recipe.map((recipecampfire) -> { ++ // Paper end + return recipecampfire.assemble(inventorysubcontainer); + }).orElse(itemstack); + BlockPos blockposition = this.getBlockPos(); +@@ -82,7 +85,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable, Ticka + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(this.level, this.worldPosition), source, result); ++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(this.level, this.worldPosition), source, result, (org.bukkit.inventory.CookingRecipe) recipe.map(CampfireCookingRecipe::toBukkitRecipe).orElse(null)); // Paper + this.level.getCraftServer().getPluginManager().callEvent(blockCookEvent); + + if (blockCookEvent.isCancelled()) { diff --git a/Remapped-Spigot-Server-Patches/0687-Add-Block-isValidTool.patch b/Remapped-Spigot-Server-Patches/0687-Add-Block-isValidTool.patch new file mode 100644 index 000000000..a9fe23e27 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0687-Add-Block-isValidTool.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 6 Jul 2020 12:44:31 -0700 +Subject: [PATCH] Add Block#isValidTool + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 376b0497c28a35d7ea615397c87b2558b95c596a..def19e23996b85e1e540cd5edc6821ae0ae37f91 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -803,5 +803,9 @@ public class CraftBlock implements Block { + } + return speed; + } ++ ++ public boolean isValidTool(ItemStack itemStack) { ++ return getDrops(itemStack).size() != 0; ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0688-Allow-using-signs-inside-spawn-protection.patch b/Remapped-Spigot-Server-Patches/0688-Allow-using-signs-inside-spawn-protection.patch new file mode 100644 index 000000000..fa0bc9f10 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0688-Allow-using-signs-inside-spawn-protection.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anton Lindroth +Date: Wed, 15 Apr 2020 01:54:02 +0200 +Subject: [PATCH] Allow using signs inside spawn protection + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index eb367b8feda8219a97a547c3ef6ab82d278d2f25..108a005c296c4ed370de4af636163088971bed13 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -811,4 +811,9 @@ public class PaperWorldConfig { + fixWitherTargetingBug = getBoolean("fix-wither-targeting-bug", false); + log("Withers properly target players: " + fixWitherTargetingBug); + } ++ ++ public boolean allowUsingSignsInsideSpawnProtection = false; ++ private void allowUsingSignsInsideSpawnProtection() { ++ allowUsingSignsInsideSpawnProtection = getBoolean("allow-using-signs-inside-spawn-protection", allowUsingSignsInsideSpawnProtection); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cb6568b622abeb939a1195f4656accc8a1c3f1fc..1add53082ab9382cb2e90dc8305b8c71ef1c6a46 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -152,6 +152,7 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.CommandBlock; ++import net.minecraft.world.level.block.SignBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.CommandBlockEntity; + import net.minecraft.world.level.block.entity.JigsawBlockEntity; +@@ -1690,7 +1691,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + this.player.resetLastActionTime(); + if (blockposition.getY() < this.server.getMaxBuildHeight()) { +- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract((net.minecraft.world.entity.player.Player) this.player, blockposition)) { ++ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract((net.minecraft.world.entity.player.Player) this.player, blockposition) || (worldserver.paperConfig.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof SignBlock))) { // Paper + // CraftBukkit start - Check if we can actually do something over this large a distance + // Paper - move check up + this.player.stopUsingItem(); // SPIGOT-4706 diff --git a/Remapped-Spigot-Server-Patches/0689-Implement-Keyed-on-World.patch b/Remapped-Spigot-Server-Patches/0689-Implement-Keyed-on-World.patch new file mode 100644 index 000000000..94fd5a370 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0689-Implement-Keyed-on-World.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jan 2021 00:34:04 -0800 +Subject: [PATCH] Implement Keyed on World + + +diff --git a/src/main/java/net/minecraft/core/Registry.java b/src/main/java/net/minecraft/core/Registry.java +index 5a98bc1522c2035487ce0a048c236903dbfa816e..4924d8cd3004a6e1ce76cd5cf7520556c23fe20a 100644 +--- a/src/main/java/net/minecraft/core/Registry.java ++++ b/src/main/java/net/minecraft/core/Registry.java +@@ -130,7 +130,7 @@ public abstract class Registry implements Codec, Keyable, IdMap { + public static final ResourceKey> LOOT_FUNCTION_REGISTRY = createRegistryKey("loot_function_type"); + public static final ResourceKey> LOOT_ITEM_REGISTRY = createRegistryKey("loot_condition_type"); + public static final ResourceKey> DIMENSION_TYPE_REGISTRY = createRegistryKey("dimension_type"); +- public static final ResourceKey> DIMENSION_REGISTRY = createRegistryKey("dimension"); ++ public static final ResourceKey> DIMENSION_REGISTRY = createRegistryKey("dimension"); public static final ResourceKey> getWorldRegistry() { return DIMENSION_REGISTRY; } // Paper - OBFHELPER + public static final ResourceKey> LEVEL_STEM_REGISTRY = createRegistryKey("dimension"); + public static final Registry SOUND_EVENT = registerSimple(Registry.SOUND_EVENT_REGISTRY, () -> { + return SoundEvents.ITEM_PICKUP; +@@ -339,9 +339,9 @@ public abstract class Registry implements Codec, Keyable, IdMap { + ResourceLocation minecraftkey = resourcekey.location(); + + Registry.LOADERS.put(minecraftkey, defaultEntry); +- WritableRegistry iregistrywritable = Registry.WRITABLE_REGISTRY; ++ WritableRegistry iregistrywritable = Registry.WRITABLE_REGISTRY; // Paper - decompile fix + +- return (WritableRegistry) iregistrywritable.register(resourcekey, (Object) registry, lifecycle); ++ return (R) iregistrywritable.register(resourcekey, (Object) registry, lifecycle); // Paper - decompile fix + } + + protected Registry(ResourceKey> key, Lifecycle lifecycle) { +@@ -428,11 +428,11 @@ public abstract class Registry implements Codec, Keyable, IdMap { + } + + public static T register(Registry registry, ResourceLocation id, T entry) { +- return ((WritableRegistry) registry).register(ResourceKey.create(registry.key, id), entry, Lifecycle.stable()); ++ return ((WritableRegistry) registry).register(ResourceKey.create(registry.key, id), entry, Lifecycle.stable()); // Paper - decompile fix + } + + public static T registerMapping(Registry registry, int rawId, String id, T entry) { +- return ((WritableRegistry) registry).registerMapping(rawId, ResourceKey.create(registry.key, new ResourceLocation(id)), entry, Lifecycle.stable()); ++ return ((WritableRegistry) registry).registerMapping(rawId, ResourceKey.create(registry.key, new ResourceLocation(id)), entry, Lifecycle.stable()); // Paper - decompile fix + } + + static { +diff --git a/src/main/java/net/minecraft/resources/ResourceKey.java b/src/main/java/net/minecraft/resources/ResourceKey.java +index 2f39438ee9b23706efb2fd877fe223777b6968c7..3085ec1f20f4c945242697b809188a8c828cfb75 100644 +--- a/src/main/java/net/minecraft/resources/ResourceKey.java ++++ b/src/main/java/net/minecraft/resources/ResourceKey.java +@@ -12,6 +12,7 @@ public class ResourceKey { + private final ResourceLocation registryName; + private final ResourceLocation location; + ++ public static ResourceKey newResourceKey(ResourceKey> registryKey, ResourceLocation minecraftKey) { return create(registryKey, minecraftKey); } // Paper - OBFHELPER + public static ResourceKey create(ResourceKey> registry, ResourceLocation value) { + return create(registry.location, value); + } +@@ -41,6 +42,7 @@ public class ResourceKey { + return this.registryName.equals(registry.location()); + } + ++ public ResourceLocation getLocation() { return location(); } // Paper - OBFHELPER + public ResourceLocation location() { + return this.location; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 794b894ed24636aec60de9a28ba7613d7a917324..6905256147d9bd79e5f52bf86bdb21c89b8411a7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1156,7 +1156,7 @@ public final class CraftServer implements Server { + } else if (name.equals(levelName + "_the_end")) { + worldKey = net.minecraft.world.level.Level.END; + } else { +- worldKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(name.toLowerCase(java.util.Locale.ENGLISH))); ++ worldKey = ResourceKey.newResourceKey(Registry.getWorldRegistry(), new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper + } + + ServerLevel internal = (ServerLevel) new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, dimensionmanager, getServer().progressListenerFactory.create(11), +@@ -1246,6 +1246,15 @@ public final class CraftServer implements Server { + return null; + } + ++ // Paper start ++ @Override ++ public World getWorld(NamespacedKey worldKey) { ++ ServerLevel worldServer = console.getLevel(ResourceKey.newResourceKey(Registry.getWorldRegistry(), CraftNamespacedKey.toMinecraft(worldKey))); ++ if (worldServer == null) return null; ++ return worldServer.getWorld(); ++ } ++ // Paper end ++ + public void addWorld(World world) { + // Check if a World already exists with the UID. + if (getWorld(world.getUID()) != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index f497b9e11a075a84ff0a2117eb79d0532e4a326f..b0212b2043ee5fd77c8876ef0b51ef91488712f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2566,6 +2566,11 @@ public class CraftWorld implements World { + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, net.minecraft.server.MinecraftServer.getServer()); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(world.dimension().getLocation()); ++ } + // Paper end + + // Spigot start diff --git a/Remapped-Spigot-Server-Patches/0690-Add-fast-alternative-constructor-for-Vector3f.patch b/Remapped-Spigot-Server-Patches/0690-Add-fast-alternative-constructor-for-Vector3f.patch new file mode 100644 index 000000000..b39576113 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0690-Add-fast-alternative-constructor-for-Vector3f.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Irmo van den Berge +Date: Wed, 10 Mar 2021 21:26:31 +0100 +Subject: [PATCH] Add fast alternative constructor for Vector3f + +Signed-off-by: Irmo van den Berge + +diff --git a/src/main/java/net/minecraft/core/Rotations.java b/src/main/java/net/minecraft/core/Rotations.java +index e3c2affb5dfaf2d78139e98c9e8a40b37c92bf2f..4782cbd9d86d8246954db76993741a8a749a7fe0 100644 +--- a/src/main/java/net/minecraft/core/Rotations.java ++++ b/src/main/java/net/minecraft/core/Rotations.java +@@ -19,6 +19,18 @@ public class Rotations { + this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2)); + } + ++ // Paper start - faster alternative constructor ++ private Rotations(float x, float y, float z, Void dummy_var) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ } ++ ++ public static Rotations createWithoutValidityChecks(float x, float y, float z) { ++ return new Rotations(x, y, z, null); ++ } ++ // Paper end ++ + public ListTag save() { + ListTag nbttaglist = new ListTag(); + diff --git a/Remapped-Spigot-Server-Patches/0691-Item-Rarity-API.patch b/Remapped-Spigot-Server-Patches/0691-Item-Rarity-API.patch new file mode 100644 index 000000000..51a841f55 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0691-Item-Rarity-API.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 12 Mar 2021 17:09:42 -0800 +Subject: [PATCH] Item Rarity API + + +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index 58400e84830c93675b0a1fe632be5e217c19a932..cb079bfd5339b96ad372b0a3b483d02cd0636bfd 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -45,7 +45,7 @@ public class Item implements ItemLike { + protected static final UUID BASE_ATTACK_SPEED_UUID = UUID.fromString("FA233E1C-4180-4865-B01B-BCCE9785ACA3"); + protected static final Random random = new Random(); + protected final CreativeModeTab category; +- private final Rarity rarity; ++ private final Rarity rarity; public final Rarity getItemRarity() { return rarity; } // Paper - OBFHELPER + private final int maxStackSize; + private final int maxDamage; + private final boolean isFireResistant; +@@ -209,6 +209,7 @@ public class Item implements ItemLike { + return stack.isEnchanted(); + } + ++ public Rarity getItemStackRarity(ItemStack itemStack) { return getRarity(itemStack); } // Paper - OBFHELPER + public Rarity getRarity(ItemStack stack) { + if (!stack.isEnchanted()) { + return this.rarity; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 25a29d997f163ce2b11330d66a691601f514a9cb..472b0615dcdc3c0c52bd377fd69752716f354262 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -470,6 +470,20 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int nextEntityId() { + return net.minecraft.world.entity.Entity.nextEntityId(); + } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemRarity(org.bukkit.Material material) { ++ Item item = getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item, and rarity does not apply to blocks"); ++ } ++ return io.papermc.paper.inventory.ItemRarity.values()[item.getItemRarity().ordinal()]; ++ } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { ++ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getItemStackRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0692-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/Remapped-Spigot-Server-Patches/0692-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch new file mode 100644 index 000000000..c076df007 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0692-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Fri, 19 Mar 2021 16:07:21 -0700 +Subject: [PATCH] Only set despawnTimer for Wandering Traders spawned by + MobSpawnerTrader + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index d38828485d6deb08036e11d8bf16b3d63a60fbae..f6d2aca2fe3ee9b69a0b200c8b2ea35f222fb521 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -319,6 +319,12 @@ public class EntityType { + + @Nullable + public T spawnCreature(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - add consumer to modify entity before spawn ++ return this.spawnCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, null); ++ } ++ @Nullable ++ public T spawnCreature(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason, @Nullable java.util.function.Consumer op) { ++ // Paper end + // Paper start - Call PreCreatureSpawnEvent + org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityType.getKey(this).getPath()); + if (type != null) { +@@ -334,6 +340,7 @@ public class EntityType { + } + // Paper end + T t0 = this.create(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); ++ if (t0 != null && op != null) op.accept(t0); // Paper + + if (t0 != null) { + worldserver.addAllEntities(t0, spawnReason); +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index 69d92590d265abe8a04d8bf48bbe9a6ae606ae50..04c4cca4be8886feb59f180915977b77f9c7dde8 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -61,7 +61,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + public WanderingTrader(EntityType type, Level world) { + super(type, world); + this.forcedLoading = true; +- this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader ++ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index 9074d57e1576db2da3e4c76add4f7e07e5567879..f861d83affbb0b5eaf7440a909ca3b5f7a604da7 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -114,7 +114,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + return false; + } + +- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawnCreature(worldserver, (CompoundTag) null, (Component) null, (Player) null, blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit ++ WanderingTrader entityvillagertrader = EntityType.WANDERING_TRADER.spawnCreature(worldserver, null, null, null, blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, trader -> trader.setDespawnDelay(48000)); // CraftBukkit // Paper - set despawnTimer before spawn events called + + if (entityvillagertrader != null) { + for (int i = 0; i < 2; ++i) { diff --git a/Remapped-Spigot-Server-Patches/0693-copy-TESign-isEditable-from-snapshots.patch b/Remapped-Spigot-Server-Patches/0693-copy-TESign-isEditable-from-snapshots.patch new file mode 100644 index 000000000..862aed64d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0693-copy-TESign-isEditable-from-snapshots.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 23 Mar 2021 06:43:30 +0000 +Subject: [PATCH] copy TESign#isEditable from snapshots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index 65e8a349c80a700f63dd27b11bb2099f65cbc069..eb0739c0927e821a5080d14e762225fd4936b82d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -109,6 +109,7 @@ public class CraftSign extends CraftBlockEntityState implements + } + // Paper end + } ++ sign.isEditable = getSnapshot().isEditable; // Paper - copy manually + } + + // Paper start diff --git a/Remapped-Spigot-Server-Patches/0694-Drop-carried-item-when-player-has-disconnected.patch b/Remapped-Spigot-Server-Patches/0694-Drop-carried-item-when-player-has-disconnected.patch new file mode 100644 index 000000000..4e8128fd6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0694-Drop-carried-item-when-player-has-disconnected.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dmitry Sidorov +Date: Thu, 4 Feb 2021 20:32:01 +0300 +Subject: [PATCH] Drop carried item when player has disconnected + +Fixes disappearance of held items, when a player gets disconnected and PlayerDropItemEvent is cancelled. +Closes #5036 + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index dd121ec8f779b3786eeb7fe85519cf9e472f5adf..23cfaf5c432221f2d1afe37ba657f723d6d21a73 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -79,6 +79,7 @@ import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.npc.AbstractVillager; ++import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; +@@ -605,6 +606,14 @@ public abstract class PlayerList { + } + // Paper end + ++ // Paper - Drop carried item when player has disconnected ++ if (!entityplayer.inventory.getCarried().isEmpty()) { ++ ItemStack carried = entityplayer.inventory.getCarried(); ++ entityplayer.inventory.setCarried(ItemStack.NULL_ITEM); ++ entityplayer.drop(carried, false); ++ } ++ // Paper end ++ + this.save(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); diff --git a/Remapped-Spigot-Server-Patches/0695-forced-whitelist-use-configurable-kick-message.patch b/Remapped-Spigot-Server-Patches/0695-forced-whitelist-use-configurable-kick-message.patch new file mode 100644 index 000000000..4b433dc16 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0695-forced-whitelist-use-configurable-kick-message.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 27 Mar 2021 09:24:23 +0100 +Subject: [PATCH] forced whitelist: use configurable kick message + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9b654fed2a00740cef84cf72258abfc7aeafc0c2..fd76d776c7003585c9efef44c6d7da0f6c3f574e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -73,7 +73,6 @@ import net.minecraft.nbt.NbtOps; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.TextComponent; +-import net.minecraft.network.chat.TranslatableComponent; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; + import net.minecraft.network.protocol.game.ClientboundSetTimePacket; +@@ -2040,7 +2039,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Mon, 29 Mar 2021 09:07:25 +0200 +Subject: [PATCH] Make sure to remove correct TE during TE tick + +This looks like it can cause premature TE removal. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b89cefc8890774dbc64fd6bddeb038d2ee36d485..4523bc1f49e7be248a47eeb599fa7b6550dbb08d 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -895,7 +895,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + //this.tileEntityList.remove(tileentity); // Paper - remove unused list + // Paper - prevent double chunk lookups + LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getBlockPos())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again +- chunk.removeBlockEntity(tileentity.getBlockPos()); ++ chunk.removeTileEntity(tileentity.getBlockPos(), tileentity); // Paper - remove correct TE + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 9b76dc15417eef420804e5184a6d684e1137a746..a15c08be3e1bd0e7934175db6ae0684bbb05e249 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -818,10 +818,18 @@ public class LevelChunk implements ChunkAccess { + + @Override + public void removeBlockEntity(BlockPos pos) { ++ // Paper start - remove correct TE ++ removeTileEntity(pos, null); ++ } ++ public void removeTileEntity(BlockPos blockposition, BlockEntity match) { ++ // Paper end + if (this.loaded || this.world.isClientSide()) { +- BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos); ++ // Paper start ++ BlockEntity tileentity = (BlockEntity) this.blockEntities.get(blockposition); + +- if (tileentity != null) { ++ if (tileentity != null && (match == null || match == tileentity)) { ++ this.blockEntities.remove(blockposition); ++ // Paper end + tileentity.setRemoved(); + } + } diff --git a/Remapped-Spigot-Server-Patches/0697-Don-t-ignore-result-of-PlayerEditBookEvent.patch b/Remapped-Spigot-Server-Patches/0697-Don-t-ignore-result-of-PlayerEditBookEvent.patch new file mode 100644 index 000000000..930a7a71e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0697-Don-t-ignore-result-of-PlayerEditBookEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Mon, 5 Apr 2021 18:35:15 -0700 +Subject: [PATCH] Don't ignore result of PlayerEditBookEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1add53082ab9382cb2e90dc8305b8c71ef1c6a46..65afc23f4791aca19bff78ed86b3b0d31fa81977 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1144,7 +1144,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + list.stream().map(StringTag::valueOf).forEach(nbttaglist::add); + ItemStack old = itemstack.copy(); // CraftBukkit + itemstack.addTagElement("pages", (Tag) nbttaglist); +- CraftEventFactory.handleEditBookEvent(player, i, old, itemstack); // CraftBukkit ++ this.player.inventory.setItem(i, CraftEventFactory.handleEditBookEvent(player, i, old, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + } + diff --git a/Remapped-Spigot-Server-Patches/0698-fix-cancelling-block-falling-causing-client-desync.patch b/Remapped-Spigot-Server-Patches/0698-fix-cancelling-block-falling-causing-client-desync.patch new file mode 100644 index 000000000..00cd7e5d6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0698-fix-cancelling-block-falling-causing-client-desync.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 27 Mar 2021 11:13:30 +0100 +Subject: [PATCH] fix cancelling block falling causing client desync + + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 2ba81e7179c7f9e2e1add1ad6bd6b96ee12c5da1..718e20f83a9b510c095d7e12241616cdce33d2d6 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -13,6 +13,7 @@ import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.FluidTags; + import net.minecraft.tags.Tag; +@@ -41,6 +42,7 @@ import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class FallingBlockEntity extends Entity { +@@ -114,8 +116,18 @@ public class FallingBlockEntity extends Entity { + + if (this.time++ == 0) { + blockposition = this.blockPosition(); +- if (this.level.getBlockState(blockposition).is(block) && !CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState()).isCancelled()) { +- this.level.removeBlock(blockposition, false); ++ // Paper start - fix cancelling block falling causing client desync ++ if (this.level.getBlockState(blockposition).isSameInstance(block)) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ if (this.level.getBlockState(blockposition).isSameInstance(block)) { //if listener didn't update the block ++ ((ServerLevel) level).getChunkSource().blockChanged(blockposition); ++ } ++ this.remove(); ++ return; ++ } else { ++ this.level.setAir(blockposition, false); ++ } ++ // Paper end - fix cancelling block falling causing client desync + } else if (!this.level.isClientSide) { + this.remove(); + return; +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 17baae6b11f191f4738a107c7e62ea5bdac17a3c..32cda8c2e14cf8b218cb006a9b25330f0dab849a 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -682,6 +682,7 @@ public abstract class BlockBehaviour { + return this.getBlock().is(tag) && predicate.test(this); + } + ++ public final boolean isSameInstance(Block block) { return is(block); } // Paper - OBFHELPER + public boolean is(Block block) { + return this.getBlock().is(block); + } diff --git a/Remapped-Spigot-Server-Patches/0699-Expose-protocol-version.patch b/Remapped-Spigot-Server-Patches/0699-Expose-protocol-version.patch new file mode 100644 index 000000000..0e4392f5f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0699-Expose-protocol-version.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Fri, 26 Mar 2021 11:23:17 +0100 +Subject: [PATCH] Expose protocol version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 472b0615dcdc3c0c52bd377fd69752716f354262..402e5a98290a1701dd67d27c484c97e0a6067c4f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -484,6 +484,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getItemStackRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } ++ ++ @Override ++ public int getProtocolVersion() { ++ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); ++ } + // Paper end + + /** diff --git a/Remapped-Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch b/Remapped-Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch new file mode 100644 index 000000000..036bfe3e5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch @@ -0,0 +1,132 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 1 Apr 2021 00:34:02 -0700 +Subject: [PATCH] Allow for Component suggestion tooltips in + AsyncTabCompleteEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 65afc23f4791aca19bff78ed86b3b0d31fa81977..67defaf71752ed29fde483e8232aa358ffa53675 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -769,12 +769,11 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + // Paper start - async tab completion + com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; +- java.util.List completions = new java.util.ArrayList<>(); + String buffer = packet.getCommand(); +- event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(), completions, ++ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(), + buffer, true, null); + event.callEvent(); +- completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ java.util.List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); + // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server + if (!event.isHandled()) { + if (!event.isCancelled()) { +@@ -793,10 +792,16 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + }); + } + } else if (!completions.isEmpty()) { +- com.mojang.brigadier.suggestion.SuggestionsBuilder builder = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); ++ com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); + +- builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); +- completions.forEach(builder::suggest); ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); ++ completions.forEach(completion -> { ++ if (completion.tooltip() == null) { ++ builder.suggest(completion.suggestion()); ++ } else { ++ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); ++ } ++ }); + com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); + com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); + suggestEvent.setCancelled(suggestions.isEmpty()); +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index c5e00bd9e2790992202aadf8eec2002fc88c78f1..dd8e87ad192c19743577bb95253a127072ea196c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -29,34 +29,56 @@ public class ConsoleCommandCompleter implements Completer { + final CraftServer server = this.server.server; + final String buffer = line.line(); + // Async Tab Complete +- com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; +- java.util.List completions = new java.util.ArrayList<>(); +- event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), completions, +- buffer, true, null); ++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = ++ new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), buffer, true, null); + event.callEvent(); +- completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); + + if (event.isCancelled() || event.isHandled()) { + // Still fire sync event with the provided completions, if someone is listening + if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) { +- List finalCompletions = completions; ++ List finalCompletions = new java.util.ArrayList<>(completions); + Waitable> syncCompletions = new Waitable>() { + @Override + protected List evaluate() { +- org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, finalCompletions); ++ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, ++ finalCompletions.stream() ++ .map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::suggestion) ++ .collect(java.util.stream.Collectors.toList())); + return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); + } + }; + server.getServer().processQueue.add(syncCompletions); + try { +- completions = syncCompletions.get(); ++ final List legacyCompletions = syncCompletions.get(); ++ completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed ++ // add any new suggestions ++ for (final String completion : legacyCompletions) { ++ if (notNewSuggestion(completions, completion)) { ++ continue; ++ } ++ completions.add(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion(completion)); ++ } + } catch (InterruptedException | ExecutionException e1) { + e1.printStackTrace(); + } + } + + if (!completions.isEmpty()) { +- candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList())); ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { ++ if (completion.suggestion().isEmpty()) { ++ continue; ++ } ++ candidates.add(new Candidate( ++ completion.suggestion(), ++ completion.suggestion(), ++ null, ++ io.papermc.paper.adventure.PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null), ++ null, ++ null, ++ false ++ )); ++ } + } + return; + } +@@ -106,4 +128,15 @@ public class ConsoleCommandCompleter implements Completer { + Thread.currentThread().interrupt(); + } + } ++ ++ // Paper start ++ private boolean notNewSuggestion(final List completions, final String completion) { ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion it : completions) { ++ if (it.suggestion().equals(completion)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch b/Remapped-Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch new file mode 100644 index 000000000..f6416177a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -0,0 +1,316 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Tue, 30 Mar 2021 16:06:08 -0700 +Subject: [PATCH] Enhance console tab completions for brigadier commands + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index c56e7fb18f9a56c8025eb70a524f028b5942da37..efc1e42d606e1c9feb1a4871c0714933ae92a1b2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -479,4 +479,11 @@ public class PaperConfig { + private static void fixEntityPositionDesync() { + fixEntityPositionDesync = getBoolean("settings.fix-entity-position-desync", fixEntityPositionDesync); + } ++ ++ public static boolean enableBrigadierConsoleHighlighting = true; ++ public static boolean enableBrigadierConsoleCompletions = true; ++ private static void consoleSettings() { ++ enableBrigadierConsoleHighlighting = getBoolean("settings.console.enable-brigadier-highlighting", enableBrigadierConsoleHighlighting); ++ enableBrigadierConsoleCompletions = getBoolean("settings.console.enable-brigadier-completions", enableBrigadierConsoleCompletions); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index a4070b59e261f0f1ac4beec47b11492f4724bf27..372a459bd08f79f10ffd1391ec492e37f1e8bb50 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -1,5 +1,7 @@ + package com.destroystokyo.paper.console; + ++import com.destroystokyo.paper.PaperConfig; ++import io.papermc.paper.console.BrigadierCommandHighlighter; + import net.minecraft.server.dedicated.DedicatedServer; + import net.minecrell.terminalconsole.SimpleTerminalConsole; + import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; +@@ -16,11 +18,15 @@ public final class PaperConsole extends SimpleTerminalConsole { + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { +- return super.buildReader(builder ++ builder + .appName("Paper") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) +- ); ++ .option(LineReader.Option.COMPLETE_IN_WORD, true); ++ if (PaperConfig.enableBrigadierConsoleHighlighting) { ++ builder.highlighter(new BrigadierCommandHighlighter(this.server, this.server.createCommandSourceStack())); ++ } ++ return super.buildReader(builder); + } + + @Override +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2768028750fb9a95b3f3b409d047be14bb0083d5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +@@ -0,0 +1,95 @@ ++package io.papermc.paper.console; ++ ++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.suggestion.Suggestion; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.ComponentUtils; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Candidate; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++ ++import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; ++ ++public final class BrigadierCommandCompleter { ++ private final CommandSourceStack commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server, final @NonNull CommandSourceStack commandSourceStack) { ++ this.server = server; ++ this.commandSourceStack = commandSourceStack; ++ } ++ ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { ++ if (!com.destroystokyo.paper.PaperConfig.enableBrigadierConsoleCompletions) { ++ this.addCandidates(candidates, Collections.emptyList(), existing); ++ return; ++ } ++ final CommandDispatcher dispatcher = this.server.getCommands().dispatcher(); ++ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack); ++ this.addCandidates( ++ candidates, ++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), ++ existing ++ ); ++ } ++ ++ private void addCandidates( ++ final @NonNull List candidates, ++ final @NonNull List brigSuggestions, ++ final @NonNull List existing ++ ) { ++ final List completions = new ArrayList<>(); ++ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); ++ for (final Completion completion : existing) { ++ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { ++ continue; ++ } ++ completions.add(completion); ++ } ++ for (final Completion completion : completions) { ++ if (completion.suggestion().isEmpty()) { ++ continue; ++ } ++ candidates.add(toCandidate(completion)); ++ } ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { ++ final String suggestionText = completion.suggestion(); ++ final String suggestionTooltip = PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null); ++ return new Candidate( ++ suggestionText, ++ suggestionText, ++ null, ++ suggestionTooltip, ++ null, ++ null, ++ false ++ ); ++ } ++ ++ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) { ++ if (suggestion.getTooltip() == null) { ++ return completion(suggestion.getText()); ++ } ++ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()))); ++ } ++ ++ static @NonNull StringReader prepareStringReader(final @NonNull String line) { ++ final StringReader stringReader = new StringReader(line); ++ if (stringReader.canRead() && stringReader.peek() == '/') { ++ stringReader.skip(); ++ } ++ return stringReader; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..812027fb84e1b7825f2dd0fb6fa33831367c2dc0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +@@ -0,0 +1,57 @@ ++package io.papermc.paper.console; ++ ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Highlighter; ++import org.jline.reader.LineReader; ++import org.jline.utils.AttributedString; ++import org.jline.utils.AttributedStringBuilder; ++import org.jline.utils.AttributedStyle; ++ ++public final class BrigadierCommandHighlighter implements Highlighter { ++ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; ++ private final CommandSourceStack commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server, final @NonNull CommandSourceStack commandSourceStack) { ++ this.server = server; ++ this.commandSourceStack = commandSourceStack; ++ } ++ ++ @Override ++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { ++ final AttributedStringBuilder builder = new AttributedStringBuilder(); ++ final ParseResults results = this.server.getCommands().dispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack); ++ int pos = 0; ++ if (buffer.startsWith("/")) { ++ builder.append("/", AttributedStyle.DEFAULT); ++ pos = 1; ++ } ++ int component = -1; ++ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { ++ if (node.getRange().getStart() >= buffer.length()) { ++ break; ++ } ++ final int start = node.getRange().getStart(); ++ final int end = Math.min(node.getRange().getEnd(), buffer.length()); ++ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); ++ if (node.getNode() instanceof LiteralCommandNode) { ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); ++ } else { ++ if (++component >= COLORS.length) { ++ component = 0; ++ } ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); ++ } ++ pos = end; ++ } ++ if (pos < buffer.length()) { ++ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ return builder.toAttributedString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 5ed78383ce247ceb24cda0335dbeae293958055c..e2f18b5bf1e091fe5fd868520a6d1bcc2669c24c 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -440,7 +440,7 @@ public class Commands { + }; + } + +- public com.mojang.brigadier.CommandDispatcher getDispatcher() { ++ public com.mojang.brigadier.CommandDispatcher getDispatcher() { return this.dispatcher(); } public com.mojang.brigadier.CommandDispatcher dispatcher() { // Paper - OBFHELPER + return this.dispatcher; + } + +diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +index b5a59aed5d5cfbe0f75a8209b058b368b1f2b595..ed90c4348ca030d678251b0f9891d00153992f89 100644 +--- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java ++++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +@@ -90,7 +90,7 @@ public class ComponentUtils { + TextComponent chatcomponenttext = new TextComponent(""); + boolean flag = true; + +- for (Iterator iterator = elements.iterator(); iterator.hasNext(); flag = false) { ++ for (Iterator iterator = elements.iterator(); iterator.hasNext(); flag = false) { // Paper - decompile fix + T t0 = iterator.next(); + + if (!flag) { +@@ -108,7 +108,7 @@ public class ComponentUtils { + return new TranslatableComponent("chat.square_brackets", new Object[]{text}); + } + +- public static Component fromMessage(Message message) { ++ public static Component fromMessage(Message message) { return fromMessage(message); } public static Component fromMessage(final @org.checkerframework.checker.nullness.qual.NonNull Message message) { // Paper - OBFHELPER + return (Component) (message instanceof Component ? (Component) message : new TextComponent(message.getString())); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index dd8e87ad192c19743577bb95253a127072ea196c..eaad328d0d15ef450bb5a305828ce413d1eab53b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent; + + public class ConsoleCommandCompleter implements Completer { + private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer ++ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper + + public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; ++ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server, this.server.createCommandSourceStack()); // Paper + } + + // Paper start - Change method signature for JLine update +@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { + } + } + +- if (!completions.isEmpty()) { ++ if (false && !completions.isEmpty()) { + for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { + if (completion.suggestion().isEmpty()) { + continue; +@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { + )); + } + } ++ this.addCompletions(reader, line, candidates, completions); + return; + } + +@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { + try { + List offers = waitable.get(); + if (offers == null) { ++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper + return; // Paper - Method returns void + } + + // Paper start - JLine update ++ /* + for (String completion : offers) { + if (completion.isEmpty()) { + continue; +@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { + + candidates.add(new Candidate(completion)); + } ++ */ ++ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); + // Paper end + + // Paper start - JLine handles cursor now +@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { + } + return false; + } ++ ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { ++ this.brigadierCompleter.complete(reader, line, candidates, existing); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/Remapped-Spigot-Server-Patches/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch new file mode 100644 index 000000000..a86a2c1b4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 19 Mar 2021 00:33:15 -0500 +Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly + +When the active item is not cleared, the item is still readied +for use and will repeatedly trigger the PlayerItemConsumeEvent +till their item is switched. +This patch clears the active item when the event is cancelled + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8bc74878919ab7cf6a50d425da61f1b8a8b0ee44..37787a725725d22b0870dcab0f3bec8b94cfd130 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3348,6 +3348,7 @@ public abstract class LivingEntity extends Entity { + level.getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use + // Update client + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + ((ServerPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/Remapped-Spigot-Server-Patches/0703-Add-bypass-host-check.patch b/Remapped-Spigot-Server-Patches/0703-Add-bypass-host-check.patch new file mode 100644 index 000000000..88f6d3d8d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0703-Add-bypass-host-check.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Apr 2021 21:27:01 +0100 +Subject: [PATCH] Add bypass host check + +Paper.bypassHostCheck + +Seriously, fix your firewalls. -.- + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index c648b73a4c478f9d8020274205d6684f7c7c416f..4e055a41de3ee410682cc05a3b883ac8babeb290 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -30,6 +30,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + private static final Component IGNORE_STATUS_REASON = new TextComponent("Ignoring status request"); + private final MinecraftServer server; + private final Connection connection; final Connection getNetworkManager() { return this.connection; } // Paper - OBFHELPER ++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper + + public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { + this.server = server; +@@ -118,7 +119,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + // Spigot Start + //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + String[] split = packet.hostName.split("\00"); +- if ( ( split.length == 3 || split.length == 4 ) && ( HOST_PATTERN.matcher( split[1] ).matches() ) ) { ++ if ( ( split.length == 3 || split.length == 4 ) && ( BYPASS_HOSTCHECK || HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper + packet.hostName = split[0]; + connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort()); + connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); diff --git a/Remapped-Spigot-Server-Patches/0704-don-t-throw-when-loading-invalid-TEs.patch b/Remapped-Spigot-Server-Patches/0704-don-t-throw-when-loading-invalid-TEs.patch new file mode 100644 index 000000000..487cbc3ff --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0704-don-t-throw-when-loading-invalid-TEs.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 20 Apr 2021 01:15:04 +0100 +Subject: [PATCH] don't throw when loading invalid TEs + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 05fa76c02ce61e26891ad995fe89e925ea086557..b7ebb213efd759253f0042f77e11f2a8102ea6ca 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block.entity; + + import javax.annotation.Nullable; + import net.minecraft.CrashReportCategory; ++import net.minecraft.ResourceLocationException; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Registry; + import net.minecraft.nbt.CompoundTag; +@@ -133,7 +134,13 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject { + public static BlockEntity loadStatic(BlockState state, CompoundTag tag) { + String s = tag.getString("id"); + +- return (BlockEntity) Registry.BLOCK_ENTITY_TYPE.getOptional(new ResourceLocation(s)).map((tileentitytypes) -> { ++ // Paper ++ ResourceLocation minecraftKey = null; ++ try { ++ minecraftKey = new ResourceLocation(s); ++ } catch (ResourceLocationException ex) {} ++ // Paper end ++ return (BlockEntity) Registry.BLOCK_ENTITY_TYPE.getOptional(minecraftKey).map((tileentitytypes) -> { + try { + return tileentitytypes.create(); + } catch (Throwable throwable) { diff --git a/Remapped-Spigot-Server-Patches/0705-Set-area-affect-cloud-rotation.patch b/Remapped-Spigot-Server-Patches/0705-Set-area-affect-cloud-rotation.patch new file mode 100644 index 000000000..f907ad7a6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0705-Set-area-affect-cloud-rotation.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 16:58:20 -0400 +Subject: [PATCH] Set area affect cloud rotation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b0212b2043ee5fd77c8876ef0b51ef91488712f0..b44e83d93bba579e439b93e5093350675137b070 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1959,6 +1959,7 @@ public class CraftWorld implements World { + entity = net.minecraft.world.entity.EntityType.LIGHTNING_BOLT.create(world); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.AreaEffectCloud(world, x, y, z); ++ entity.moveTo(x, y, z, yaw, pitch); // Paper - Set area effect cloud Rotation + } else if (EvokerFangs.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.projectile.EvokerFangs(world, x, y, z, (float) Math.toRadians(yaw), 0, null); + } diff --git a/Remapped-Spigot-Server-Patches/0706-add-isDeeplySleeping-to-HumanEntity.patch b/Remapped-Spigot-Server-Patches/0706-add-isDeeplySleeping-to-HumanEntity.patch new file mode 100644 index 000000000..75f3e9118 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0706-add-isDeeplySleeping-to-HumanEntity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 8 Apr 2021 17:36:10 -0700 +Subject: [PATCH] add isDeeplySleeping to HumanEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index aceb57c93c91730345f49f78838780c41ce2dcef..0559f2cfab817e989c02ce2d13bcdabb8ad3b82b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -122,6 +122,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start ++ @Override ++ public boolean isDeeplySleeping() { ++ return getHandle().isSleepingLongEnough(); ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return getHandle().sleepCounter; diff --git a/Remapped-Spigot-Server-Patches/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch b/Remapped-Spigot-Server-Patches/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch new file mode 100644 index 000000000..067239325 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alphaesia +Date: Fri, 23 Apr 2021 09:57:56 +1200 +Subject: [PATCH] Fix duplicating /give items on item drop cancel + +Fixes SPIGOT-2942 (Give command fires PlayerDropItemEvent, cancelling it causes item duplication). + +For every stack of items to give, /give puts the item stack straight +into the player's inventory. However, it also summons a "fake item" +at the player's location. When the PlayerDropItemEvent for this fake +item is cancelled, the server attempts to put the item back into the +player's inventory. The result is that the fake item, which is never +meant to be obtained, is combined with the real items injected directly +into the player's inventory. This means more items than the amount +specified in /give are given to the player - one for every stack of +items given. (e.g. /give @s dirt 1 gives you 2 dirt). + +While this isn't a big issue for general building usage, it can affect +e.g. adventure maps where the number of items the player receives is +important (and you want to restrict the player from throwing items). + +If there are any overflow items that didn't make it into the inventory +(insufficient space), those items are dropped as a real item instead +of a fake one. While cancelling this drop would also result in the +server attempting to put those items into the inventory, since it is +full this has no effect. + +Just ignoring cancellation of the PlayerDropItemEvent seems like the +cleanest and least intrusive way to fix it. + +diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java +index a6259e9160f291cf527a4ea5533a2e5530471874..3b8a7b9bdd2445afa93e4f2dc971a1d252c1463a 100644 +--- a/src/main/java/net/minecraft/server/commands/GiveCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java +@@ -49,7 +49,7 @@ public class GiveCommand { + + if (flag && itemstack.isEmpty()) { + itemstack.setCount(1); +- entityitem = entityplayer.drop(itemstack, false); ++ entityitem = entityplayer.drop(itemstack, false, false, true); // Paper - Fix duplicating /give items on item drop cancel + if (entityitem != null) { + entityitem.makeFakeItem(); + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 4817b8ab259d348b48bc325d34ba9351ffe951df..cfb9bd6b9863a0f6f0f50181b7553adce90cfebe 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -639,7 +639,14 @@ public abstract class Player extends LivingEntity { + + @Nullable + public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { +- if (stack.isEmpty()) { ++ // Paper start - Fix duplicating /give items on item drop cancel ++ return this.drop(stack, throwRandomly, retainOwnership, false); ++ } ++ ++ @Nullable ++ public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean alwaysSucceed) { ++ // Paper end ++ if (itemstack.isEmpty()) { + return null; + } else { + if (this.level.isClientSide) { +@@ -647,17 +654,17 @@ public abstract class Player extends LivingEntity { + } + + double d0 = this.getEyeY() - 0.30000001192092896D; +- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), d0, this.getZ(), stack); ++ ItemEntity entityitem = new ItemEntity(this.level, this.getX(), d0, this.getZ(), itemstack); + + entityitem.setPickUpDelay(40); +- if (retainOwnership) { ++ if (flag1) { + entityitem.setThrower(this.getUUID()); + } + + float f; + float f1; + +- if (throwRandomly) { ++ if (flag) { + f = this.random.nextFloat() * 0.5F; + f1 = this.random.nextFloat() * 6.2831855F; + entityitem.setDeltaMovement((double) (-Mth.sin(f1) * f), 0.20000000298023224D, (double) (Mth.cos(f1) * f)); +@@ -680,12 +687,12 @@ public abstract class Player extends LivingEntity { + PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop); + this.level.getCraftServer().getPluginManager().callEvent(event); + +- if (event.isCancelled()) { ++ if (event.isCancelled() && !alwaysSucceed) { // Paper - Fix duplicating /give items on item drop cancel + org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); +- if (retainOwnership && (cur == null || cur.getAmount() == 0)) { ++ if (flag1 && (cur == null || cur.getAmount() == 0)) { + // The complete stack was dropped + player.getInventory().setItemInHand(drop.getItemStack()); +- } else if (retainOwnership && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) { ++ } else if (flag1 && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) { + // Only one item is dropped + cur.setAmount(cur.getAmount() + 1); + player.getInventory().setItemInHand(cur); +@@ -697,9 +704,9 @@ public abstract class Player extends LivingEntity { + } + // CraftBukkit end + // Paper start - remove player from map on drop +- if (stack.getItem() == Items.FILLED_MAP) { +- MapItemSavedData worldmap = MapItem.getOrCreateSavedData(stack, this.level); +- worldmap.updateSeenPlayers(this, stack); ++ if (itemstack.getItem() == Items.FILLED_MAP) { ++ MapItemSavedData worldmap = MapItem.getOrCreateSavedData(itemstack, this.level); ++ worldmap.updateSeenPlayers(this, itemstack); + } + // Paper end + diff --git a/Remapped-Spigot-Server-Patches/0708-add-consumeFuel-to-FurnaceBurnEvent.patch b/Remapped-Spigot-Server-Patches/0708-add-consumeFuel-to-FurnaceBurnEvent.patch new file mode 100644 index 000000000..f9cc30884 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0708-add-consumeFuel-to-FurnaceBurnEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 16:45:28 -0700 +Subject: [PATCH] add consumeFuel to FurnaceBurnEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 2dcabfc765cbf6341546a7e2c48156fd921fcc82..6e4feb2280021c26dbbfb92feb08c95165e126fe 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -329,7 +329,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + if (this.isLit() && furnaceBurnEvent.isBurning()) { + // CraftBukkit end + flag1 = true; +- if (!itemstack.isEmpty()) { ++ if (!itemstack.isEmpty() && furnaceBurnEvent.willConsumeFuel()) { // Paper + Item item = itemstack.getItem(); + + itemstack.shrink(1); diff --git a/Remapped-Spigot-Server-Patches/0709-add-get-set-drop-chance-to-EntityEquipment.patch b/Remapped-Spigot-Server-Patches/0709-add-get-set-drop-chance-to-EntityEquipment.patch new file mode 100644 index 000000000..c18ac532d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0709-add-get-set-drop-chance-to-EntityEquipment.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 00:28:11 -0700 +Subject: [PATCH] add get-set drop chance to EntityEquipment + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +index e8d6e1abf29a5c50e1cafa01c602f36596d42ecf..9841b4ed648b95272feee45e6f1e8fd9399c322e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +@@ -244,6 +244,17 @@ public class CraftEntityEquipment implements EntityEquipment { + public void setBootsDropChance(float chance) { + setDropChance(net.minecraft.world.entity.EquipmentSlot.FEET, chance); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return getDropChance(CraftEquipmentSlot.getNMS(slot)); ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ setDropChance(CraftEquipmentSlot.getNMS(slot), chance); ++ } ++ // Paper end + + private void setDropChance(net.minecraft.world.entity.EquipmentSlot slot, float chance) { + if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index 7a8181f559cc3c92c3b3aa2ff8eda515719eba08..c326a75728fffbab301654afd94b6839c009fe93 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +@@ -354,4 +354,15 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i + public void setBootsDropChance(float chance) { + throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return 1; ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0710-fix-PigZombieAngerEvent-cancellation.patch b/Remapped-Spigot-Server-Patches/0710-fix-PigZombieAngerEvent-cancellation.patch new file mode 100644 index 000000000..5d4feaac2 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0710-fix-PigZombieAngerEvent-cancellation.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Thu, 18 Mar 2021 21:38:01 +0100 +Subject: [PATCH] fix PigZombieAngerEvent cancellation + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index f85dfd8b57cf81ad7c6b12753fdd42e93f772f9e..16f4dbfd21a287bad3e10c174fa77a1cac771afa 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -28,6 +28,7 @@ public abstract class Goal { + + public void start() { this.start(); } public void start() {} // Paper - OBFHELPER + ++ public final void onTaskResetObfHelper() { stop(); } // Paper - OBFHELPER + public void stop() { + onTaskReset(); // Paper + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +index d1cbbfbf9d3c5e65785aad00c2292245a5de1422..d67a50740b1c6d4ecd49a5541d24d1e4bacb8887 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -49,6 +49,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + private UUID persistentAngerTarget; + private static final IntRange ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); + private int ticksUntilNextAlert; ++ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper + + public ZombifiedPiglin(EntityType type, Level world) { + super(type, world); +@@ -69,7 +70,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + protected void addBehaviourGoals() { + this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); +- this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(new Class[0])); // CraftBukkit - decompile error ++ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = new HurtByTargetGoal(this).setAlertOthers(new Class[0])); // CraftBukkit - decompile error // Paper - assign field + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); + this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); + } +@@ -172,6 +173,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + this.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.setPersistentAngerTarget(null); ++ pathfinderGoalHurtByTarget.onTaskResetObfHelper(); // Paper - clear goalTargets to fix cancellation + return; + } + this.setRemainingPersistentAngerTime(event.getNewAnger()); diff --git a/Remapped-Spigot-Server-Patches/0711-Fix-checkReach-check-for-Shulker-boxes.patch b/Remapped-Spigot-Server-Patches/0711-Fix-checkReach-check-for-Shulker-boxes.patch new file mode 100644 index 000000000..38d7d768a --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0711-Fix-checkReach-check-for-Shulker-boxes.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 4 Apr 2021 14:25:04 -0400 +Subject: [PATCH] Fix checkReach check for Shulker boxes + + +diff --git a/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java +index c40518b0e4ad2b043a9acc858413648d6419f3a3..4b7814a31deeef31e877cee96f0d51b348e7a281 100644 +--- a/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java +@@ -65,6 +65,7 @@ public class ShulkerBoxMenu extends AbstractContainerMenu { + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // Paper - Add reachable override for ContainerShulkerBox + return this.container.stillValid(player); + } + diff --git a/Remapped-Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch b/Remapped-Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch new file mode 100644 index 000000000..094351a8f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Thu, 22 Apr 2021 19:02:07 -0700 +Subject: [PATCH] fix PlayerItemHeldEvent firing twice + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket.java +index 68026536cfc26f07ca99ee9e76fd74b4ed4a995c..848fb02988b0fb319655f790112274ac2a437d25 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket.java +@@ -24,6 +24,7 @@ public class ServerboundSetCarriedItemPacket implements Packet= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ if (packet.getItemInHandIndex() == this.player.inventory.selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change + PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getPlayer(), this.player.inventory.selected, packet.getSlot()); + this.craftServer.getPluginManager().callEvent(event); + if (event.isCancelled()) { diff --git a/Remapped-Spigot-Server-Patches/0713-Added-PlayerDeepSleepEvent.patch b/Remapped-Spigot-Server-Patches/0713-Added-PlayerDeepSleepEvent.patch new file mode 100644 index 000000000..14d231bf0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0713-Added-PlayerDeepSleepEvent.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 21 Apr 2021 15:58:19 -0700 +Subject: [PATCH] Added PlayerDeepSleepEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index cfb9bd6b9863a0f6f0f50181b7553adce90cfebe..c0d2ca1daca0c0c6f21334bc4d9d039440efc453 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -232,6 +232,11 @@ public abstract class Player extends LivingEntity { + + if (this.isSleeping()) { + ++this.sleepCounter; ++ // Paper start ++ if (this.sleepCounter == 100) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { this.sleepCounter = Integer.MIN_VALUE; } ++ } ++ // Paper end + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + } diff --git a/Remapped-Spigot-Server-Patches/0714-More-World-API.patch b/Remapped-Spigot-Server-Patches/0714-More-World-API.patch new file mode 100644 index 000000000..37cb7379d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0714-More-World-API.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 7 Jul 2020 10:52:34 -0700 +Subject: [PATCH] More World API + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a7553a856b9c99bee8f75d514b97cfab952bfd33..511e6a941d441c55a4b38660f0f7f8c47fa689dd 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1863,6 +1863,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return !this.worldDataServer.worldGenSettings().generateFeatures() ? null : this.getChunkSource().getGenerator().findNearestMapFeature(this, feature, pos, radius, skipExistingChunks); // CraftBukkit + } + ++ public BlockPos getNearestBiome(Biome biomeBase, BlockPos blockPosition, int radius, int step) { return this.findNearestBiome(biomeBase, blockPosition, radius, step); } // Paper - OBFHELPER + @Nullable + public BlockPos findNearestBiome(Biome biome, BlockPos pos, int radius, int j) { + return this.getChunkSource().getGenerator().getBiomeSource().findBiomeHorizontal(pos.getX(), pos.getY(), pos.getZ(), radius, j, (biomebase1) -> { +@@ -1885,6 +1886,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + return this.noSave; + } + ++ public RegistryAccess getWorldCustomRegistry() { return registryAccess(); } // Paper - OBFHELPER + @Override + public RegistryAccess registryAccess() { + return this.server.registryAccess(); +diff --git a/src/main/java/net/minecraft/world/level/dimension/DimensionType.java b/src/main/java/net/minecraft/world/level/dimension/DimensionType.java +index 7dead90a0d77e936816c2a54fe70c87d92dc8e5b..fb2bdfbeb44de6ce967af2deb4738972b44dcf44 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/DimensionType.java ++++ b/src/main/java/net/minecraft/world/level/dimension/DimensionType.java +@@ -142,10 +142,10 @@ public class DimensionType { + public static RegistryAccess.RegistryHolder registerBuiltin(RegistryAccess.RegistryHolder registryManager) { + WritableRegistry iregistrywritable = registryManager.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY); + +- iregistrywritable.register(DimensionType.OVERWORLD_LOCATION, (Object) DimensionType.DEFAULT_OVERWORLD, Lifecycle.stable()); +- iregistrywritable.register(DimensionType.OVERWORLD_CAVES_LOCATION, (Object) DimensionType.DEFAULT_OVERWORLD_CAVES, Lifecycle.stable()); +- iregistrywritable.register(DimensionType.NETHER_LOCATION, (Object) DimensionType.DEFAULT_NETHER, Lifecycle.stable()); +- iregistrywritable.register(DimensionType.END_LOCATION, (Object) DimensionType.DEFAULT_END, Lifecycle.stable()); ++ iregistrywritable.register(DimensionType.OVERWORLD_LOCATION, DimensionType.DEFAULT_OVERWORLD, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.register(DimensionType.OVERWORLD_CAVES_LOCATION, DimensionType.DEFAULT_OVERWORLD_CAVES, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.register(DimensionType.NETHER_LOCATION, DimensionType.DEFAULT_NETHER, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.register(DimensionType.END_LOCATION, DimensionType.DEFAULT_END, Lifecycle.stable()); // Paper - decompile fix + return registryManager; + } + +@@ -164,10 +164,10 @@ public class DimensionType { + public static MappedRegistry defaultDimensions(Registry dimensionRegistry, Registry biomeRegistry, Registry chunkGeneratorSettingsRegistry, long seed) { + MappedRegistry registrymaterials = new MappedRegistry<>(Registry.LEVEL_STEM_REGISTRY, Lifecycle.experimental()); + +- registrymaterials.register(LevelStem.NETHER, (Object) (new LevelStem(() -> { ++ registrymaterials.register(LevelStem.NETHER, (new LevelStem(() -> { // Paper - decompile fix + return (DimensionType) dimensionRegistry.getOrThrow(DimensionType.NETHER_LOCATION); + }, defaultNetherGenerator(biomeRegistry, chunkGeneratorSettingsRegistry, seed))), Lifecycle.stable()); +- registrymaterials.register(LevelStem.END, (Object) (new LevelStem(() -> { ++ registrymaterials.register(LevelStem.END, (new LevelStem(() -> { // Paper - decompile fix + return (DimensionType) dimensionRegistry.getOrThrow(DimensionType.END_LOCATION); + }, defaultEndGenerator(biomeRegistry, chunkGeneratorSettingsRegistry, seed))), Lifecycle.stable()); + return registrymaterials; +@@ -256,6 +256,7 @@ public class DimensionType { + return this.brightnessRamp[i]; + } + ++ public Tag getInfiniburnTag() { return infiniburn(); } // Paper - OBFHELPER + public Tag infiniburn() { + Tag tag = BlockTags.getAllTags().getTag(this.infiniburn); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b44e83d93bba579e439b93e5093350675137b070..a1fa2d5e00bd125abd38a00e0bc3936f2fb8186f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2515,6 +2515,75 @@ public class CraftWorld implements World { + return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); + } + ++ // Paper start ++ @Override ++ public Location locateNearestBiome(Location origin, Biome biome, int radius) { ++ return this.locateNearestBiome(origin, biome, radius, 8); ++ } ++ ++ @Override ++ public Location locateNearestBiome(Location origin, Biome biome, int radius, int step) { ++ BlockPos originPos = new BlockPos(origin.getX(), origin.getY(), origin.getZ()); ++ BlockPos nearest = getHandle().getNearestBiome(CraftBlock.biomeToBiomeBase(getHandle().getWorldCustomRegistry().registryOrThrow(Registry.BIOME_REGISTRY), biome), originPos, radius, step); ++ return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); ++ } ++ ++ @Override ++ public boolean isUltrawarm() { ++ return getHandle().dimensionType().ultraWarm(); ++ } ++ ++ @Override ++ public boolean isNatural() { ++ return getHandle().dimensionType().natural(); ++ } ++ ++ @Override ++ public double getCoordinateScale() { ++ return getHandle().dimensionType().coordinateScale(); ++ } ++ ++ @Override ++ public boolean hasSkylight() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean hasBedrockCeiling() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean isPiglinSafe() { ++ return getHandle().dimensionType().piglinSafe(); ++ } ++ ++ @Override ++ public boolean doesBedWork() { ++ return getHandle().dimensionType().bedWorks(); ++ } ++ ++ @Override ++ public boolean doesRespawnAnchorWork() { ++ return getHandle().dimensionType().respawnAnchorWorks(); ++ } ++ ++ @Override ++ public boolean hasRaids() { ++ return getHandle().dimensionType().hasRaids(); ++ } ++ ++ @Override ++ public boolean isFixedTime() { ++ return getHandle().dimensionType().hasFixedTime(); ++ } ++ ++ @Override ++ public Collection getInfiniburn() { ++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(getHandle().dimensionType().getInfiniburnTag().getTagged().iterator(), CraftMagicNumbers::getMaterial)); ++ } ++ // Paper end ++ + @Override + public Raid locateNearestRaid(Location location, int radius) { + Validate.notNull(location, "Location cannot be null"); diff --git a/Remapped-Spigot-Server-Patches/0715-Added-PlayerBedFailEnterEvent.patch b/Remapped-Spigot-Server-Patches/0715-Added-PlayerBedFailEnterEvent.patch new file mode 100644 index 000000000..87c355341 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0715-Added-PlayerBedFailEnterEvent.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:27:41 -0800 +Subject: [PATCH] Added PlayerBedFailEnterEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index c0d2ca1daca0c0c6f21334bc4d9d039440efc453..007d9476943f5a9c75554927df941bda0f7dd6d5 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2230,6 +2230,7 @@ public abstract class Player extends LivingEntity { + this.message = ichatbasecomponent; + } + ++ public @Nullable Component getChatComponent() { return this.getMessage(); }; // Paper - OBFHELPER + @Nullable + public Component getMessage() { + return this.message; +diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index 9dcbc0f741f5980305ae031daac70c7933bc6862..9dcf145b6d1a93785150d268828bca4bb31984c8 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -43,6 +43,8 @@ import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.apache.commons.lang3.ArrayUtils; ++import io.papermc.paper.event.player.PlayerBedFailEnterEvent; // Paper ++import io.papermc.paper.adventure.PaperAdventure; // Paper + + public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock { + +@@ -101,14 +103,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + BlockPos finalblockposition = pos; + // CraftBukkit end + player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { ++ // Paper start - PlayerBedFailEnterEvent ++ if (entityhuman_enumbedresult != null) { ++ PlayerBedFailEnterEvent event = new PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), PlayerBedFailEnterEvent.FailReason.VALUES[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), entityhuman_enumbedresult == Player.BedSleepingProblem.NOT_POSSIBLE_HERE, PaperAdventure.asAdventure(entityhuman_enumbedresult.getChatComponent())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start - handling bed explosion from below here +- if (entityhuman_enumbedresult == Player.BedSleepingProblem.NOT_POSSIBLE_HERE) { ++ if (event.getWillExplode()) { // Paper + this.explodeBed(finaliblockdata, world, finalblockposition); + } else + // CraftBukkit end + if (entityhuman_enumbedresult != null) { +- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); ++ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper ++ if(message != null) player.displayClientMessage(PaperAdventure.asVanilla(message), true); // Paper + } ++ } // Paper + + }); + return InteractionResult.SUCCESS; diff --git a/Remapped-Spigot-Server-Patches/0716-Implement-methods-to-convert-between-Component-and-B.patch b/Remapped-Spigot-Server-Patches/0716-Implement-methods-to-convert-between-Component-and-B.patch new file mode 100644 index 000000000..e796b6207 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0716-Implement-methods-to-convert-between-Component-and-B.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 24 Apr 2021 02:09:32 -0700 +Subject: [PATCH] Implement methods to convert between Component and + Brigadier's Message + + +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.minecraft.network.chat.ComponentUtils; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++import static java.util.Objects.requireNonNull; ++ ++public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { ++ INSTANCE; ++ ++ PaperBrigadierProviderImpl() { ++ PaperBrigadierProvider.initialize(this); ++ } ++ ++ @Override ++ public @NonNull Message message(final @NonNull ComponentLike componentLike) { ++ requireNonNull(componentLike, "componentLike"); ++ return PaperAdventure.asVanilla(componentLike.asComponent()); ++ } ++ ++ @Override ++ public @NonNull Component componentFromMessage(final @NonNull Message message) { ++ requireNonNull(message, "message"); ++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index c2947313cc0eda3247fb4b20ddd1d0b86c37c50a..0198268bc614b190cd84f625a62f6c55247a01c8 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + com.destroystokyo.paper.PaperConfig.registerCommands(); + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now ++ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider + // Paper end + + this.setPvpAllowed(dedicatedserverproperties.pvp); diff --git a/Remapped-Spigot-Server-Patches/0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch b/Remapped-Spigot-Server-Patches/0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch new file mode 100644 index 000000000..cf90fd18e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Fri, 23 Apr 2021 22:42:42 +0100 +Subject: [PATCH] Fix anchor respawn acting as a bed respawn from the end + portal + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 23cfaf5c432221f2d1afe37ba657f723d6d21a73..a4e897171ce05736bcead319b7fda74d2b02fd2e 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -867,6 +867,7 @@ public abstract class PlayerList { + + // Paper start + boolean isBedSpawn = false; ++ boolean isAnchorSpawn = false; + boolean isRespawn = false; + boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 + // Paper end +@@ -887,6 +888,7 @@ public abstract class PlayerList { + if (optional.isPresent()) { + BlockState iblockdata = worldserver1.getBlockState(blockposition); + boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR); ++ isAnchorSpawn = flag3; // Paper - Fix anchor respawn acting as a bed respawn from the end portal + Vec3 vec3d = (Vec3) optional.get(); + float f1; + +@@ -914,7 +916,7 @@ public abstract class PlayerList { + } + + Player respawnPlayer = cserver.getPlayer(entityplayer1); +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !flag2, flag2); ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal + cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.connection.isDisconnected()) { diff --git a/Remapped-Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch b/Remapped-Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch new file mode 100644 index 000000000..b0c5ab326 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 17:17:47 -0700 +Subject: [PATCH] add RespawnFlags to PlayerRespawnEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5b0eb8744dba5df6f16bafd6d907cd1efd508fe6..a0e69cac7699ddc318057c8016e329850d3baa26 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2440,7 +2440,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true); ++ this.player = this.server.getPlayerList().moveToWorld(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - add isEndCreditsRespawn argument + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); + } else { + if (this.player.getHealth() > 0.0F) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a4e897171ce05736bcead319b7fda74d2b02fd2e..6011b43ae8a858f88b8fcf6dc0bf147024a4742c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -816,7 +816,13 @@ public abstract class PlayerList { + return this.moveToWorld(player, this.server.getLevel(player.getRespawnDimension()), alive, null, true); + } + ++ // Paper start + public ServerPlayer moveToWorld(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation) { ++ return moveToWorld(entityplayer, worldserver, flag, location, avoidSuffocation, new org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag[0]); ++ } ++ ++ public ServerPlayer moveToWorld(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { ++ // Paper end + entityplayer.stopRiding(); // CraftBukkit + this.players.remove(entityplayer); + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -916,7 +922,7 @@ public abstract class PlayerList { + } + + Player respawnPlayer = cserver.getPlayer(entityplayer1); +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn, com.google.common.collect.ImmutableSet.builder().add(respawnFlags)); // Paper - Fix anchor respawn acting as a bed respawn from the end portal + cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.connection.isDisconnected()) { diff --git a/Remapped-Spigot-Server-Patches/0719-Introduce-beacon-activation-deactivation-events.patch b/Remapped-Spigot-Server-Patches/0719-Introduce-beacon-activation-deactivation-events.patch new file mode 100644 index 000000000..57b84d6df --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0719-Introduce-beacon-activation-deactivation-events.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spyridon Pagkalos +Date: Thu, 25 Mar 2021 20:28:04 +0200 +Subject: [PATCH] Introduce beacon activation/deactivation events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index fed29e5707e2a7f64159d284c52647dd91e1948e..7025a27c25f8640df06a3c65c2086059769870b5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -199,6 +199,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + this.playSound(SoundEvents.BEACON_AMBIENT); + } + } ++ // Paper start - beacon activation/deactivation events ++ if (!(i1 > 0) && this.levels > 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); ++ } else if (i1 > 0 && !(this.levels > 0)) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ } ++ // Paper end + + if (this.lastCheckY >= l) { + this.lastCheckY = -1; +@@ -255,6 +264,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Tick + + @Override + public void setRemoved() { ++ // Paper start - BeaconDeactivatedEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ // Paper end + this.playSound(SoundEvents.BEACON_DEACTIVATE); + super.setRemoved(); + } diff --git a/Remapped-Spigot-Server-Patches/0720-Add-Channel-initialization-listeners.patch b/Remapped-Spigot-Server-Patches/0720-Add-Channel-initialization-listeners.patch new file mode 100644 index 000000000..72cbb09b5 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0720-Add-Channel-initialization-listeners.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Thu, 29 Apr 2021 21:19:33 +0200 +Subject: [PATCH] Add Channel initialization listeners + + +diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.network; ++ ++import io.netty.channel.Channel; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++/** ++ * Internal API to register channel initialization listeners. ++ *

    ++ * This is not officially supported API and we make no guarantees to the existence or state of this interface. ++ */ ++@FunctionalInterface ++public interface ChannelInitializeListener { ++ ++ void afterInitChannel(@NonNull Channel channel); ++} +diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..30e62719e0a83525daa33cf41cb61df360c0e046 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java +@@ -0,0 +1,74 @@ ++package io.papermc.paper.network; ++ ++import io.netty.channel.Channel; ++import net.kyori.adventure.key.Key; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Internal API to register channel initialization listeners. ++ *

    ++ * This is not officially supported API and we make no guarantees to the existence or state of this class. ++ */ ++public final class ChannelInitializeListenerHolder { ++ ++ private static final Map LISTENERS = new HashMap<>(); ++ private static final Map IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS); ++ ++ private ChannelInitializeListenerHolder() { ++ } ++ ++ /** ++ * Registers whether an initialization listener is registered under the given key. ++ * ++ * @param key key ++ * @return whether an initialization listener is registered under the given key ++ */ ++ public static boolean hasListener(@NonNull Key key) { ++ return LISTENERS.containsKey(key); ++ } ++ ++ /** ++ * Registers a channel initialization listener called after ServerConnection is initialized. ++ * ++ * @param key key ++ * @param listener initialization listeners ++ */ ++ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) { ++ LISTENERS.put(key, listener); ++ } ++ ++ /** ++ * Removes and returns an initialization listener registered by the given key if present. ++ * ++ * @param key key ++ * @return removed initialization listener if present ++ */ ++ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) { ++ return LISTENERS.remove(key); ++ } ++ ++ /** ++ * Returns an immutable map of registered initialization listeners. ++ * ++ * @return immutable map of registered initialization listeners ++ */ ++ public static @NonNull Map getListeners() { ++ return IMMUTABLE_VIEW; ++ } ++ ++ /** ++ * Calls the registered listeners with the given channel. ++ * ++ * @param channel channel ++ */ ++ public static void callListeners(@NonNull Channel channel) { ++ for (ChannelInitializeListener listener : LISTENERS.values()) { ++ listener.afterInitChannel(channel); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index d46910cfdc0aef046a0c79731a85d381953c328a..bcc19d0a4b6c5f683dc416e27a13705b57213d21 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -104,6 +104,7 @@ public class ServerConnectionListener { + pending.add((Connection) object); // Paper + channel.pipeline().addLast("packet_handler", (ChannelHandler) object); + ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); ++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper + } + }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit + } diff --git a/Remapped-Spigot-Server-Patches/0721-Send-empty-commands-if-tab-completion-is-disabled.patch b/Remapped-Spigot-Server-Patches/0721-Send-empty-commands-if-tab-completion-is-disabled.patch new file mode 100644 index 000000000..de0a65c71 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0721-Send-empty-commands-if-tab-completion-is-disabled.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 26 Apr 2021 01:27:08 +0100 +Subject: [PATCH] Send empty commands if tab completion is disabled + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index e2f18b5bf1e091fe5fd868520a6d1bcc2669c24c..1d880a80348527d036b6f8b04f7dd1c4a479fcfd 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -327,7 +327,12 @@ public class Commands { + } + + public void sendCommands(ServerPlayer player) { +- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot ++ // Paper start - Send empty commands if tab completion is disabled ++ if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) { //return; // Spigot ++ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); ++ return; ++ } ++ // Paper end + // CraftBukkit start + // Register Vanilla commands into builtRoot as before + // Paper start - Async command map building diff --git a/Remapped-Spigot-Server-Patches/0722-Add-more-WanderingTrader-API.patch b/Remapped-Spigot-Server-Patches/0722-Add-more-WanderingTrader-API.patch new file mode 100644 index 000000000..b99c0c451 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0722-Add-more-WanderingTrader-API.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Thu, 6 May 2021 14:56:43 +0100 +Subject: [PATCH] Add more WanderingTrader API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index 04c4cca4be8886feb59f180915977b77f9c7dde8..22a695ee3f6c60d484285f1b441b809da61f2436 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -57,6 +57,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + @Nullable + private BlockPos wanderTarget; + private int despawnDelay; ++ // Paper start - Add more WanderingTrader API ++ public boolean canDrinkPotion = true; ++ public boolean canDrinkMilk = true; ++ // Paper end + + public WanderingTrader(EntityType type, Level world) { + super(type, world); +@@ -68,10 +72,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { +- return this.world.isNight() && !entityvillagertrader.isInvisible(); ++ return canDrinkPotion && this.world.isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { +- return this.level.isDay() && entityvillagertrader.isInvisible(); ++ return canDrinkMilk && this.level.isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index a00b1ee5d80d3918ece2260dc1360aa90de16c8a..de58fc26bd811e87cd393bdecee796faf72e65e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -34,4 +34,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + public void setDespawnDelay(int despawnDelay) { + getHandle().setDespawnDelay(despawnDelay); + } ++ ++ // Paper start - Add more WanderingTrader API ++ @Override ++ public void setCanDrinkPotion(boolean bool) { ++ getHandle().canDrinkPotion = bool; ++ } ++ ++ @Override ++ public boolean canDrinkPotion() { ++ return getHandle().canDrinkPotion; ++ } ++ ++ @Override ++ public void setCanDrinkMilk(boolean bool) { ++ getHandle().canDrinkMilk = bool; ++ } ++ ++ @Override ++ public boolean canDrinkMilk() { ++ return getHandle().canDrinkMilk; ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0723-Add-EntityBlockStorage-clearEntities.patch b/Remapped-Spigot-Server-Patches/0723-Add-EntityBlockStorage-clearEntities.patch new file mode 100644 index 000000000..530ee44dc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0723-Add-EntityBlockStorage-clearEntities.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 18:12:29 -0400 +Subject: [PATCH] Add EntityBlockStorage#clearEntities() + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index cbd0587eeecf14b1914bdb33f7a4584bd6b5c8d0..83e5367ee1126afe31e9f704a0daedd310339d15 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -120,6 +120,11 @@ public class BeehiveBlockEntity extends BlockEntity implements TickableBlockEnti + return this.stored.size(); + } + ++ // Paper start - Add EntityBlockStorage clearEntities ++ public void clearBees() { ++ this.stored.clear(); ++ } ++ // Paper end + public static int getHoneyLevel(BlockState state) { + return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +index e61ea9c7fb711e8335a5d0fd5a8bc0152a225038..3346a1962a992566fe840fd0a75cfa06f694833a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +@@ -83,4 +83,10 @@ public class CraftBeehive extends CraftBlockEntityState impl + + getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); + } ++ // Paper start - Add EntityBlockStorage clearEntities ++ @Override ++ public void clearEntities() { ++ getSnapshot().clearBees(); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0724-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/Remapped-Spigot-Server-Patches/0724-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch new file mode 100644 index 000000000..9006bd8ed --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0724-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alvinn8 <42838560+Alvinn8@users.noreply.github.com> +Date: Fri, 8 Jan 2021 20:31:13 +0100 +Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent + + +diff --git a/src/main/java/net/minecraft/advancements/Advancement.java b/src/main/java/net/minecraft/advancements/Advancement.java +index 9d27b658d32297337fb367bae628a1cc3a78712a..4abbe45237667c08753c040ef1281af1b2f34f88 100644 +--- a/src/main/java/net/minecraft/advancements/Advancement.java ++++ b/src/main/java/net/minecraft/advancements/Advancement.java +@@ -76,6 +76,7 @@ public class Advancement { + return this.parent; + } + ++ public final @Nullable DisplayInfo getAdvancementDisplay() { return this.getDisplay(); } // Paper - OBFHELPER + @Nullable + public DisplayInfo getDisplay() { + return this.display; +@@ -125,6 +126,7 @@ public class Advancement { + return this.requirements; + } + ++ public final Component getChatComponent() { return this.getChatComponent(); } // Paper - OBFHELPER + public Component getChatComponent() { + return this.chatComponent; + } +diff --git a/src/main/java/net/minecraft/advancements/DisplayInfo.java b/src/main/java/net/minecraft/advancements/DisplayInfo.java +index 2bcf14826ec1c6a1654246844d03184ceae55d79..1b41578fed1da6d15a806a7200e53954f608b625 100644 +--- a/src/main/java/net/minecraft/advancements/DisplayInfo.java ++++ b/src/main/java/net/minecraft/advancements/DisplayInfo.java +@@ -54,10 +54,12 @@ public class DisplayInfo { + return this.description; + } + ++ public final FrameType getFrameType() { return this.getFrame(); } // Paper - OBFHELPER + public FrameType getFrame() { + return this.frame; + } + ++ public final boolean shouldAnnounceToChat() { return this.shouldAnnounceChat(); } // Paper - OBFHELPER + public boolean shouldAnnounceChat() { + return this.announceChat; + } +diff --git a/src/main/java/net/minecraft/advancements/FrameType.java b/src/main/java/net/minecraft/advancements/FrameType.java +index 0ccac480a760259412b525e66c43de3a209543f5..f6d4b876bdd25942763780f17c8bb69ac3d56031 100644 +--- a/src/main/java/net/minecraft/advancements/FrameType.java ++++ b/src/main/java/net/minecraft/advancements/FrameType.java +@@ -20,6 +20,7 @@ public enum FrameType { + this.displayName = new TranslatableComponent("advancements.toast." + s); + } + ++ public final String getId() { return this.getName(); } // Paper - OBFHELPER + public String getName() { + return this.name; + } +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index b8d3f2c59199e245e2035d6205dd1a042aa93f77..fcb59f6538e66fa43d11d4998e6eeac2e33b0393 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -51,6 +51,7 @@ import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.GameRules; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + + public class PlayerAdvancements { + +@@ -313,10 +314,18 @@ public class PlayerAdvancements { + this.progressChanged.add(advancement); + flag = true; + if (!flag1 && advancementprogress.isDone()) { +- this.player.level.getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit)); // CraftBukkit ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ boolean announceToChat = advancement.getAdvancementDisplay() != null && advancement.getAdvancementDisplay().shouldAnnounceToChat(); ++ net.kyori.adventure.text.Component message = announceToChat ? PaperAdventure.asAdventure(new TranslatableComponent("chat.type.advancement." + advancement.getAdvancementDisplay().getFrameType().getId(), this.player.getDisplayName(), advancement.getChatComponent())) : null; ++ org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit, message); ++ this.player.level.getCraftServer().getPluginManager().callEvent(event); ++ message = event.message(); ++ // Paper end + advancement.getRewards().a(this.player); +- if (advancement.getDisplay() != null && advancement.getDisplay().shouldAnnounceChat() && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { +- this.playerList.broadcastMessage(new TranslatableComponent("chat.type.advancement." + advancement.getDisplay().getFrame().getName(), new Object[]{this.player.getDisplayName(), advancement.getChatComponent()}), ChatType.SYSTEM, Util.NIL_UUID); ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ if (message != null && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { ++ this.playerList.broadcastMessage(PaperAdventure.asVanilla(message), ChatType.SYSTEM, Util.getNullUUID()); ++ // Paper end + } + } + } diff --git a/Remapped-Spigot-Server-Patches/0725-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch b/Remapped-Spigot-Server-Patches/0725-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch new file mode 100644 index 000000000..a376cbbc1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0725-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Wed, 12 May 2021 08:09:19 +0100 +Subject: [PATCH] Add raw address to AsyncPlayerPreLoginEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 573963a09f15046cfcaab83aef906801ce70d75a..99275a8bc2e7bf242ff3c5b5c29924af5328327a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -325,12 +325,13 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + // Paper end + String playerName = gameProfile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress(); ++ java.net.InetAddress rawAddress = ((java.net.InetSocketAddress) connection.getRawAddress()).getAddress(); // Paper + java.util.UUID uniqueId = gameProfile.getId(); + final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; + + // Paper start + PlayerProfile profile = CraftPlayerProfile.asBukkitMirror(getGameProfile()); +- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); + profile.complete(true); diff --git a/Remapped-Spigot-Server-Patches/0726-Inventory-close.patch b/Remapped-Spigot-Server-Patches/0726-Inventory-close.patch new file mode 100644 index 000000000..f206ea0fb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0726-Inventory-close.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 11 May 2021 14:54:56 -0700 +Subject: [PATCH] Inventory#close + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index 7ccc085228f373e6eba55d809bed480d43d5c211..16d34f44e92e26d13188417942f444952f98dfa1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -448,6 +448,14 @@ public class CraftInventory implements Inventory { + clear(i); + } + } ++ // Paper start ++ @Override ++ public int close() { ++ int count = this.inventory.getViewers().size(); ++ com.google.common.collect.Lists.newArrayList(this.inventory.getViewers()).forEach(HumanEntity::closeInventory); ++ return count; ++ } ++ // Paper end + + @Override + public ListIterator iterator() { diff --git a/Remapped-Spigot-Server-Patches/0727-call-PortalCreateEvent-players-and-end-platform.patch b/Remapped-Spigot-Server-Patches/0727-call-PortalCreateEvent-players-and-end-platform.patch new file mode 100644 index 000000000..4776fc7a7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0727-call-PortalCreateEvent-players-and-end-platform.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 May 2021 03:21:22 -0700 +Subject: [PATCH] call PortalCreateEvent players and end platform + + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 727af6ac84075db87615ebac51a024e6376fa3cb..6e2a272075ead399004dabfb7d4f0ea5fd32ffc2 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -496,6 +496,7 @@ public class BlockPos extends Vec3i { + return this.set(this.getX() + direction.getStepX() * distance, this.getY() + direction.getStepY() * distance, this.getZ() + direction.getStepZ() * distance); + } + ++ public BlockPos.MutableBlockPos withOffset(int x, int y, int z) { return move(x, y, z); } // Paper - OBFHELPER + public BlockPos.MutableBlockPos move(int dx, int dy, int dz) { + return this.set(this.getX() + dx, this.getY() + dy, this.getZ() + dz); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 314f168c9d17ab3654c9dda07e48839570f0d332..5d710a1f4e0c61d4be6efe8cebd9b80789868338 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1142,15 +1142,21 @@ public class ServerPlayer extends Player implements ContainerListener { + private void createEndPlatform(ServerLevel world, BlockPos centerPos) { + BlockPos.MutableBlockPos blockposition_mutableblockposition = centerPos.mutable(); + ++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(world); // Paper + for (int i = -2; i <= 2; ++i) { + for (int j = -2; j <= 2; ++j) { + for (int k = -1; k < 3; ++k) { + BlockState iblockdata = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState(); + +- world.setBlockAndUpdate(blockposition_mutableblockposition.set(centerPos).move(j, k, i), iblockdata); ++ blockList.setBlock(blockposition_mutableblockposition.setValues(centerPos).withOffset(j, k, i), iblockdata, 3); // Paper + } + } + } ++ // Paper start ++ if (new org.bukkit.event.world.PortalCreateEvent((List< org.bukkit.block.BlockState>) (List) blockList.getList(), world.getWorld(), this.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM).callEvent()) { ++ blockList.updateList(); ++ } ++ // Paper end + + } + diff --git a/Remapped-Spigot-Server-Patches/0728-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/Remapped-Spigot-Server-Patches/0728-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch new file mode 100644 index 000000000..fb4c1ca38 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0728-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch @@ -0,0 +1,126 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 11 May 2021 00:48:33 +0200 +Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and + Skeletons + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 68e52e3a31e70569d1a92602aff4b7b81c594757..a8a0dba43453b7ac73e8e0faf7728445d9bcc2cd 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -98,9 +98,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + return MobType.UNDEAD; + } + ++ // Paper start ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Paper end ++ + @Override + public void aiStep() { +- boolean flag = this.isSunBurnTick(); ++ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning + + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); +@@ -224,7 +230,16 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + public void readAdditionalSaveData(CompoundTag tag) { + super.readAdditionalSaveData(tag); + this.reassessWeaponGoal(); ++ this.shouldBurnInDay = tag.getBoolean("Paper.ShouldBurnInDay"); // Paper ++ } ++ ++ // Paper start ++ @Override ++ public void addAdditionalSaveData(CompoundTag tag) { ++ super.addAdditionalSaveData(tag); ++ tag.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + } ++ // Paper end + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index a40c23e824652cff59633b7c314e27ec9a515c07..8f4dd4540330966689e71568e9e9ef77f82a786a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -134,7 +134,7 @@ public class Phantom extends FlyingMob implements Enemy { + + @Override + public void aiStep() { +- if (this.isAlive() && this.isSunBurnTick()) { ++ if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning + this.setSecondsOnFire(8); + } + +@@ -165,6 +165,7 @@ public class Phantom extends FlyingMob implements Enemy { + if (tag.hasUUID("Paper.SpawningEntity")) { + this.spawningEntity = tag.getUUID("Paper.SpawningEntity"); + } ++ this.shouldBurnInDay = tag.getBoolean("Paper.ShouldBurnInDay"); + // Paper end + } + +@@ -179,6 +180,7 @@ public class Phantom extends FlyingMob implements Enemy { + if (this.spawningEntity != null) { + tag.setUUID("Paper.SpawningEntity", this.spawningEntity); + } ++ tag.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + // Paper end + } + +@@ -233,6 +235,10 @@ public class Phantom extends FlyingMob implements Enemy { + return spawningEntity; + } + public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } + // Paper end + + class PhantomAttackPlayerTargetGoal extends Goal { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 92162fa22f5e98b7837bde5830bd47c31b8b52d8..011ad2224a3a3a2d255b2498e406fbb047359240 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -39,5 +39,15 @@ public class CraftPhantom extends CraftFlying implements Phantom { + public java.util.UUID getSpawningEntity() { + return getHandle().getSpawningEntity(); + } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index b2d3244cca4d9d108159f3537d8a9aace3f8e77f..28dda8beb1793fad47b2c9db815c0b6cf6ed781a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -36,4 +36,16 @@ public class CraftSkeleton extends CraftMonster implements Skeleton, com.destroy + public void setSkeletonType(SkeletonType type) { + throw new UnsupportedOperationException("Not supported."); + } ++ ++ // Paper start ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0729-Fix-CraftPotionBrewer-cache.patch b/Remapped-Spigot-Server-Patches/0729-Fix-CraftPotionBrewer-cache.patch new file mode 100644 index 000000000..140b30b68 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0729-Fix-CraftPotionBrewer-cache.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sceri +Date: Fri, 14 May 2021 19:06:51 +0500 +Subject: [PATCH] Fix CraftPotionBrewer cache + + +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +index e20d96ec67dcbe935357b4de7e0e9a6984dc3303..eb28a055ba60bb1da3412cf0dbbe5ce76a779f38 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +@@ -15,12 +15,18 @@ import org.bukkit.potion.PotionEffectType; + import org.bukkit.potion.PotionType; + + public class CraftPotionBrewer implements PotionBrewer { +- private static final Map> cache = Maps.newHashMap(); ++ private static final Map> cache = Maps.newHashMap(); // Paper + + @Override + public Collection getEffects(PotionType damage, boolean upgraded, boolean extended) { +- if (cache.containsKey(damage)) +- return cache.get(damage); ++ // Paper start ++ int key = damage.ordinal() << 2; ++ key |= (upgraded ? 1 : 0) << 1; ++ key |= extended ? 1 : 0; ++ ++ if (cache.containsKey(key)) ++ return cache.get(key); ++ // Paper end + + List mcEffects = Potion.byName(CraftPotionUtil.fromBukkit(new PotionData(damage, extended, upgraded))).getEffects(); + +@@ -29,9 +35,9 @@ public class CraftPotionBrewer implements PotionBrewer { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + +- cache.put(damage, builder.build()); ++ cache.put(key, builder.build()); // Paper + +- return cache.get(damage); ++ return cache.get(key); // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0730-Add-basic-Datapack-API.patch b/Remapped-Spigot-Server-Patches/0730-Add-basic-Datapack-API.patch new file mode 100644 index 000000000..547e7cb55 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0730-Add-basic-Datapack-API.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Sun, 16 May 2021 15:07:34 +0100 +Subject: [PATCH] Add basic Datapack API + + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..66debefcd474e4dcc2a8889a82af6c1809c93f46 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +@@ -0,0 +1,51 @@ ++package io.papermc.paper.datapack; ++ ++import Compatibility; ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.packs.repository.Pack; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++public class PaperDatapack implements Datapack { ++ private final String name; ++ private final Compatibility compatibility; ++ private final boolean enabled; ++ ++ PaperDatapack(Pack loader, boolean enabled) { ++ this.name = loader.getName(); ++ this.compatibility = Compatibility.valueOf(loader.getVersion().name()); ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public String getName() { ++ return name; ++ } ++ ++ @Override ++ public Compatibility getCompatibility() { ++ return compatibility; ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ @Override ++ public void setEnabled(boolean enabled) { ++ if (enabled == this.enabled) { ++ return; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ List enabledKeys = server.getPackRepository().getEnabledPacks().stream().map(Pack::getName).collect(Collectors.toList()); ++ if (enabled) { ++ enabledKeys.add(this.name); ++ } else { ++ enabledKeys.remove(this.name); ++ } ++ server.reloadServerResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ec221183bef1065bda0a37e1025958df0d1f6318 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.datapack; ++ ++import java.util.Collection; ++import java.util.stream.Collectors; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackRepository; ++ ++public class PaperDatapackManager implements DatapackManager { ++ private final PackRepository repository; ++ ++ public PaperDatapackManager(PackRepository repository) { ++ this.repository = repository; ++ } ++ ++ @Override ++ public Collection getPacks() { ++ Collection enabledPacks = repository.getEnabledPacks(); ++ return repository.getPacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); ++ } ++ ++ @Override ++ public Collection getEnabledPacks() { ++ return repository.getEnabledPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/packs/repository/Pack.java b/src/main/java/net/minecraft/server/packs/repository/Pack.java +index df5d258b80e37077fa236e4190ad934853c88619..4120326e0d047347e5d7ab9521babe7bb6b6a7db 100644 +--- a/src/main/java/net/minecraft/server/packs/repository/Pack.java ++++ b/src/main/java/net/minecraft/server/packs/repository/Pack.java +@@ -101,6 +101,7 @@ public class Pack implements AutoCloseable { + }); + } + ++ public final PackCompatibility getVersion() { return this.getCompatibility(); } // Paper - OBFHELPER + public PackCompatibility getCompatibility() { + return this.compatibility; + } +@@ -109,6 +110,7 @@ public class Pack implements AutoCloseable { + return (PackResources) this.supplier.get(); + } + ++ public final String getName() { return this.getId(); } // Paper - OBFHELPER + public String getId() { + return this.id; + } +diff --git a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java +index bce2fd67048bd1fd53865eef81bac262dbda2865..8d0ef3bdf9fa283f54628800768717181df28aa5 100644 +--- a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java ++++ b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java +@@ -88,6 +88,7 @@ public class PackRepository implements AutoCloseable { + return this.available.keySet(); + } + ++ public final Collection getPacks() { return this.getAvailablePacks(); } // Paper - OBFHELPER + public Collection getAvailablePacks() { + return this.available.values(); + } +@@ -96,6 +97,7 @@ public class PackRepository implements AutoCloseable { + return (Collection) this.selected.stream().map(Pack::getId).collect(ImmutableSet.toImmutableSet()); + } + ++ public final Collection getEnabledPacks() { return this.getSelectedPacks(); } // Paper - OBFHELPER + public Collection getSelectedPacks() { + return this.selected; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6905256147d9bd79e5f52bf86bdb21c89b8411a7..63639923f7875d76f569b8c6e958782c6462d906 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -18,6 +18,7 @@ import com.mojang.serialization.Lifecycle; + import io.netty.buffer.ByteBuf; + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; ++import io.papermc.paper.datapack.PaperDatapackManager; // Paper + import io.papermc.paper.util.TraceUtil; + import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; + import java.awt.image.BufferedImage; +@@ -264,6 +265,7 @@ public final class CraftServer implements Server { + public boolean ignoreVanillaPermissions = false; + private final List playerView; + public int reloadCount; ++ private final PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { +@@ -346,6 +348,7 @@ public final class CraftServer implements Server { + TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + minimumAPI = configuration.getString("settings.minimum-api"); + loadIcon(); ++ datapackManager = new PaperDatapackManager(console.getPackRepository()); // Paper + } + + public boolean getCommandBlockOverride(String command) { +@@ -2496,5 +2499,11 @@ public final class CraftServer implements Server { + public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { + return mobGoals; + } ++ ++ @Override ++ public PaperDatapackManager getDatapackManager() { ++ return datapackManager; ++ } ++ + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0731-Add-environment-variable-to-disable-server-gui.patch b/Remapped-Spigot-Server-Patches/0731-Add-environment-variable-to-disable-server-gui.patch new file mode 100644 index 000000000..a1f57cca9 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0731-Add-environment-variable-to-disable-server-gui.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Mon, 17 May 2021 00:34:55 -0700 +Subject: [PATCH] Add environment variable to disable server gui + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 9dc9a5e6ad7f23c8bf3553c765ceeecd67a49ac1..ba6c17da4875c3a342da99e354c9f07cc7f17326 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -225,6 +225,7 @@ public class Main { + */ + boolean flag1 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); + ++ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper + if (flag1 && !GraphicsEnvironment.isHeadless()) { + dedicatedserver1.showGui(); + } diff --git a/Remapped-Spigot-Server-Patches/0732-additions-to-PlayerGameModeChangeEvent.patch b/Remapped-Spigot-Server-Patches/0732-additions-to-PlayerGameModeChangeEvent.patch new file mode 100644 index 000000000..31a866937 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0732-additions-to-PlayerGameModeChangeEvent.patch @@ -0,0 +1,138 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 10:04:43 -0700 +Subject: [PATCH] additions to PlayerGameModeChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +index c0098a8f8a9fa2671ff66cbcf50ac74b057d1446..ebbd6f2b0e7236b33d136ab2218c8eca4c5df03e 100644 +--- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -43,7 +43,13 @@ public class DefaultGameModeCommands { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + + if (entityplayer.gameMode.getGameModeForPlayer() != defaultGameMode) { +- entityplayer.setGameMode(defaultGameMode); ++ // Paper start - handle event cancelling the change ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = entityplayer.setGamemode(defaultGameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.text("Failed to set the gamemode of '" + entityplayer.getScoreboardName() + "'", net.kyori.adventure.text.format.NamedTextColor.RED)); ++ if (event != null && event.isCancelled()) { ++ source.sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); ++ continue; ++ } ++ // Paper end + ++i; + } + } +diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +index 3e999090fb3b03b996a9790c53e5b4618c8891f7..3b17c81167f8e011e7f9c09bf42eb632f5a3c2f2 100644 +--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +@@ -62,13 +62,13 @@ public class GameModeCommand { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + + if (entityplayer.gameMode.getGameModeForPlayer() != gameMode) { +- entityplayer.setGameMode(gameMode); +- // CraftBukkit start - handle event cancelling the change +- if (entityplayer.gameMode.getGameModeForPlayer() != gameMode) { +- context.getSource().sendFailure(new net.minecraft.network.chat.TextComponent("Failed to set the gamemode of '" + entityplayer.getScoreboardName() + "'")); ++ // Paper start - handle event cancelling the change ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = entityplayer.setGamemode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.text("Failed to set the gamemode of '" + entityplayer.getScoreboardName() + "'", net.kyori.adventure.text.format.NamedTextColor.RED)); ++ if (event != null && event.isCancelled()) { ++ context.getSource().sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + continue; + } +- // CraftBukkit end ++ // Paper end + logGamemodeChange((CommandSourceStack) context.getSource(), entityplayer, gameMode); + ++i; + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 5d710a1f4e0c61d4be6efe8cebd9b80789868338..779b926921fd435620cbbc69ed6f9931a422b652 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -393,7 +393,16 @@ public class ServerPlayer extends Player implements ContainerListener { + if (this.getY() > 300) this.setPosRaw(getX(), 257, getZ()); // Paper - bring down to a saner Y level if out of world + if (tag.contains("playerGameType", 99)) { + if (this.getServer().getForceGameType()) { ++ // Paper start - call PlayerGameModeChangeEvent on join for players that do not have the correct gamemode ++ if (this.getServer().getDefaultGameType() != GameType.byId(tag.getInt("playerGameType"))) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), GameMode.getByValue(this.getServer().getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { + this.gameMode.setGameModeForPlayer(this.getServer().getDefaultGameType(), GameType.NOT_SET); ++ } else { ++ this.gameMode.setGameModeForPlayer(GameType.byId(tag.getInt("playerGameType")), tag.contains("previousPlayerGameType", 3) ? GameType.byId(tag.getInt("previousPlayerGameType")) : GameType.NOT_SET); // copied from below; if cancelled, set gamemode normally ++ } ++ } else { ++ this.gameMode.setGameModeForPlayer(GameType.byId(tag.getInt("playerGameType")), tag.contains("previousPlayerGameType", 3) ? GameType.byId(tag.getInt("previousPlayerGameType")) : GameType.NOT_SET); // copied from below; if no change needed, set gamemode normally ++ } // Paper end + } else { + this.gameMode.setGameModeForPlayer(GameType.byId(tag.getInt("playerGameType")), tag.contains("previousPlayerGameType", 3) ? GameType.byId(tag.getInt("previousPlayerGameType")) : GameType.NOT_SET); + } +@@ -1789,21 +1798,27 @@ public class ServerPlayer extends Player implements ContainerListener { + + @Override + public void setGameMode(GameType gameMode) { ++ // Paper start - Add cause and nullable message to event ++ setGamemode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ } ++ ++ public PlayerGameModeChangeEvent setGamemode(GameType enumgamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, net.kyori.adventure.text.Component message) { ++ // Paper end + // CraftBukkit start +- if (gameMode == this.gameMode.getGameModeForPlayer()) { +- return; ++ if (enumgamemode == this.gameMode.getGameModeForPlayer()) { ++ return null; // Paper + } + +- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(getBukkitEntity(), GameMode.getByValue(gameMode.getId())); ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(getBukkitEntity(), GameMode.getByValue(enumgamemode.getId()), cause, message); // Paper + level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { +- return; ++ return event; // Paper + } + // CraftBukkit end + +- this.gameMode.setGameModeForPlayer(gameMode); +- this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); +- if (gameMode == GameType.SPECTATOR) { ++ this.gameMode.setGameModeForPlayer(enumgamemode); ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) enumgamemode.getId())); ++ if (enumgamemode == GameType.SPECTATOR) { + this.removeEntitiesOnShoulder(); + this.stopRiding(); + } else { +@@ -1812,6 +1827,7 @@ public class ServerPlayer extends Player implements ContainerListener { + + this.onUpdateAbilities(); + this.updateEffectVisibility(); ++ return event; // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a0e69cac7699ddc318057c8016e329850d3baa26..c454908f23a436f66f8e64fc346186f113b6eefb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2449,7 +2449,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + this.player = this.server.getPlayerList().respawn(this.player, false); + if (this.server.isHardcore()) { +- this.player.setGameMode(GameType.SPECTATOR); ++ this.player.setGamemode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper + ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ec8c7499662c0a810f1337ebc0fa24d2f3ca79e7..3dbe94d9b9647f5cc1e27335b36042e50c652cea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1189,7 +1189,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + throw new IllegalArgumentException("Mode cannot be null"); + } + +- getHandle().setGameMode(GameType.byId(mode.getValue())); ++ getHandle().setGamemode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0733-ItemStack-repair-check-API.patch b/Remapped-Spigot-Server-Patches/0733-ItemStack-repair-check-API.patch new file mode 100644 index 000000000..74dbd3c77 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0733-ItemStack-repair-check-API.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 22:11:11 -0700 +Subject: [PATCH] ItemStack repair check API + + +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index cb079bfd5339b96ad372b0a3b483d02cd0636bfd..42085d3f4ae5c6ceecaffde79fb3187712a2af00 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -269,6 +269,7 @@ public class Item implements ItemLike { + return this.category; + } + ++ public boolean canRepair(ItemStack toBeRepaired, ItemStack repairMaterial) { return isValidRepairItem(toBeRepaired, repairMaterial); } // Paper - OBFHELPER + public boolean isValidRepairItem(ItemStack stack, ItemStack ingredient) { + return false; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 402e5a98290a1701dd67d27c484c97e0a6067c4f..34eed57c7ed884e0d634ca403e38d25c95b6a038 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -485,6 +485,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getItemStackRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } + ++ @Override ++ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { ++ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { ++ return false; ++ } ++ return this.getItem(itemToBeRepaired.getType()).canRepair(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); +diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8d9c9b3bd53d407391d4fcb7fc773153d1a7b402 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Material; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertThrows; ++import static org.junit.Assert.assertTrue; ++ ++public class ItemStackRepairCheckTest extends AbstractTestingBase { ++ ++ @Test ++ public void testIsRepariableBy() { ++ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); ++ ++ assertTrue("diamond pick isn't repairable by a diamond", diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++ ++ @Test ++ public void testCanRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertTrue("diamond can't repair a diamond axe", diamond.canRepair(new ItemStack(Material.DIAMOND_AXE))); ++ } ++ ++ @Test ++ public void testIsNotRepairableBy() { ++ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); ++ ++ assertFalse("acacia sapling is repairable by a diamond", notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++ ++ @Test ++ public void testCanNotRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertFalse("diamond can repair oak button", diamond.canRepair(new ItemStack(Material.OAK_BUTTON))); ++ } ++ ++ @Test ++ public void testInvalidItem() { ++ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); ++ ++ assertFalse("acacia wall sign is repairable by diamond", badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++} diff --git a/Remapped-Spigot-Server-Patches/0734-More-Enchantment-API.patch b/Remapped-Spigot-Server-Patches/0734-More-Enchantment-API.patch new file mode 100644 index 000000000..4bf44e22d --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0734-More-Enchantment-API.patch @@ -0,0 +1,211 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 6 May 2021 19:57:58 -0700 +Subject: [PATCH] More Enchantment API + + +diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +index c23ec1b31950471905c65e46273ae105de853d9b..b994a7aca0ce01b5c0d44b9b126295ffcd2f795d 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +@@ -17,7 +17,7 @@ import net.minecraft.world.item.ItemStack; + + public abstract class Enchantment { + +- private final EquipmentSlot[] slots; ++ private final EquipmentSlot[] slots; public final EquipmentSlot[] getSlots() { return this.slots; } // Paper - OBFHELPER + private final Enchantment.Rarity rarity; + public final EnchantmentCategory category; + @Nullable +@@ -46,6 +46,7 @@ public abstract class Enchantment { + return map; + } + ++ public Enchantment.Rarity getRarity() { return getRarity(); } // Paper - OBFHELPER + public Enchantment.Rarity getRarity() { + return this.rarity; + } +@@ -70,6 +71,7 @@ public abstract class Enchantment { + return 0; + } + ++ public float getDamageIncrease(int level, MobType enumMonsterType) { return getDamageBonus(level, enumMonsterType); } // Paper - OBFHELPER + public float getDamageBonus(int level, MobType group) { + return 0.0F; + } +@@ -123,14 +125,17 @@ public abstract class Enchantment { + return false; + } + ++ public boolean isCursed() { return isCurse(); } // Paper - OBFHELPER + public boolean isCurse() { + return false; + } + ++ public boolean isTradeable() { return isTradeable(); } // Paper - OBFHELPER + public boolean isTradeable() { + return true; + } + ++ public boolean isDiscoverable() { return isDiscoverable(); } // Paper - OBFHELPER + public boolean isDiscoverable() { + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index b1ffe6c7a5915f00a476e88f3a38349b740b4910..20858bb8463ff86c96b5fcdeca455c20c696870f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -1,8 +1,6 @@ + package org.bukkit.craftbukkit.enchantments; + + import net.minecraft.core.Registry; +-import net.minecraft.world.item.enchantment.BindingCurseEnchantment; +-import net.minecraft.world.item.enchantment.VanishingCurseEnchantment; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.enchantments.Enchantment; +@@ -71,7 +69,7 @@ public class CraftEnchantment extends Enchantment { + + @Override + public boolean isCursed() { +- return target instanceof BindingCurseEnchantment || target instanceof VanishingCurseEnchantment; ++ return target.isCursed(); // Paper + } + + @Override +@@ -192,6 +190,45 @@ public class CraftEnchantment extends Enchantment { + public net.kyori.adventure.text.Component displayName(int level) { + return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getTranslationComponentForLevel(level)); + } ++ ++ @Override ++ public boolean isTradeable() { ++ return target.isTradeable(); ++ } ++ ++ @Override ++ public boolean isDiscoverable() { ++ return target.isDiscoverable(); ++ } ++ ++ @Override ++ public io.papermc.paper.enchantments.EnchantmentRarity getRarity() { ++ return fromNMSRarity(target.getRarity()); ++ } ++ ++ @Override ++ public float getDamageIncrease(int level, org.bukkit.entity.EntityCategory entityCategory) { ++ return target.getDamageIncrease(level, org.bukkit.craftbukkit.entity.CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); ++ } ++ ++ @Override ++ public java.util.Set getActiveSlots() { ++ return java.util.stream.Stream.of(target.getSlots()).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet()); ++ } ++ ++ public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) { ++ if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.COMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.RARE; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE; ++ } ++ ++ throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class)); ++ } + // Paper end + + public net.minecraft.world.item.enchantment.Enchantment getHandle() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index eefb6bd580ea176c3a242695ab4af46e7c61b492..25ba7a26c951fc5e4638bdb0db36e94d3e08fb2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -835,5 +835,21 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setHurtDirection(float hurtDirection) { + getHandle().setHurtDirection(hurtDirection); + } ++ ++ public static MobType fromBukkitEntityCategory(EntityCategory entityCategory) { ++ switch (entityCategory) { ++ case NONE: ++ return MobType.UNDEFINED; ++ case UNDEAD: ++ return MobType.UNDEAD; ++ case ARTHROPOD: ++ return MobType.ARTHROPOD; ++ case ILLAGER: ++ return MobType.ILLAGER; ++ case WATER: ++ return MobType.WATER; ++ } ++ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); ++ } + // Paper end + } +diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62b56b5b43696b03fc72cac59f986d006edc3f76 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.enchantments; ++ ++import net.minecraft.world.item.enchantment.Enchantment.Rarity; ++import org.bukkit.craftbukkit.enchantments.CraftEnchantment; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertNotNull; ++ ++public class EnchantmentRarityTest { ++ ++ @Test ++ public void test() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ // Will throw exception if a bukkit counterpart is not found ++ CraftEnchantment.fromNMSRarity(nmsRarity); ++ } ++ } ++} +diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..adb0e5b4268fa115b814143cf29d9a3688e4bc17 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper.entity; ++ ++import com.google.common.base.Joiner; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import net.minecraft.world.entity.EnumMonsterType; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.EntityCategory; ++import org.junit.Test; ++ ++import java.lang.reflect.Field; ++import java.util.Map; ++import java.util.Set; ++ ++import static org.junit.Assert.assertTrue; ++ ++public class EntityCategoryTest { ++ ++ @Test ++ public void test() throws IllegalAccessException { ++ ++ Map enumMonsterTypeFieldMap = Maps.newHashMap(); ++ for (Field field : EnumMonsterType.class.getDeclaredFields()) { ++ if (field.getType() == EnumMonsterType.class) { ++ enumMonsterTypeFieldMap.put( (EnumMonsterType) field.get(null), field.getName()); ++ } ++ } ++ ++ for (EntityCategory entityCategory : EntityCategory.values()) { ++ enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); ++ } ++ assertTrue(EnumMonsterType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents", enumMonsterTypeFieldMap.size() == 0); ++ } ++} diff --git a/Remapped-Spigot-Server-Patches/0735-Add-command-line-option-to-load-extra-plugin-jars-no.patch b/Remapped-Spigot-Server-Patches/0735-Add-command-line-option-to-load-extra-plugin-jars-no.patch new file mode 100644 index 000000000..6c752cc24 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0735-Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Tue, 18 May 2021 14:39:44 -0700 +Subject: [PATCH] Add command line option to load extra plugin jars not in the + plugins folder + +ex: java -jar paperclip.jar nogui -add-plugin=/path/to/plugin.jar -add-plugin=/path/to/another/plugin_jar.jar + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 63639923f7875d76f569b8c6e958782c6462d906..f1e6d0050092ad51bf233c80b6a51a121e961b07 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -384,8 +384,13 @@ public final class CraftServer implements Server { + + File pluginFolder = (File) console.options.valueOf("plugins"); + +- if (pluginFolder.exists()) { +- Plugin[] plugins = pluginManager.loadPlugins(pluginFolder); ++ // Paper start ++ if (true || pluginFolder.exists()) { ++ if (!pluginFolder.exists()) { ++ pluginFolder.mkdirs(); ++ } ++ Plugin[] plugins = pluginManager.loadPlugins(pluginFolder, this.extraPluginJars()); ++ // Paper end + for (Plugin plugin : plugins) { + try { + String message = String.format("Loading %s", plugin.getDescription().getFullName()); +@@ -400,6 +405,18 @@ public final class CraftServer implements Server { + } + } + ++ // Paper start ++ private List extraPluginJars() { ++ @SuppressWarnings("unchecked") ++ final List jars = (List) this.console.options.valuesOf("add-plugin"); ++ return jars.stream() ++ .filter(File::exists) ++ .filter(File::isFile) ++ .filter(file -> file.getName().endsWith(".jar")) ++ .collect(java.util.stream.Collectors.toList()); ++ } ++ // Paper end ++ + public void enablePlugins(PluginLoadOrder type) { + if (type == PluginLoadOrder.STARTUP) { + helpMap.clear(); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index bd10345cb90f98b8af1519afd603a5244f3a5ca2..521542999b25b1da448fadb3fe6531e083f93e67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -153,6 +153,12 @@ public class Main { + .ofType(String.class) + .defaultsTo("Unknown Server") + .describedAs("Name"); ++ ++ acceptsAll(asList("add-plugin", "add-extra-plugin-jar")) ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File[] {}) ++ .describedAs("Specify paths to extra plugin jars to be loaded in addition to those in the plugins folder. This argument can be specified multiple times, once for each extra plugin jar path."); + // Paper end + } + }; diff --git a/Remapped-Spigot-Server-Patches/0736-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch b/Remapped-Spigot-Server-Patches/0736-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch new file mode 100644 index 000000000..7f7be61f7 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0736-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 10 May 2021 15:46:57 -0700 +Subject: [PATCH] Fix incorrect status dataconverter for pre 1.13 chunks + +Vanilla was setting non-populated OR non-lit chunks to empty, but +really this is just completely wrong. It should be set to "carved" +at minmum, because pre 1.13 chunks went through 3 distinct stages +of generation: carving, population, and lighting - in this order. +There is no "empty" status, because a chunk was simply carved +or it didn't exist. So mapping any chunk data to empty is simply +invalid. + +If the chunk is terrain populated, then obviously it must be at +minmum "decorated." If the chunk is lit and populated, then it is marked +"mobs_spawned" (which is what Vanilla is doing, and this is the last +stage before moving to full so it looks correct). + +So now here is a table representing the new status conversion: + +Chunk is lit Chunk is populated Vanilla + F F empty + T F empty + F T empty + T T mobs_spawned + +Chunk is lit Chunk is populated Paper + F F carved + T F carved + F T decorated + T T mobs_spawned + +This should fix some problems converting old data, as the +changes here are going to prevent the chunk from being regenerated +incorrectly. + +diff --git a/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java b/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java +index 16f6aa39385e2d278d4883a32c11ddd119d0d85d..3d262dcf6db0e395d2fa457f7197f24013037ac1 100644 +--- a/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java ++++ b/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java +@@ -43,13 +43,21 @@ public class ChunkToProtochunkFix extends DataFix { + return dynamic.asStreamOpt().result(); + }); + Dynamic dynamic = (Dynamic) typed1.get(DSL.remainderFinder()); +- boolean flag = dynamic.get("TerrainPopulated").asBoolean(false) && (!dynamic.get("LightPopulated").asNumber().result().isPresent() || dynamic.get("LightPopulated").asBoolean(false)); +- +- dynamic = dynamic.set("Status", dynamic.createString(flag ? "mobs_spawned" : "empty")); ++ // Paper start - fix incorrect status conversion ++ // Vanilla is setting chunks to incorrect status here, they should be using at minimum carved. ++ // for populated chunks, it should be at minimum decorated ++ // and for lit and populated, mobs_spawned is correct (technically mobs_spawned should be for populated, ++ // but if it's not lit then it can't be set above lit) ++ final boolean terrainPopulated = dynamic.get("TerrainPopulated").asBoolean(false); ++ final boolean lightPopulated = dynamic.get("LightPopulated").asBoolean(false) || dynamic.get("LightPopulated").asNumber().result().isPresent(); ++ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated"); ++ ++ dynamic = dynamic.set("Status", dynamic.createString(newStatus)); + dynamic = dynamic.set("hasLegacyStructureData", dynamic.createBoolean(true)); +- Dynamic dynamic1; ++ // Paper end - fix incorrect status conversion ++ Dynamic dynamic1; // Paper - decompile fix + +- if (flag) { ++ if (true) { // Paper - fix incorrect status conversion + Optional optional1 = dynamic.get("Biomes").asByteBufferOpt().result(); + + if (optional1.isPresent()) { +@@ -70,7 +78,7 @@ public class ChunkToProtochunkFix extends DataFix { + }).collect(Collectors.toList()); + + if (optional.isPresent()) { +- ((Stream) optional.get()).forEach((dynamic2) -> { ++ optional.get().forEach((dynamic2) -> { // Paper - decompile fix + int j = dynamic2.get("x").asInt(0); + int k = dynamic2.get("y").asInt(0); + int l = dynamic2.get("z").asInt(0); +@@ -78,11 +86,11 @@ public class ChunkToProtochunkFix extends DataFix { + + ((ShortList) list.get(k >> 4)).add(short0); + }); ++ Dynamic finalDynamic = dynamic; // Paper - decompile fix + dynamic = dynamic.set("ToBeTicked", dynamic.createList(list.stream().map((shortlist) -> { +- Stream stream = shortlist.stream(); ++ Stream stream = shortlist.stream(); // Paper - decompile fix + +- dynamic.getClass(); +- return dynamic.createList(stream.map(dynamic::createShort)); ++ return finalDynamic.createList(stream.map(finalDynamic::createShort)); + }))); + } + diff --git a/Remapped-Spigot-Server-Patches/0737-Fix-MC-148809-Increase-structure-block-data-length-t.patch b/Remapped-Spigot-Server-Patches/0737-Fix-MC-148809-Increase-structure-block-data-length-t.patch new file mode 100644 index 000000000..885e2d6ca --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0737-Fix-MC-148809-Increase-structure-block-data-length-t.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SamB440 +Date: Fri, 21 May 2021 00:22:09 +0100 +Subject: [PATCH] Fix MC-148809: Increase structure block data length to 128 + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java +index 4c797dd82bb1989861e350a7e628eb847b58bbd8..4792aafd8d992cd64d05f8bbef5cbf30988949ed 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java +@@ -43,7 +43,7 @@ public class ServerboundSetStructureBlockPacket implements Packet +Date: Thu, 20 May 2021 07:02:22 -0700 +Subject: [PATCH] Fix and optimise world force upgrading + +The WorldUpgrader class was incorrectly modified by +CB. It will store an IChunkLoader instance for all +dimension types in the world, but obviously with how +CB shifts around worlds only one dimension type exists +per world. But this would be OK if CB did this +change correctly. All IChunkLoader instances +will point to the same regionfiles. And all +IChunkLoader instances are going to be read from. + +This problem hasn't really been reported because +it relies on the persistent legacy data to be converted +as well to cause corruption. Why? Because the legacy +data is also shared, it will result in different +outputs from conversion (as once conversion for legacy +persistent data takes place, it is REMOVED - so the next +convert will _not_ have the data). Which means different +sizes on disk. Which means different regionfile sector +allocations. Which means there are 3 different possible +regionfile sector allocations in memory, and none of them +are going to be correct. + +I've fixed this by writing a world upgrader suited to +CB's changes to world folder format. It was brain dead +easy to add threading, so I did. + +diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f476f21bcfd64d4eb2b690c9100275093c49c9d6 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +@@ -0,0 +1,200 @@ ++package io.papermc.paper.world; ++ ++import com.mojang.datafixers.DataFixer; ++import net.minecraft.SharedConstants; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.util.worldupdate.WorldUpgrader; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.ChunkStorage; ++import net.minecraft.world.level.chunk.storage.RegionFileStorage; ++import net.minecraft.world.level.dimension.DimensionType; ++import net.minecraft.world.level.dimension.LevelStem; ++import net.minecraft.world.level.storage.DimensionDataStorage; ++import net.minecraft.world.level.storage.LevelStorageSource; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import java.io.File; ++import java.io.IOException; ++import java.text.DecimalFormat; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Supplier; ++ ++public class ThreadedWorldUpgrader { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final ResourceKey dimensionType; ++ private final ResourceKey worldKey; ++ private final String worldName; ++ private final ExecutorService threadPool; ++ private final DataFixer dataFixer; ++ private final boolean removeCaches; ++ ++ public ThreadedWorldUpgrader(final ResourceKey dimensionType, final ResourceKey worldKey, final String worldName, final int threads, ++ final DataFixer dataFixer, final boolean removeCaches) { ++ this.dimensionType = dimensionType; ++ this.worldKey = worldKey; ++ this.worldName = worldName; ++ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() { ++ private final AtomicInteger threadCounter = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement()); ++ ret.setUncaughtExceptionHandler((thread, throwable) -> { ++ LOGGER.fatal("Error upgrading world", throwable); ++ }); ++ ++ return ret; ++ } ++ }); ++ this.dataFixer = dataFixer; ++ this.removeCaches = removeCaches; ++ } ++ ++ public void convert() { ++ final File worldFolder = LevelStorageSource.getFolder(new File(this.worldName), this.dimensionType); ++ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer); ++ ++ final File regionFolder = new File(worldFolder, "region"); ++ ++ LOGGER.info("Force upgrading " + this.worldName); ++ LOGGER.info("Counting regionfiles for " + this.worldName); ++ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> { ++ return WorldUpgrader.getRegionfileRegex().matcher(name).matches(); ++ }); ++ if (regionFiles == null) { ++ LOGGER.info("Found no regionfiles to convert for world " + this.worldName); ++ return; ++ } ++ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert"); ++ LOGGER.info("Starting conversion now for world " + this.worldName); ++ ++ final WorldInfo info = new WorldInfo(() -> worldPersistentData, ++ new ChunkStorage(regionFolder, this.dataFixer, false), this.removeCaches, this.worldKey); ++ ++ long expectedChunks = (long)regionFiles.length * (32L * 32L); ++ ++ for (final File regionFile : regionFiles) { ++ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile); ++ if (regionPos == null) { ++ expectedChunks -= (32L * 32L); ++ continue; ++ } ++ ++ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); ++ } ++ this.threadPool.shutdown(); ++ ++ final DecimalFormat format = new DecimalFormat("#0.00"); ++ ++ final long start = System.nanoTime(); ++ ++ while (!this.threadPool.isTerminated()) { ++ final long current = info.convertedChunks.get(); ++ ++ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks); ++ ++ try { ++ Thread.sleep(1000L); ++ } catch (final InterruptedException ignore) {} ++ } ++ ++ final long end = System.nanoTime(); ++ ++ try { ++ info.loader.close(); ++ } catch (final IOException ex) { ++ LOGGER.fatal("Failed to close chunk loader", ex); ++ } ++ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)", ++ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0)); ++ } ++ ++ private static final class WorldInfo { ++ ++ public final Supplier persistentDataSupplier; ++ public final ChunkStorage loader; ++ public final boolean removeCaches; ++ public final ResourceKey worldKey; ++ public final AtomicLong convertedChunks = new AtomicLong(); ++ public final AtomicLong modifiedChunks = new AtomicLong(); ++ ++ private WorldInfo(final Supplier persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches, ++ final ResourceKey worldKey) { ++ this.persistentDataSupplier = persistentDataSupplier; ++ this.loader = loader; ++ this.removeCaches = removeCaches; ++ this.worldKey = worldKey; ++ } ++ } ++ ++ private static final class ConvertTask implements Runnable { ++ ++ private final WorldInfo worldInfo; ++ private final int regionX; ++ private final int regionZ; ++ ++ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) { ++ this.worldInfo = worldInfo; ++ this.regionX = regionX; ++ this.regionZ = regionZ; ++ } ++ ++ @Override ++ public void run() { ++ final int regionCX = this.regionX << 5; ++ final int regionCZ = this.regionZ << 5; ++ ++ final Supplier persistentDataSupplier = this.worldInfo.persistentDataSupplier; ++ final ChunkStorage loader = this.worldInfo.loader; ++ final boolean removeCaches = this.worldInfo.removeCaches; ++ final ResourceKey worldKey = this.worldInfo.worldKey; ++ ++ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) { ++ for (int cx = regionCX; cx < (regionCX + 32); ++cx) { ++ final ChunkPos chunkPos = new ChunkPos(cx, cz); ++ try { ++ // no need to check the coordinate of the chunk, the regionfilecache does that for us ++ ++ CompoundTag chunkNBT = loader.read(chunkPos); ++ ++ if (chunkNBT == null) { ++ continue; ++ } ++ ++ final int versionBefore = ChunkStorage.getVersion(chunkNBT); ++ ++ chunkNBT = loader.getChunkData(worldKey, persistentDataSupplier, chunkNBT, chunkPos, null); ++ ++ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getWorldVersion(); ++ ++ if (removeCaches) { ++ final CompoundTag level = chunkNBT.getCompound("Level"); ++ modified |= level.contains("Heightmaps"); ++ level.remove("Heightmaps"); ++ modified |= level.contains("isLightOn"); ++ level.remove("isLightOn"); ++ } ++ ++ if (modified) { ++ this.worldInfo.modifiedChunks.getAndIncrement(); ++ loader.write(chunkPos, chunkNBT); ++ } ++ } catch (final Exception ex) { ++ LOGGER.error("Error upgrading chunk {}", chunkPos, ex); ++ } finally { ++ this.worldInfo.convertedChunks.getAndIncrement(); ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index ba6c17da4875c3a342da99e354c9f07cc7f17326..d0105061c714af4403b3e14e96e54e17a82c172b 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -15,6 +15,7 @@ import java.nio.file.Paths; + import java.util.Optional; + import java.util.concurrent.CompletableFuture; + import java.util.function.BooleanSupplier; ++import io.papermc.paper.world.ThreadedWorldUpgrader; + import joptsimple.NonOptionArgumentSpec; + import joptsimple.OptionParser; + import joptsimple.OptionSet; +@@ -269,6 +270,15 @@ public class Main { + } + // Paper end + ++ // Paper start - fix and optimise world upgrading ++ public static void convertWorldButItWorks(ResourceKey dimensionType, ResourceKey worldKey, String worldName, ++ DataFixer dataFixer, boolean removeCaches) { ++ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8; ++ final ThreadedWorldUpgrader worldUpgrader = new ThreadedWorldUpgrader(dimensionType, worldKey, worldName, threads, dataFixer, removeCaches); ++ worldUpgrader.convert(); ++ } ++ // Paper end - fix and optimise world upgrading ++ + public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier booleansupplier, ImmutableSet> worlds) { // CraftBukkit + Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, worlds, eraseCache); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fd76d776c7003585c9efef44c6d7da0f6c3f574e..9d7cebd703bd0171ca3e95d2985c1a52fdb59712 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -512,13 +512,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return true; +- }, worlddata.worldGenSettings().dimensions().entrySet().stream().map((entry1) -> { +- return ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, ((ResourceKey) entry1.getKey()).location()); +- }).collect(ImmutableSet.toImmutableSet())); +- } ++ // Paper - move down + + ServerLevelData iworlddataserver = worlddata; + WorldGenSettings generatorsettings = worlddata.worldGenSettings(); +@@ -538,6 +532,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop worldKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, dimensionKey.location()); + + if (dimensionKey == LevelStem.OVERWORLD) { +diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +index 79491c5081dbc0cc479d6bc0329ff9b374559c9b..29c0fe698e91e232bcebfafdd853d222c83d5af8 100644 +--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java ++++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +@@ -52,7 +52,7 @@ public class WorldUpgrader { + private volatile int skipped; + private final Object2FloatMap> progressMap = Object2FloatMaps.synchronize(new Object2FloatOpenCustomHashMap(Util.identityStrategy())); // CraftBukkit + private volatile Component status = new TranslatableComponent("optimizeWorld.stage.counting"); +- private static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); ++ private static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); public static final Pattern getRegionfileRegex() { return REGEX; } // Paper - OBFHELPER + private final DimensionDataStorage overworldDataStorage; + + public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, ImmutableSet> worlds, boolean eraseCache) { // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 4523bc1f49e7be248a47eeb599fa7b6550dbb08d..d155d00abf8f423e64e6e6d80ddadbc1cfb58a64 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -181,6 +181,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return typeKey; + } + ++ // Paper start - fix and optimise world upgrading ++ // copied from below ++ public static ResourceKey getDimensionKey(DimensionType manager) { ++ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryHolder.dimensionTypes().getResourceKey(manager).orElseThrow(() -> { ++ return new IllegalStateException("Unregistered dimension type: " + manager); ++ }); ++ } ++ // Paper end - fix and optimise world upgrading ++ + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 1af804c5c6fb2b20ea3f020610763c1d7dcee110..0e38f2f31d167c417b707f00aa68cacaef3d9f6c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -109,6 +109,7 @@ public class ChunkStorage implements AutoCloseable { + return nbttagcompound; + } + ++ public static int getVersion(CompoundTag nbttagcompound) { return getVersion(nbttagcompound); } // Paper - OBFHELPER + public static int getVersion(CompoundTag tag) { + return tag.contains("DataVersion", 99) ? tag.getInt("DataVersion") : -1; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 0498982ac14f20145d68dbf64a46bcaacf5516ef..7a01f2fbe459e36cee5416455a049b25963e257a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -30,6 +30,28 @@ public class RegionFileStorage implements AutoCloseable { // Paper - no final + + + // Paper start ++ public static ChunkPos getRegionFileCoordinates(File file) { ++ String fileName = file.getName(); ++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkPos(x << 5, z << 5); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ + public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { // Paper - synchronize for async io + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f1e6d0050092ad51bf233c80b6a51a121e961b07..831f187cde88e815c9a859e52ab45fbbe054f83e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1145,14 +1145,7 @@ public final class CraftServer implements Server { + } + worlddata.checkName(name); + worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().isPresent()); +- +- if (console.options.has("forceUpgrade")) { +- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), console.options.has("eraseCache"), () -> { +- return true; +- }, worlddata.worldGenSettings().dimensions().entrySet().stream().map((entry) -> { +- return ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, ((ResourceKey) entry.getKey()).location()); +- }).collect(ImmutableSet.toImmutableSet())); +- } ++ // Paper - move down + + long j = BiomeManager.obfuscateSeed(creator.seed()); + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); +@@ -1169,6 +1162,14 @@ public final class CraftServer implements Server { + chunkgenerator = worlddimension.generator(); + } + ++ // Paper start - fix and optimise world upgrading ++ if (console.options.has("forceUpgrade")) { ++ net.minecraft.server.Main.convertWorldButItWorks( ++ actualDimension, net.minecraft.world.level.Level.getDimensionKey(dimensionmanager), worldSession.getLevelId(), DataFixers.getDataFixer(), console.options.has("eraseCache") ++ ); ++ } ++ // Paper end - fix and optimise world upgrading ++ + ResourceKey worldKey; + String levelName = this.getServer().getProperties().levelName; + if (name.equals(levelName + "_nether")) { diff --git a/Remapped-Spigot-Server-Patches/0739-Add-Mob-lookAt-API.patch b/Remapped-Spigot-Server-Patches/0739-Add-Mob-lookAt-API.patch new file mode 100644 index 000000000..ce4217778 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0739-Add-Mob-lookAt-API.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 14 May 2021 13:42:17 -0500 +Subject: [PATCH] Add Mob#lookAt API + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index ea34306858116e5626383af408529091836c2752..5692b497875ba2ee455859bc8a88d7888afd86fc 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -850,14 +850,17 @@ public abstract class Mob extends LivingEntity { + + protected void customServerAiStep() {} + ++ public int getMaxHeadXRot() { return getMaxHeadXRot(); } // Paper - OBFHELPER + public int getMaxHeadXRot() { + return 40; + } + ++ public int getMaxHeadYRot() { return getMaxHeadYRot(); } // Paper - OBFHELPER + public int getMaxHeadYRot() { + return 75; + } + ++ public int getHeadRotSpeed() { return getHeadRotSpeed(); } // Paper - OBFHELPER + public int getHeadRotSpeed() { + return 10; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/LookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/LookControl.java +index faba4a95883bb0fcfd4f65c3f62bd6f476ded249..3fe159c4bdc3ad3e95354e18e2921305af121725 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/LookControl.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/LookControl.java +@@ -20,18 +20,28 @@ public class LookControl { + this.mob = entity; + } + ++ public void lookAt(Vec3 vec3d) { setLookAt(vec3d); } // Paper - OBFHELPER + public void setLookAt(Vec3 direction) { + this.setLookAt(direction.x, direction.y, direction.z); + } + ++ // Paper start ++ public void lookAt(Entity entity) { ++ this.lookAt(entity.getX(), getWantedY(entity), entity.getZ()); ++ } ++ // Paper end ++ ++ public void lookAt(Entity entity, float f, float f1) { setLookAt(entity, f, f1); } // Paper - OBFHELPER + public void setLookAt(Entity entity, float yawSpeed, float pitchSpeed) { + this.setLookAt(entity.getX(), getWantedY(entity), entity.getZ(), yawSpeed, pitchSpeed); + } + ++ public void lookAt(double d0, double d1, double d2) { setLookAt(d0, d1, d2); } // Paper - OBFHELPER + public void setLookAt(double x, double y, double z) { + this.setLookAt(x, y, z, (float) this.mob.getHeadRotSpeed(), (float) this.mob.getMaxHeadXRot()); + } + ++ public void lookAt(double d0, double d1, double d2, float f, float f1) { setLookAt(d0, d1, d2, f, f1); } // Paper - OBFHELPER + public void setLookAt(double x, double y, double z, float yawSpeed, float pitchSpeed) { + this.wantedX = x; + this.wantedY = y; +@@ -103,6 +113,7 @@ public class LookControl { + return from + f4; + } + ++ public static double getWantedY(Entity entity) { return getWantedY(entity); } // Paper - OBFHELPER + private static double getWantedY(Entity entity) { + return entity instanceof LivingEntity ? entity.getEyeY() : (entity.getBoundingBox().minY + entity.getBoundingBox().maxY) / 2.0D; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 1e3a0851c75d8067d2699f00bb3f6621d1d739d8..f597cf70779fde265cc45868aba3ae9db898fb6e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -83,5 +83,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public boolean isInDaylight() { + return getHandle().isInDaylight(); + } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().lookAt(location.getX(), location.getY(), location.getZ()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().lookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().lookAt(((CraftEntity) entity).getHandle()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().lookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z) { ++ getHandle().getLookControl().lookAt(x, y, z); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) { ++ getHandle().getLookControl().lookAt(x, y, z, headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public int getHeadRotationSpeed() { ++ return getHandle().getHeadRotSpeed(); ++ } ++ ++ @Override ++ public int getMaxHeadPitch() { ++ return getHandle().getMaxHeadXRot(); ++ } + // Paper end + } diff --git a/Remapped-Spigot-Server-Patches/0740-Add-Unix-domain-socket-support.patch b/Remapped-Spigot-Server-Patches/0740-Add-Unix-domain-socket-support.patch new file mode 100644 index 000000000..83c74f604 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0740-Add-Unix-domain-socket-support.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Tue, 11 May 2021 17:39:22 -0400 +Subject: [PATCH] Add Unix domain socket support + +For Windows and ARM support, JEP-380 is required: +https://inside.java/2021/02/03/jep380-unix-domain-sockets-channels/ +This will be possible as of the Minecraft 1.17 Java version bump. + +Tested-by: Mariell Hoversholm +Reviewed-by: Mariell Hoversholm + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 3ba9c38fc44a8edba9b504112a383249052a0035..cc823a1337bea3ad552687add46706128311f26d 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -564,6 +564,11 @@ public class Connection extends SimpleChannelInboundHandler> { + // Spigot Start + public SocketAddress getRawAddress() + { ++ // Paper start - this can be nullable in the case of a Unix domain socket, so if it is, fake something ++ if (this.channel.remoteAddress() == null) { ++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); ++ } ++ // Paper end + return this.channel.remoteAddress(); + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 0198268bc614b190cd84f625a62f6c55247a01c8..2821fa9505d6b5adc8e776219df024713fc1a486 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -222,6 +222,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); + // this.saveData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading + DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress bindAddress; ++ if (this.getLocalIp().startsWith("unix:")) { ++ if (!io.netty.channel.epoll.Epoll.isAvailable()) { ++ DedicatedServer.LOGGER.fatal("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.fatal("You are trying to use a Unix domain socket but you're not on a supported OS."); ++ return false; ++ } else if (!com.destroystokyo.paper.PaperConfig.velocitySupport && !org.spigotmc.SpigotConfig.bungee) { ++ DedicatedServer.LOGGER.fatal("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.fatal("Unix domain sockets require IPs to be forwarded from a proxy."); ++ return false; ++ } ++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); ++ } else { + InetAddress inetaddress = null; + + if (!this.getLocalIp().isEmpty()) { +@@ -231,12 +245,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (this.getPort() < 0) { + this.setPort(dedicatedserverproperties.serverPort); + } ++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); ++ } ++ // Paper end + + this.initializeKeyPair(); + DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); + + try { +- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); ++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support + } catch (IOException ioexception) { + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index bcc19d0a4b6c5f683dc416e27a13705b57213d21..67a17ced9460bc83a6f564b38fdb43b1a7f1b8a0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -70,7 +70,12 @@ public class ServerConnectionListener { + this.running = true; + } + ++ // Paper start + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ bind(new java.net.InetSocketAddress(address, port)); ++ } ++ public void bind(java.net.SocketAddress address) throws IOException { ++ // Paper end + List list = this.channels; + + synchronized (this.channels) { +@@ -78,7 +83,11 @@ public class ServerConnectionListener { + LazyLoadedValue lazyinitvar; + + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + oclass = EpollServerSocketChannel.class; ++ } + lazyinitvar = ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP; + ServerConnectionListener.LOGGER.info("Using epoll channel type"); + } else { +@@ -106,7 +115,7 @@ public class ServerConnectionListener { + ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper + } +- }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit ++ }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 4e055a41de3ee410682cc05a3b883ac8babeb290..e1fbb207d6921516c7423e9a3cded8efb5676d49 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -44,6 +44,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + this.connection.setProtocol(ConnectionProtocol.LOGIN); + // CraftBukkit start - Connection throttle + try { ++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - the connection throttle is useless when you have a Unix domain socket + long currentTime = System.currentTimeMillis(); + long connectionThrottle = this.server.server.getConnectionThrottle(); + InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); +@@ -72,6 +73,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + } + } ++ } // Paper - add closing bracket for if check above + } catch (Throwable t) { + org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); + } +@@ -120,8 +122,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + String[] split = packet.hostName.split("\00"); + if ( ( split.length == 3 || split.length == 4 ) && ( BYPASS_HOSTCHECK || HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress socketAddress = connection.getRemoteAddress(); + packet.hostName = split[0]; +- connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort()); ++ connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ // Paper end + connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); + } else + { diff --git a/Remapped-Spigot-Server-Patches/0741-Add-EntityInsideBlockEvent.patch b/Remapped-Spigot-Server-Patches/0741-Add-EntityInsideBlockEvent.patch new file mode 100644 index 000000000..6380d78da --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0741-Add-EntityInsideBlockEvent.patch @@ -0,0 +1,222 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 18:02:36 -0700 +Subject: [PATCH] Add EntityInsideBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index ad37261e716b15d62fc2083d137cdac818308cdd..58519e224a5005bf6468b99196acb7209b2e1398 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -48,6 +48,7 @@ public abstract class BaseFireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!entity.fireImmune()) { + entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); + if (entity.getRemainingFireTicks() == 0) { +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index 6acb24f96591d555e550d399d2ed38036a0220fe..8d8fd646c9527852bfe9a2d3cd0a6bf1bfa9e4a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -65,6 +65,7 @@ public abstract class BasePressurePlateBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + int i = this.getSignalForState(state); + +diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +index f741980d0e1759e3fefe322b654760dab35200d6..faa1070aaf3fcc40bf7e36a0dda8ec7879c89dd6 100644 +--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -33,6 +33,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + BlockState iblockdata1 = world.getBlockState(pos.above()); + + if (iblockdata1.isAir()) { +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index 52ca5dad75674b81b997f8a1cf3f5d52bf4313c1..2c4c5fc6d45225cc9f7f1c2038bd4d0ae20c9daa 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -179,6 +179,7 @@ public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide && this.sensitive && !(Boolean) state.getValue(ButtonBlock.POWERED)) { + this.checkPressed(state, world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index de61393e3f702554817d81ff10693ec3fb63d492..28b083a8220856723b6169b5b13677b965a4dab6 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -115,6 +115,7 @@ public class CactusBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit + entity.hurt(DamageSource.CACTUS, 1.0F); + CraftEventFactory.blockDamage = null; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +index 87f106ad9139157af69a0ae1602c32ed372a04be..b0c6ac74daea347f4acb5da95e6b0cb013827509 100644 +--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +@@ -88,6 +88,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!entity.fireImmune() && (Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { + entity.hurt(DamageSource.IN_FIRE, (float) this.fireDamage); + } +diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +index 941061b50f7909278e962fb2aa080bb630862aa1..7d23adaa833c9c45708705294969fe268153b927 100644 +--- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +@@ -58,6 +58,7 @@ public class CauldronBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + int i = (Integer) state.getValue(CauldronBlock.LEVEL); + float f = (float) pos.getY() + (6.0F + (float) (3 * i)) / 16.0F; + +diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java +index 4cd6f18e0a2ee8d0495b3c822b227e212a13b11f..8368a5911690a9abb26c0f381bda7a0633197548 100644 +--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java +@@ -159,6 +159,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (entity instanceof Ravager && !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // CraftBukkit + world.destroyBlock(pos, true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 1854809e045300e84a713dc7c3a8264f53ec6c0f..d970fe7137f7dd37221b54e4025b98d15b950489 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -44,6 +44,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { + this.checkPressed(world, pos, state); +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index a6d793478be8e2aab1f72b68a6a96c86642ad1fc..2231680140d0e3c4c10b6722fd13fe0ee55ce294 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -41,6 +41,7 @@ public class EndPortalBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (world instanceof ServerLevel && !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { + ResourceKey resourcekey = world.getTypeKey() == DimensionType.END_LOCATION ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends + ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); +diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +index 7d0206dc5ac46220970adad51863028840b4a9ad..ef74852a1677a4ec80149aa7eafca910fac5ee91 100644 +--- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +@@ -49,6 +49,7 @@ public class HoneyBlock extends HalfTransparentBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (this.isSlidingDown(pos, entity)) { + this.maybeDoSlideAchievement(entity, pos); + this.doSlideMovement(entity); +diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +index 1f50c2b4bebeb6a224eb0ac552d6ea693f7831a6..40b1bba69ae96e4cd652261e3f97850e2e7c51a3 100644 +--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +@@ -197,6 +197,7 @@ public class HopperBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + BlockEntity tileentity = world.getBlockEntity(pos); + + if (tileentity instanceof HopperBlockEntity) { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index ae58929886921d0714bf811de92f99dc0dc120dc..53e7570cc2538e73f1cfe3d28ffc491f61cac372 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -81,6 +81,7 @@ public class NetherPortalBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions()) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +index 09122e2031e9ddfae4544911f8c25b937b03933f..6b5e8654ddd2c268627d0244b01dadeaeb4a7de4 100644 +--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -66,6 +66,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { + entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); + if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index eaee409bc8fbf8c5541afd10d24975535556a7f6..a488a2a589126df617564d9278eb0496915b6f88 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -118,6 +118,7 @@ public class TripWireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos); +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index 2b2a28d0383ccc8c0e7debd90331570b02b5e65f..bd4295f8d24ca9fd8c3af31abcd13da24db1c5d5 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -25,6 +25,7 @@ public class WaterlilyBlock extends BushBlock { + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { + super.entityInside(state, world, pos, entity); ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (world instanceof ServerLevel && entity instanceof Boat && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit + world.destroyBlock(new BlockPos(pos), true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java +index c368531420d464414de3661e1624e2a284976ab6..fc1c2e057e8b63048c919b5cbcc0a0e897f1dd01 100644 +--- a/src/main/java/net/minecraft/world/level/block/WebBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java +@@ -15,6 +15,7 @@ public class WebBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + entity.makeStuckInBlock(state, new Vec3(0.25D, 0.05000000074505806D, 0.25D)); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +index c1648c28c761cdf31089d434f9cb896ddef41521..5cb4e1ec7ce3087163d2ecf26d043894310768d1 100644 +--- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +@@ -26,6 +26,7 @@ public class WitherRoseBlock extends FlowerBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { + if (entity instanceof LivingEntity) { + LivingEntity entityliving = (LivingEntity) entity; diff --git a/Remapped-Spigot-Server-Patches/0742-Attributes-API-for-item-defaults.patch b/Remapped-Spigot-Server-Patches/0742-Attributes-API-for-item-defaults.patch new file mode 100644 index 000000000..ce7b672b1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0742-Attributes-API-for-item-defaults.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 15:01:54 -0700 +Subject: [PATCH] Attributes API for item defaults + + +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index 42085d3f4ae5c6ceecaffde79fb3187712a2af00..3043378c3addf959e0ccf323c1cde8b300b5f237 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -274,6 +274,7 @@ public class Item implements ItemLike { + return false; + } + ++ public Multimap getAttributesForSlot(EquipmentSlot enumItemSlot) { return getDefaultAttributeModifiers(enumItemSlot); } // Paper - OBFHELPER + public Multimap getDefaultAttributeModifiers(EquipmentSlot slot) { + return ImmutableMultimap.of(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 34eed57c7ed884e0d634ca403e38d25c95b6a038..824111fc74bade75ce06b1f0b57498006d0a66cd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -493,6 +493,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + return this.getItem(itemToBeRepaired.getType()).canRepair(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); + } + ++ @Override ++ public com.google.common.collect.Multimap getItemAttributes(org.bukkit.Material material, org.bukkit.inventory.EquipmentSlot equipmentSlot) { ++ Item item = this.getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item and therefore does not have attributes"); ++ } ++ com.google.common.collect.ImmutableMultimap.Builder attributeMapBuilder = com.google.common.collect.ImmutableMultimap.builder(); ++ item.getAttributesForSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(equipmentSlot)).forEach((attributeBase, attributeModifier) -> { ++ attributeMapBuilder.put(org.bukkit.Registry.ATTRIBUTE.get(CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.ATTRIBUTE.getKey(attributeBase))), org.bukkit.craftbukkit.attribute.CraftAttributeInstance.convert(attributeModifier)); ++ }); ++ return attributeMapBuilder.build(); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); diff --git a/Remapped-Spigot-Server-Patches/0743-Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch b/Remapped-Spigot-Server-Patches/0743-Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch new file mode 100644 index 000000000..339837dc3 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0743-Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander +Date: Thu, 6 May 2021 13:01:25 +0100 +Subject: [PATCH] Have CraftMerchantCustom emit PlayerPurchaseEvent + + +diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java +index 8298e667aa3a17d987bbc4cc2b63600af48beabc..334df355470bff98db63396b33c8db6a0abcc61a 100644 +--- a/src/main/java/net/minecraft/world/item/trading/Merchant.java ++++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java +@@ -19,7 +19,7 @@ public interface Merchant { + + MerchantOffers getOffers(); + +- void notifyTrade(MerchantOffer offer); ++ void notifyTrade(MerchantOffer offer); default void handlePurchase(MerchantOffer merchantRecipe) { notifyTrade(merchantRecipe); } // Paper - OBFHELPER + + void notifyTradeUpdated(ItemStack stack); + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index 306c6483708ae1b41bd16f122d36beec1916a776..d52192545c39734be3c97c7978652a54d7b9f029 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -81,6 +81,35 @@ public class CraftMerchantCustom extends CraftMerchant { + + @Override + public void notifyTrade(MerchantOffer offer) { ++ // Paper start ++ /** Based on {@link net.minecraft.world.entity.npc.EntityVillagerAbstract#b(MerchantRecipe)} */ ++ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { ++ final net.minecraft.server.level.ServerPlayer trader = (net.minecraft.server.level.ServerPlayer) getTradingPlayer(); ++ final io.papermc.paper.event.player.PlayerPurchaseEvent event = new io.papermc.paper.event.player.PlayerPurchaseEvent( ++ trader.getBukkitEntity(), ++ offer.asBukkit(), ++ false, // reward xp? ++ true); // should increase uses? ++ event.callEvent(); ++ if (event.isCancelled()) { ++ return; ++ } ++ final org.bukkit.inventory.MerchantRecipe eventTrade = event.getTrade(); ++ if (event.willIncreaseTradeUses()) { ++ eventTrade.setUses(eventTrade.getUses() + 1); ++ } ++ if (event.isRewardingExp() && eventTrade.hasExperienceReward()) { ++ /** Based on {@link net.minecraft.world.entity.npc.EntityVillagerTrader#b(MerchantRecipe)} */ ++ final int xp = 3 + net.minecraft.world.entity.Entity.SHARED_RANDOM.nextInt(4); ++ final Level world = trader.getCommandSenderWorld(); ++ world.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb( ++ world, trader.getX(), trader.getY() + 0.5d, trader.getZ(), xp, ++ org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, trader, null)); ++ } ++ return; ++ } ++ // Paper end ++ + // increase recipe's uses + offer.increaseUses(); + } diff --git a/Remapped-Spigot-Server-Patches/0744-Add-cause-to-Weather-ThunderChangeEvents.patch b/Remapped-Spigot-Server-Patches/0744-Add-cause-to-Weather-ThunderChangeEvents.patch new file mode 100644 index 000000000..fb889a469 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0744-Add-cause-to-Weather-ThunderChangeEvents.patch @@ -0,0 +1,136 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 18:23:26 -0800 +Subject: [PATCH] Add cause to Weather/ThunderChangeEvents + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 511e6a941d441c55a4b38660f0f7f8c47fa689dd..85e62e3c52950a517c4dbae739d21d879cb467a4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -413,8 +413,8 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.worldDataServer.setClearWeatherTime(clearDuration); + this.worldDataServer.setRainTime(rainDuration); + this.worldDataServer.setThunderTime(rainDuration); +- this.worldDataServer.setRaining(raining); +- this.worldDataServer.setThundering(thundering); ++ this.worldDataServer.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper ++ this.worldDataServer.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper + } + + public Biome getBiomeBySeed(int i, int j, int k) { return getUncachedNoiseBiome(i, j, k); } // Paper - OBFHELPER +@@ -476,8 +476,8 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + this.worldDataServer.setThunderTime(j); + this.worldDataServer.setRainTime(k); + this.worldDataServer.setClearWeatherTime(i); +- this.worldDataServer.setThundering(flag1); +- this.worldDataServer.setRaining(flag2); ++ this.worldDataServer.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper ++ this.worldDataServer.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper + } + + this.oThunderLevel = this.thunderLevel; +@@ -879,14 +879,14 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + private void stopWeather() { + // CraftBukkit start +- this.worldDataServer.setRaining(false); ++ this.worldDataServer.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... + if (!this.worldDataServer.isRaining()) { + this.worldDataServer.setRainTime(0); + } + // CraftBukkit end +- this.worldDataServer.setThundering(false); ++ this.worldDataServer.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night + // CraftBukkit start + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... +diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +index 83f2fdfa1ac2435f5199b5c33bfc409d2e94f4ed..e902534fd64f72e46feefa04f526e0dacd612627 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java ++++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +@@ -325,21 +325,26 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setThundering(boolean thundering) { ++ // Paper start ++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); ++ } ++ public void setThundering(boolean flag, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { ++ // Paper end + // CraftBukkit start +- if (this.thundering == thundering) { ++ if (this.thundering == flag) { + return; + } + + org.bukkit.World world = Bukkit.getWorld(getLevelName()); + if (world != null) { +- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, flag, cause); // Paper + Bukkit.getServer().getPluginManager().callEvent(thunder); + if (thunder.isCancelled()) { + return; + } + } + // CraftBukkit end +- this.thundering = thundering; ++ this.thundering = flag; + } + + @Override +@@ -359,21 +364,27 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setRaining(boolean raining) { ++ // Paper start ++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); ++ } ++ ++ public void setStorm(boolean flag, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { ++ // Paper end + // CraftBukkit start +- if (this.raining == raining) { ++ if (this.raining == flag) { + return; + } + + org.bukkit.World world = Bukkit.getWorld(getLevelName()); + if (world != null) { +- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, flag, cause); // Paper + Bukkit.getServer().getPluginManager().callEvent(weather); + if (weather.isCancelled()) { + return; + } + } + // CraftBukkit end +- this.raining = raining; ++ this.raining = flag; + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a1fa2d5e00bd125abd38a00e0bc3936f2fb8186f..1c9321cef1a05c5e8a22dd52bc63a5103eaf7311 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1452,7 +1452,7 @@ public class CraftWorld implements World { + + @Override + public void setStorm(boolean hasStorm) { +- world.levelData.setRaining(hasStorm); ++ world.worldDataServer.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper + setWeatherDuration(0); // Reset weather duration (legacy behaviour) + setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } +@@ -1474,7 +1474,7 @@ public class CraftWorld implements World { + + @Override + public void setThundering(boolean thundering) { +- world.worldDataServer.setThundering(thundering); ++ world.worldDataServer.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper + setThunderDuration(0); // Reset weather duration (legacy behaviour) + setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } diff --git a/Remapped-Spigot-Server-Patches/0745-More-Lidded-Block-API.patch b/Remapped-Spigot-Server-Patches/0745-More-Lidded-Block-API.patch new file mode 100644 index 000000000..5a3dcda89 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0745-More-Lidded-Block-API.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Sun, 23 May 2021 17:49:51 +0900 +Subject: [PATCH] More Lidded Block API + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +index 8f0477d9620ef71e10855bbca07f9b6984d5d794..70ca456fad052ca6eeaf8c4242c78d15d81084a5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +@@ -10,8 +10,9 @@ public class EnderChestBlockEntity extends BlockEntity { // Paper - Remove ITick + + public float openness; + public float oOpenness; +- public int openCount; ++ public int openCount; public int getViewerCount() { return openCount; } // Paper - OBFHELPER + private int tickInterval; ++ public boolean opened; // Paper - More Lidded Block API + + public EnderChestBlockEntity() { + super(BlockEntityType.ENDER_CHEST); +@@ -106,12 +107,14 @@ public class EnderChestBlockEntity extends BlockEntity { // Paper - Remove ITick + + public void startOpen() { + ++this.openCount; ++ if (opened) return; // Paper - More Lidded Block API + this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount); + doOpenLogic(); // Paper + } + + public void stopOpen() { + --this.openCount; ++ if (opened) return; // Paper - More Lidded Block API + this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount); + doCloseLogic(); // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java +index a0eadcbcb2575eb18f7b4951ae9eadfbc2e8af6f..fc4397a48425a23d64e0a679ace9e58fbf9b770b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java +@@ -59,4 +59,11 @@ public class CraftBarrel extends CraftLootable implements Bar + } + getTileEntity().opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().opened; ++ } ++ // Paper end - More Lidded Block API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +index a821df3e13e2ddc479dc5f55540671f43563cdac..9d7af8717085ba5c170a998aa863686d72840a40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +@@ -78,4 +78,11 @@ public class CraftChest extends CraftLootable implements Chest + } + getTileEntity().opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().opened; ++ } ++ // Paper end - More Lidded Block API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +index 25add8bee6ea35beeb205dd828759304346e4f48..fabcb2b8dc950fd074d65fed95d6b371dcfbf842 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +@@ -14,4 +14,33 @@ public class CraftEnderChest extends CraftBlockEntityState implem + if (getTileEntity().opened) { + Level world = getTileEntity().getLevel(); + world.blockEvent(getPosition(), getTileEntity().getBlockState().getBlock(), 1, 0); +- world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); ++ world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Paper - More Lidded Block API (Wrong sound) + } + getTileEntity().opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().opened; ++ } ++ // Paper end - More Lidded Block API + } diff --git a/Remapped-Spigot-Server-Patches/0746-Add-PlayerKickEvent-causes.patch b/Remapped-Spigot-Server-Patches/0746-Add-PlayerKickEvent-causes.patch new file mode 100644 index 000000000..82c7b23bb --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0746-Add-PlayerKickEvent-causes.patch @@ -0,0 +1,393 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 20:30:45 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9d7cebd703bd0171ca3e95d2985c1a52fdb59712..9e3b8c7478d97bf65a875807a268d1c98389c1f8 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2041,7 +2041,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage); // Paper - use configurable kick message ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -346,7 +346,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage); // Paper - use configurable kick message ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -368,7 +368,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + if (this.isPendingPing()) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info +- this.disconnect(new TranslatableComponent("disconnect.timeout", new Object[0])); ++ this.disconnect(new TranslatableComponent("disconnect.timeout", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + } else { + if (elapsedTime >= 15000L) { // 15 seconds +@@ -398,7 +398,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.idling")); ++ this.disconnect(new TranslatableComponent("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + + } +@@ -423,14 +423,22 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + public void disconnect(String s) { + // Paper start +- this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s)); ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(String s, PlayerKickEvent.Cause cause) { ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s), cause); + } + + public void disconnect(final Component reason) { +- this.disconnect(PaperAdventure.asAdventure(reason)); ++ this.disconnect(PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(PaperAdventure.asAdventure(reason), cause); + } + +- public void disconnect(net.kyori.adventure.text.Component reason) { ++ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { +@@ -438,7 +446,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, this.player.getBukkitEntity().displayName()); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.craftServer.getPlayer(this.player), reason, leaveMessage); // Paper - Adventure ++ PlayerKickEvent event = new PlayerKickEvent(this.craftServer.getPlayer(this.player), reason, leaveMessage, cause); // Paper - Adventure & kick event reason + + if (this.craftServer.getServer().isRunning()) { + this.craftServer.getPluginManager().callEvent(event); +@@ -516,7 +524,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (containsInvalidValues(packet)) { +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else { + Entity entity = this.player.getRootVehicle(); + +@@ -750,13 +758,13 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start + if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper start + String str = packet.getCommand(); int index = -1; + if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper end +@@ -904,7 +912,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // Paper start - validate pick item position + if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.inventory.items.size())) { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.player.inventory.pickSlot(packet.getSlot()); // Paper - Diff above if changed +@@ -1058,7 +1066,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + ListTag pageList = testStack.getTag().getList("pages", 8); + if (pageList.size() > 100) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with too many pages"); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + long byteTotal = 0; +@@ -1070,7 +1078,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (byteLength > 256 * 4) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + byteTotal += byteLength; +@@ -1093,14 +1101,14 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + + if (byteTotal > byteAllowed) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + } + // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!"); ++ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.lastBookTick = MinecraftServer.currentTick; +@@ -1212,7 +1220,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (containsInvalidValues(packet)) { +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + ServerLevel worldserver = this.player.getLevel(); + +@@ -1611,7 +1619,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.dropCount++; + if (this.dropCount >= 20) { + LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); +- this.disconnect("You dropped your items too quickly (Hacking?)"); ++ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } +@@ -1924,7 +1932,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + +@@ -1961,7 +1969,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- ServerGamePacketListenerImpl.this.disconnect(new TranslatableComponent("multiplayer.disconnect.illegal_characters")); ++ ServerGamePacketListenerImpl.this.disconnect(new TranslatableComponent("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event cause + return null; + } + }; +@@ -1976,7 +1984,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + throw new RuntimeException(e); + } + } else { +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(new TranslatableComponent("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event cause + } + // CraftBukkit end + return; +@@ -2030,7 +2038,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- ServerGamePacketListenerImpl.this.disconnect(new TranslatableComponent("disconnect.spam")); ++ ServerGamePacketListenerImpl.this.disconnect(new TranslatableComponent("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + return null; + } + }; +@@ -2045,7 +2053,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + throw new RuntimeException(e); + } + } else { +- this.disconnect(new TranslatableComponent("disconnect.spam")); ++ this.disconnect(new TranslatableComponent("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } + // CraftBukkit end + } +@@ -2318,7 +2326,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // Spigot Start + if ( entity == player && !player.isSpectator() ) + { +- disconnect( "Cannot interact with self!" ); ++ disconnect( "Cannot interact with self!", org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause + return; + } + // Spigot End +@@ -2395,7 +2403,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // CraftBukkit end + } else if (packet.getAction() == ServerboundInteractPacket.Action.ATTACK) { + if (entity instanceof ItemEntity || entity instanceof ExperienceOrb || entity instanceof AbstractArrow || (entity == this.player && !player.isSpectator())) { // CraftBukkit +- this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_entity_attacked")); ++ this.disconnect(new TranslatableComponent("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - kick event cause + ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", this.player.getName().getString()); + return; + } +@@ -2800,7 +2808,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + // Paper start + if (!Bukkit.isPrimaryThread()) { + if (recipeSpamPackets.addAndGet(PaperConfig.autoRecipeIncrement) > PaperConfig.autoRecipeLimit) { +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + } +@@ -2999,7 +3007,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } else if (!this.isSingleplayerOwner()) { + // Paper start - This needs to be handled on the main thread for plugins + server.scheduleOnMain(() -> { +- this.disconnect(new TranslatableComponent("disconnect.timeout")); ++ this.disconnect(new TranslatableComponent("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + }); + // Paper end + } +@@ -3045,7 +3053,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); +- this.disconnect("Invalid payload REGISTER!"); ++ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (packet.identifier.equals(CUSTOM_UNREGISTER)) { + try { +@@ -3055,7 +3063,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); +- this.disconnect("Invalid payload UNREGISTER!"); ++ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { + try { +@@ -3074,7 +3082,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { + craftServer.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packet.identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +- this.disconnect("Invalid custom payload!"); ++ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 6011b43ae8a858f88b8fcf6dc0bf147024a4742c..9e138bf9f9ee4efee462271d1a69a2126aa94946 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -716,7 +716,7 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (ServerPlayer) iterator.next(); + save(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.connection.disconnect(new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0])); ++ entityplayer.connection.disconnect(new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause + } + + // Instead of kicking then returning, we need to store the kick reason +@@ -1385,8 +1385,8 @@ public abstract class PlayerList { + public void shutdown(boolean isRestarting) { + // CraftBukkit start - disconnect safely + for (ServerPlayer player : this.players) { +- if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper +- player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure ++ if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) ++ player.connection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3dbe94d9b9647f5cc1e27335b36042e50c652cea..97aec6370a8a24a13ae04443d03f250f8938b2e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -495,16 +495,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot + if (getHandle().connection == null) return; + +- getHandle().connection.disconnect(message == null ? "" : message); ++ getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + + // Paper start + @Override + public void kick(final net.kyori.adventure.text.Component message) { ++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); + final ServerGamePacketListenerImpl connection = this.getHandle().connection; + if (connection != null) { +- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); + } + } + // Paper end +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 6498dc4c6630bfef1a52edf74d8574e5e4876720..ea1f088ac94616978af5e01a59c558cd2db4b619 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -74,7 +74,7 @@ public class RestartCommand extends Command + // Kick all players + for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) + { +- p.connection.disconnect(SpigotConfig.restartMessage); ++ p.connection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) + } + // Give the socket a chance to send the packets + try diff --git a/Remapped-Spigot-Server-Patches/0747-Limit-item-frame-cursors-on-maps.patch b/Remapped-Spigot-Server-Patches/0747-Limit-item-frame-cursors-on-maps.patch new file mode 100644 index 000000000..276c9629f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0747-Limit-item-frame-cursors-on-maps.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Wed, 26 May 2021 15:09:33 -0700 +Subject: [PATCH] Limit item frame cursors on maps + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 108a005c296c4ed370de4af636163088971bed13..9287dfcf29ce6f89a937f4e10b70be8faab9ab9e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -816,4 +816,9 @@ public class PaperWorldConfig { + private void allowUsingSignsInsideSpawnProtection() { + allowUsingSignsInsideSpawnProtection = getBoolean("allow-using-signs-inside-spawn-protection", allowUsingSignsInsideSpawnProtection); + } ++ ++ public int mapItemFrameCursorLimit = 128; ++ private void mapItemFrameCursorLimit() { ++ mapItemFrameCursorLimit = getInt("map-item-frame-cursor-limit", mapItemFrameCursorLimit); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index e7b178127228dea5a17ba0fbd6bae148d70e8eb5..e2f550c833030d8ad12b80d3d379f4731ddeb2ec 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -279,8 +279,12 @@ public class MapItemSavedData extends SavedData { + + MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); + ++ // Paper start ++ if (this.decorations.size() < player.level.paperConfig.mapItemFrameCursorLimit) { + this.addDecoration(MapDecoration.Type.FRAME, player.level, "frame-" + entityitemframe.getId(), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); + this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); ++ } ++ // Paper end + } + + CompoundTag nbttagcompound = stack.getTag(); diff --git a/Remapped-Spigot-Server-Patches/0748-Add-PufferFishStateChangeEvent.patch b/Remapped-Spigot-Server-Patches/0748-Add-PufferFishStateChangeEvent.patch new file mode 100644 index 000000000..58dca35d6 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0748-Add-PufferFishStateChangeEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Mon, 10 May 2021 16:59:05 +0100 +Subject: [PATCH] Add PufferFishStateChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +index eb80bde66fbb03dad845e36f1bd1a8eaf8592fbf..44b7b62622d65326aa3926764bb1bb1ed8694511 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +@@ -89,25 +89,39 @@ public class Pufferfish extends AbstractFish { + public void tick() { + if (!this.level.isClientSide && this.isAlive() && this.isEffectiveAi()) { + if (this.inflateCounter > 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.getPuffState() == 0) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(2); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.inflateCounter; ++ } // Paper - Add PufferFishStateChangeEvent + } else if (this.getPuffState() != 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.deflateTimer > 60 && this.getPuffState() == 2) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(0); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.deflateTimer; ++ } // Paper - Add PufferFishStateChangeEvent + } + } + diff --git a/Remapped-Spigot-Server-Patches/0749-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/Remapped-Spigot-Server-Patches/0749-Fix-PlayerBucketEmptyEvent-result-itemstack.patch new file mode 100644 index 000000000..85fe9b3b1 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0749-Fix-PlayerBucketEmptyEvent-result-itemstack.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 22:16:37 -0700 +Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack + +Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560 + +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index d0e847e58483695d2af1c1410826bb25231cd6f6..08c00a084b2972420eae020e13480489b29aec64 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -116,6 +116,13 @@ public class BucketItem extends Item { + } + + protected ItemStack getEmptySuccessItem(ItemStack stack, Player player) { ++ // Paper ++ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { ++ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; ++ itemLeftInHandAfterPlayerBucketEmptyEvent = null; ++ return itemInHand; ++ } ++ // Paper + return !player.abilities.instabuild ? new ItemStack(Items.BUCKET) : stack; + } + +@@ -126,6 +133,7 @@ public class BucketItem extends Item { + return a(player, world, pos, movingobjectpositionblock, null, null, null, null); + } + ++ private ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper + public boolean a(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack, InteractionHand enumhand) { + // Paper end + // CraftBukkit end +@@ -146,6 +154,9 @@ public class BucketItem extends Item { + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return false; + } ++ // Paper start ++ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()); ++ // Paper end + } + // CraftBukkit end + if (!flag1) { diff --git a/Remapped-Spigot-Server-Patches/0750-Add-option-to-fix-items-merging-through-walls.patch b/Remapped-Spigot-Server-Patches/0750-Add-option-to-fix-items-merging-through-walls.patch new file mode 100644 index 000000000..3cd329efc --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0750-Add-option-to-fix-items-merging-through-walls.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GioSDA +Date: Wed, 10 Mar 2021 10:06:45 -0800 +Subject: [PATCH] Add option to fix items merging through walls + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9287dfcf29ce6f89a937f4e10b70be8faab9ab9e..74f2413773fbe30597314e02a5284172e0fc40b2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -821,4 +821,9 @@ public class PaperWorldConfig { + private void mapItemFrameCursorLimit() { + mapItemFrameCursorLimit = getInt("map-item-frame-cursor-limit", mapItemFrameCursorLimit); + } ++ ++ public boolean fixItemsMergingThroughWalls; ++ private void fixItemsMergingThroughWalls() { ++ fixItemsMergingThroughWalls = getBoolean("fix-items-merging-through-walls", fixItemsMergingThroughWalls); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 7476ae301fb4ee503944d39022cb25ccb19f1232..d937a74d2e822c8542286fb5bcdfcec7895d845c 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -227,6 +227,14 @@ public class ItemEntity extends Entity { + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (entityitem.isMergable()) { ++ // Paper Start - Fix items merging through walls ++ if (this.level.paperConfig.fixItemsMergingThroughWalls) { ++ net.minecraft.world.level.ClipContext rayTrace = new net.minecraft.world.level.ClipContext(this.position(), entityitem.position(), ++ net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, this); ++ net.minecraft.world.phys.BlockHitResult rayTraceResult = level.clip(rayTrace); ++ if (rayTraceResult.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) continue; ++ } ++ // Paper End + this.tryToMerge(entityitem); + if (this.removed) { + break; diff --git a/Remapped-Spigot-Server-Patches/0751-Add-BellRevealRaiderEvent.patch b/Remapped-Spigot-Server-Patches/0751-Add-BellRevealRaiderEvent.patch new file mode 100644 index 000000000..78020871e --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0751-Add-BellRevealRaiderEvent.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 26 May 2021 17:09:07 -0400 +Subject: [PATCH] Add BellRevealRaiderEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +index 3f9179a7678091875161a34d13b6ec0e78025c4c..03c2831a7c4f310936dad1ee72f402ed38f3c9e7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +@@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Position; + import net.minecraft.core.particles.ParticleTypes; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.tags.EntityTypeTags; +@@ -181,6 +182,7 @@ public class BellBlockEntity extends BlockEntity implements TickableBlockEntity + } + + private void glow(LivingEntity entity) { ++ if (!new io.papermc.paper.event.block.BellRevealRaiderEvent(level.getWorld().getBlockAt(MCUtil.toLocation(level, worldPosition)), entity.getBukkitEntity()).callEvent()) return; // Paper - BellRevealRaiderEvent + entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60)); + } + } diff --git a/Remapped-Spigot-Server-Patches/0752-Fix-invulnerable-end-crystals.patch b/Remapped-Spigot-Server-Patches/0752-Fix-invulnerable-end-crystals.patch new file mode 100644 index 000000000..d830a00fa --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0752-Fix-invulnerable-end-crystals.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Max Lee +Date: Thu, 27 May 2021 14:52:30 -0700 +Subject: [PATCH] Fix invulnerable end crystals + +MC-108513 + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 74f2413773fbe30597314e02a5284172e0fc40b2..dc5ebbb44238cb5928f385d2962c9057388575f8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -826,4 +826,9 @@ public class PaperWorldConfig { + private void fixItemsMergingThroughWalls() { + fixItemsMergingThroughWalls = getBoolean("fix-items-merging-through-walls", fixItemsMergingThroughWalls); + } ++ ++ public boolean fixInvulnerableEndCrystalExploit = true; ++ private void fixInvulnerableEndCrystalExploit() { ++ fixInvulnerableEndCrystalExploit = getBoolean("unsupported-settings.fix-invulnerable-end-crystal-exploit", fixInvulnerableEndCrystalExploit); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +index 6292d04464950ac52fcd6d69345db5125d3127eb..8583209750e4bb70e86d7243a47c525f1726ee42 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -29,6 +29,7 @@ public class EndCrystal extends Entity { + private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); + private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); + public int time; ++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + + public EndCrystal(EntityType type, Level world) { + super(type, world); +@@ -65,6 +66,17 @@ public class EndCrystal extends Entity { + } + // CraftBukkit end + } ++ // Paper start - Fix invulnerable end crystals ++ if (this.level.paperConfig.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { ++ if ((this.origin.getWorld() != null && !((ServerLevel) this.level).uuid.equals(this.origin.getWorld().getUID())) ++ || ((ServerLevel) this.level).dragonFight() == null ++ || ((ServerLevel) this.level).dragonFight().respawnStage == null ++ || ((ServerLevel) this.level).dragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { ++ this.setInvulnerable(false); ++ this.setBeamTarget(null); ++ } ++ } ++ // Paper end + } + + } +@@ -76,6 +88,7 @@ public class EndCrystal extends Entity { + } + + tag.putBoolean("ShowBottom", this.showsBottom()); ++ if (this.generatedByDragonFight) tag.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -87,6 +100,7 @@ public class EndCrystal extends Entity { + if (tag.contains("ShowBottom", 1)) { + this.setShowBottom(tag.getBoolean("ShowBottom")); + } ++ if (tag.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = tag.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals + + } + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +index b99576b524a65cc1a0de88c49324d929503d655f..d51d2fb6d24bfee63b04f32f2cb157fec9d8ee6c 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +@@ -42,11 +42,11 @@ public class SpikeFeature extends Feature { + return (List) SpikeFeature.SPIKE_CACHE.getUnchecked(i); + } + +- public boolean place(WorldGenLevel world, ChunkGenerator chunkGenerator, Random random, BlockPos pos, SpikeConfiguration config) { +- List list = config.getSpikes(); ++ public boolean generate(WorldGenLevel generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPos blockposition, SpikeConfiguration worldgenfeatureendspikeconfiguration) { // Paper - decompile fix ++ List list = worldgenfeatureendspikeconfiguration.getSpikes(); + + if (list.isEmpty()) { +- list = getSpikesForLevel(world); ++ list = getSpikesForLevel(generatoraccessseed); + } + + Iterator iterator = list.iterator(); +@@ -54,8 +54,8 @@ public class SpikeFeature extends Feature { + while (iterator.hasNext()) { + SpikeFeature.EndSpike worldgenender_spike = (SpikeFeature.EndSpike) iterator.next(); + +- if (worldgenender_spike.isCenterWithinChunk(pos)) { +- this.placeSpike(world, random, config, worldgenender_spike); ++ if (worldgenender_spike.isCenterWithinChunk(blockposition)) { ++ this.placeSpike(generatoraccessseed, random, worldgenfeatureendspikeconfiguration, worldgenender_spike); + } + } + +@@ -106,6 +106,7 @@ public class SpikeFeature extends Feature { + entityendercrystal.setBeamTarget(config.getCrystalBeamTarget()); + entityendercrystal.setInvulnerable(config.isCrystalInvulnerable()); + entityendercrystal.moveTo((double) spike.getCenterX() + 0.5D, (double) (spike.getHeight() + 1), (double) spike.getCenterZ() + 0.5D, random.nextFloat() * 360.0F, 0.0F); ++ entityendercrystal.generatedByDragonFight = true; + world.addFreshEntity(entityendercrystal); + this.setBlock(world, new BlockPos(spike.getCenterX(), spike.getHeight(), spike.getCenterZ()), Blocks.BEDROCK.defaultBlockState()); + } diff --git a/Remapped-Spigot-Server-Patches/0753-Add-ElderGuardianAppearanceEvent.patch b/Remapped-Spigot-Server-Patches/0753-Add-ElderGuardianAppearanceEvent.patch new file mode 100644 index 000000000..2e0af7d21 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0753-Add-ElderGuardianAppearanceEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 19 Mar 2021 23:39:09 -0400 +Subject: [PATCH] Add ElderGuardianAppearanceEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +index b6a842f9a24634ff84f2f4f94c0f2838f10cddb0..0cf6a06f8bddac0a2a1a00f3a4b266116f7ae594 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +@@ -77,10 +77,12 @@ public class ElderGuardian extends Guardian { + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + ++ if (new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent(getBukkitEntity(), entityplayer.getBukkitEntity()).callEvent()) { // Paper - Add Guardian Appearance Event + if (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < 2 || entityplayer.getEffect(mobeffectlist).getDuration() < 1200) { + entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); + entityplayer.addEffect(new MobEffectInstance(mobeffectlist, 6000, 2), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } ++ } // Paper - Add Guardian Appearance Event + } + } + diff --git a/Remapped-Spigot-Server-Patches/0754-Reset-villager-inventory-on-cancelled-pickup-event.patch b/Remapped-Spigot-Server-Patches/0754-Reset-villager-inventory-on-cancelled-pickup-event.patch new file mode 100644 index 000000000..a6ef1d69f --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0754-Reset-villager-inventory-on-cancelled-pickup-event.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 1 Jun 2021 22:05:08 -0500 +Subject: [PATCH] Reset villager inventory on cancelled pickup event + + +diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java +index d6bb1d540e6dcbbad5e5bdf54803c495a4f3e771..3e639f5af83bea6760669696425dffd2d741cc16 100644 +--- a/src/main/java/net/minecraft/world/SimpleContainer.java ++++ b/src/main/java/net/minecraft/world/SimpleContainer.java +@@ -34,6 +34,16 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + return this.items; + } + ++ // Paper start ++ public void setContents(List items) { ++ this.items.clear(); ++ for(int i = 0; i < items.size(); i++) { ++ this.items.set(i, items.get(i)); ++ } ++ this.setChanged(); ++ } ++ // Paper end ++ + public void onOpen(CraftHumanEntity who) { + transaction.add(who); + } +@@ -148,6 +158,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + return itemstack; + } + ++ public ItemStack addItem(ItemStack itemstack) { return addItem(itemstack); } // Paper - OBFHELPER + public ItemStack addItem(ItemStack stack) { + ItemStack itemstack1 = stack.copy(); + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 7817071b1964b962c8f4017d5bb39d74ca0ca3e4..1fbb7a2db5c362a5fc9e3f81382f729c962e377b 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -844,15 +844,19 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + + // CraftBukkit start +- ItemStack remaining = new SimpleContainer(inventorysubcontainer).addItem(itemstack); +- if (CraftEventFactory.callEntityPickupItemEvent(this, item, remaining.getCount(), false).isCancelled()) { ++ // Paper start ++ List contentsSnapshot = new java.util.ArrayList<>(inventorysubcontainer.getContents()); ++ ItemStack itemstack1 = inventorysubcontainer.addItem(itemstack); ++ if (CraftEventFactory.callEntityPickupItemEvent(this, item, itemstack1.getCount(), false).isCancelled()) { ++ inventorysubcontainer.setContents(contentsSnapshot); ++ // Paper end + return; + } + // CraftBukkit end + + this.onItemPickup(item); + this.take(item, itemstack.getCount()); +- ItemStack itemstack1 = inventorysubcontainer.addItem(itemstack); ++ // ItemStack itemstack1 = inventorysubcontainer.a(itemstack); // Paper - moved up + + if (itemstack1.isEmpty()) { + item.remove(); diff --git a/Remapped-Spigot-Server-Patches/0755-Fix-dangerous-end-portal-logic.patch b/Remapped-Spigot-Server-Patches/0755-Fix-dangerous-end-portal-logic.patch new file mode 100644 index 000000000..a1fbfe040 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0755-Fix-dangerous-end-portal-logic.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 4 Jun 2021 17:06:52 -0400 +Subject: [PATCH] Fix dangerous end portal logic + +End portals could teleport entities during move calls. Stupid +logic given the caller will never expect that kind of thing, +and will result in all kinds of dupes. + +Move the tick logic into the post tick, where portaling was +designed to happen in the first place. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 779b926921fd435620cbbc69ed6f9931a422b652..25711ab723386db0f448c54e18ef069bfcd0dd99 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1020,6 +1020,7 @@ public class ServerPlayer extends Player implements ContainerListener { + return b(destination, TeleportCause.UNKNOWN); + } + ++ @Nullable public final Entity changeDimension(ServerLevel worldserver, PlayerTeleportEvent.TeleportCause cause) { return this.b(worldserver, cause); } // Paper - OBFHELPER + @Nullable + public Entity b(ServerLevel worldserver, PlayerTeleportEvent.TeleportCause cause) { + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d055b362459e5b4658aa220e16118ee6174c0de4..2462a78d976937cf4737f1ce0bfde2b2b7d5b1f7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -312,6 +312,37 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + // Paper end - optimise entity tracking + ++ // Paper start - make end portalling safe ++ public BlockPos portalBlock; ++ public ServerLevel portalWorld; ++ public void tickEndPortal() { ++ BlockPos pos = this.portalBlock; ++ ServerLevel world = this.portalWorld; ++ this.portalBlock = null; ++ this.portalWorld = null; ++ ++ if (pos == null || world == null || world != this.level) { ++ return; ++ } ++ ++ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.removed || !this.valid || !this.isAlive()) { ++ return; ++ } ++ ++ ResourceKey resourcekey = world.getTypeKey() == DimensionType.END_LOCATION ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends ++ ServerLevel worldserver = world.getServer().getLevel(resourcekey); ++ ++ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ event.callEvent(); ++ ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer)this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); ++ return; ++ } ++ this.teleportTo(worldserver, null); ++ } ++ // Paper end - make end portalling safe ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = Lists.newArrayList(); +@@ -2297,6 +2328,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s + } + + this.processPortalCooldown(); ++ this.tickEndPortal(); // Paper - make end portalling safe + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 2231680140d0e3c4c10b6722fd13fe0ee55ce294..52884b87b25bef8abfa824f40d02efe3f947f330 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -3,7 +3,6 @@ package net.minecraft.world.level.block; + import net.minecraft.core.BlockPos; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.Level; +@@ -50,16 +49,10 @@ public class EndPortalBlock extends BaseEntityBlock { + // return; // CraftBukkit - always fire event in case plugins wish to change it + } + +- // CraftBukkit start - Entity in portal +- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +- world.getCraftServer().getPluginManager().callEvent(event); +- +- if (entity instanceof ServerPlayer) { +- ((ServerPlayer) entity).b(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); +- return; +- } +- // CraftBukkit end +- entity.changeDimension(worldserver); ++ // Paper start - move all of this logic into portal tick ++ entity.portalWorld = ((ServerLevel)world); ++ entity.portalBlock = pos.immutable(); ++ // Paper end - move all of this logic into portal tick + } + + } diff --git a/Remapped-Spigot-Server-Patches/0756-Make-item-validations-configurable.patch b/Remapped-Spigot-Server-Patches/0756-Make-item-validations-configurable.patch new file mode 100644 index 000000000..964f15ad4 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0756-Make-item-validations-configurable.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Jun 2021 12:12:35 -0700 +Subject: [PATCH] Make item validations configurable + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index efc1e42d606e1c9feb1a4871c0714933ae92a1b2..7acf077bc131af718c7548cc29deef558c04e463 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -486,4 +486,19 @@ public class PaperConfig { + enableBrigadierConsoleHighlighting = getBoolean("settings.console.enable-brigadier-highlighting", enableBrigadierConsoleHighlighting); + enableBrigadierConsoleCompletions = getBoolean("settings.console.enable-brigadier-completions", enableBrigadierConsoleCompletions); + } ++ ++ public static int itemValidationDisplayNameLength = 8192; ++ public static int itemValidationLocNameLength = 8192; ++ public static int itemValidationLoreLineLength = 8192; ++ public static int itemValidationBookTitleLength = 8192; ++ public static int itemValidationBookAuthorLength = 8192; ++ public static int itemValidationBookPageLength = 16384; ++ private static void itemValidationSettings() { ++ itemValidationDisplayNameLength = getInt("settings.item-validation.display-name", itemValidationDisplayNameLength); ++ itemValidationLocNameLength = getInt("settings.item-validation.loc-name", itemValidationLocNameLength); ++ itemValidationLoreLineLength = getInt("settings.item-validation.lore-line", itemValidationLoreLineLength); ++ itemValidationBookTitleLength = getInt("settings.item-validation.book.title", itemValidationBookTitleLength); ++ itemValidationBookAuthorLength = getInt("settings.item-validation.book.author", itemValidationBookAuthorLength); ++ itemValidationBookPageLength = getInt("settings.item-validation.book.page", itemValidationBookPageLength); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index 80397e223990f11c9aa413f3f4ebd7c1b8ce1cff..2ff1619e6898add074481c7ca43bfbf9a8d163ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -94,11 +94,11 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + super(tag); + + if (tag.contains(BOOK_TITLE.NBT)) { +- this.title = limit( tag.getString(BOOK_TITLE.NBT), 8192 ); // Spigot ++ this.title = limit( tag.getString(BOOK_TITLE.NBT), com.destroystokyo.paper.PaperConfig.itemValidationBookTitleLength); // Spigot // Paper - make configurable + } + + if (tag.contains(BOOK_AUTHOR.NBT)) { +- this.author = limit( tag.getString(BOOK_AUTHOR.NBT), 8192 ); // Spigot ++ this.author = limit( tag.getString(BOOK_AUTHOR.NBT), com.destroystokyo.paper.PaperConfig.itemValidationBookAuthorLength ); // Spigot // Paper - make configurable + } + + if (tag.contains(RESOLVED.NBT)) { +@@ -126,7 +126,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + } else { + page = validatePage(page); + } +- this.pages.add( limit( page, 16384 ) ); // Spigot ++ this.pages.add( limit( page, com.destroystokyo.paper.PaperConfig.itemValidationBookPageLength ) ); // Spigot // Paper - make configurable + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 20e008277d1188fc7b31bfb2522ef9f6429cc3fb..99e18748b3cc4168b1d15c030f992a128b666d84 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -357,18 +357,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CompoundTag display = tag.getCompound(DISPLAY.NBT); + + if (display.contains(NAME.NBT)) { +- displayName = limit( display.getString(NAME.NBT), 8192 ); // Spigot ++ displayName = limit( display.getString(NAME.NBT), com.destroystokyo.paper.PaperConfig.itemValidationDisplayNameLength); // Spigot // Paper - make configurable + } + + if (display.contains(LOCNAME.NBT)) { +- locName = limit( display.getString(LOCNAME.NBT), 8192 ); // Spigot ++ locName = limit( display.getString(LOCNAME.NBT), com.destroystokyo.paper.PaperConfig.itemValidationLocNameLength ); // Spigot // Paper - make configurable + } + + if (display.contains(LORE.NBT)) { + ListTag list = display.getList(LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); + lore = new ArrayList(list.size()); + for (int index = 0; index < list.size(); index++) { +- String line = limit( list.getString(index), 8192 ); // Spigot ++ String line = limit( list.getString(index), com.destroystokyo.paper.PaperConfig.itemValidationLoreLineLength ); // Spigot // Paper - make configurable + lore.add(line); + } + } diff --git a/Remapped-Spigot-Server-Patches/0757-Add-more-line-of-sight-methods.patch b/Remapped-Spigot-Server-Patches/0757-Add-more-line-of-sight-methods.patch new file mode 100644 index 000000000..2961b16b0 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0757-Add-more-line-of-sight-methods.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> +Date: Sat, 29 May 2021 14:33:25 -0500 +Subject: [PATCH] Add more line of sight methods + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 37787a725725d22b0870dcab0f3bec8b94cfd130..79b43b1b0f8e223f256c2aaec1925426931a9a54 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3091,6 +3091,7 @@ public abstract class LivingEntity extends Entity { + Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); + Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); + ++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists + return this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 1c9321cef1a05c5e8a22dd52bc63a5103eaf7311..312ed9c693cc5108d51ad90e15d6be4bb21904e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -315,6 +315,17 @@ public class CraftWorld implements World { + public io.papermc.paper.world.MoonPhase getMoonPhase() { + return io.papermc.paper.world.MoonPhase.getPhase(getFullTime() / 24000L); + } ++ ++ @Override ++ public boolean lineOfSightExists(Location from, Location to) { ++ Validate.notNull(from, "from parameter in lineOfSightExists cannot be null"); ++ Validate.notNull(to, "to parameter in lineOfSightExists cannot be null"); ++ if (from.getWorld() != to.getWorld()) return false; ++ Vec3 vec3d = new Vec3(from.getX(), from.getY(), from.getZ()); ++ Vec3 vec3d1 = new Vec3(to.getX(), to.getY(), to.getZ()); ++ ++ return this.getHandle().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, null)).getType() == HitResult.Type.MISS; ++ } + // Paper end + + private static final Random rand = new Random(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 25ba7a26c951fc5e4638bdb0db36e94d3e08fb2e..cd42edd8f36cea3acad76974c39eb0cd1585f73d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -549,6 +549,17 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().canSee(((CraftEntity) other).getHandle()); + } + ++ // Paper start ++ @Override ++ public boolean hasLineOfSight(Location loc) { ++ if (this.getHandle().level != ((CraftWorld) loc.getWorld()).getHandle()) return false; ++ Vec3 vec3d = new Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); ++ Vec3 vec3d1 = new Vec3(loc.getX(), loc.getY(), loc.getZ()); ++ ++ return this.getHandle().level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.getHandle())).getType() == HitResult.Type.MISS; ++ } ++ // Paper end ++ + @Override + public boolean getRemoveWhenFarAway() { + return getHandle() instanceof Mob && !((Mob) getHandle()).persistenceRequired; diff --git a/Remapped-Spigot-Server-Patches/0758-add-per-world-spawn-limits.patch b/Remapped-Spigot-Server-Patches/0758-add-per-world-spawn-limits.patch new file mode 100644 index 000000000..b4f5cd533 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0758-add-per-world-spawn-limits.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chase +Date: Wed, 2 Dec 2020 22:43:39 -0800 +Subject: [PATCH] add per world spawn limits + +Taken from #2982. Credit to Chasewhip8 + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index dc5ebbb44238cb5928f385d2962c9057388575f8..f80186f663ff654ab6b69189941cd26815f65f09 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -652,6 +652,19 @@ public class PaperWorldConfig { + zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); + } + ++ public int spawnLimitMonsters = -1; ++ public int spawnLimitAnimals = -1; ++ public int spawnLimitWaterAnimals = -1; ++ public int spawnLimitWaterAmbient = -1; ++ public int spawnLimitAmbient = -1; ++ private void perWorldSpawnLimits() { ++ spawnLimitMonsters = getInt("spawn-limits.monsters", spawnLimitMonsters); ++ spawnLimitAnimals = getInt("spawn-limits.animals", spawnLimitAnimals); ++ spawnLimitWaterAnimals = getInt("spawn-limits.water-animals", spawnLimitWaterAnimals); ++ spawnLimitWaterAmbient = getInt("spawn-limits.water-ambient", spawnLimitWaterAmbient); ++ spawnLimitAmbient = getInt("spawn-limits.ambient", spawnLimitAmbient); ++ } ++ + public int lightQueueSize = 20; + private void lightQueueSize() { + lightQueueSize = getInt("light-queue-size", lightQueueSize); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 312ed9c693cc5108d51ad90e15d6be4bb21904e1..89aac4214084bd72a83eaaf043b7d47cc0b748f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -335,6 +335,13 @@ public class CraftWorld implements World { + this.generator = gen; + + environment = env; ++ // Paper start - per world spawn limits ++ this.monsterSpawn = this.world.paperConfig.spawnLimitMonsters; ++ this.animalSpawn = this.world.paperConfig.spawnLimitAnimals; ++ this.waterAnimalSpawn = this.world.paperConfig.spawnLimitWaterAnimals; ++ this.waterAmbientSpawn = this.world.paperConfig.spawnLimitWaterAmbient; ++ this.ambientSpawn = this.world.paperConfig.spawnLimitAmbient; ++ // Paper end + } + + @Override diff --git a/Remapped-Spigot-Server-Patches/0759-Fix-PotionSplashEvent-for-water-splash-potions.patch b/Remapped-Spigot-Server-Patches/0759-Fix-PotionSplashEvent-for-water-splash-potions.patch new file mode 100644 index 000000000..f351ce453 --- /dev/null +++ b/Remapped-Spigot-Server-Patches/0759-Fix-PotionSplashEvent-for-water-splash-potions.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 20:40:53 -0700 +Subject: [PATCH] Fix PotionSplashEvent for water splash potions + +Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index ed81d2a306d28561370745bb3389c49012f677a7..fbdfab50af195d219f9745324c8924fc777f76ec 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -99,6 +99,7 @@ public class DamageSource { + return (new IndirectEntityDamageSource("thrown", projectile, attacker)).setProjectile(); + } + ++ public static DamageSource indirectMagic(Entity target, Entity cause) { return indirectMagic(target, cause); } // Paper - OBFHELPER + public static DamageSource indirectMagic(Entity magic, @Nullable Entity attacker) { + return (new IndirectEntityDamageSource("indirectMagic", magic, attacker)).bypassArmor().setMagic(); + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index 2ff7e8dfc0a520fb330177c140a1ae5c729f14c0..8bf095bce9dfec6479185b6e60c2e9d76e3363eb 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -122,6 +122,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + private void applyWater() { + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); + List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE); ++ Map affected = new HashMap<>(); // Paper + + if (!list.isEmpty()) { + Iterator iterator = list.iterator(); +@@ -131,11 +132,22 @@ public class ThrownPotion extends ThrowableItemProjectile { + double d0 = this.distanceToSqr(entityliving); + + if (d0 < 16.0D && entityliving.isSensitiveToWater()) { +- entityliving.hurt(DamageSource.indirectMagic(entityliving, this.getOwner()), 1.0F); ++ // Paper start ++ double intensity = 1.0D - Math.sqrt(d0) / 4.0D; ++ affected.put(entityliving.getBukkitLivingEntity(), intensity); ++ // entityliving.damageEntity(DamageSource.c(entityliving, this.getShooter()), 1.0F); // Paper - moved down + } + } + } + ++ org.bukkit.event.entity.PotionSplashEvent event = CraftEventFactory.callPotionSplashEvent(this, affected); ++ if (!event.isCancelled()) { ++ for (LivingEntity affectedEntity : event.getAffectedEntities()) { ++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) affectedEntity).getHandle(); ++ entityliving.hurt(DamageSource.indirectMagic(entityliving, this.getOwner()), 1.0F); ++ } ++ } ++ // Paper end + } + + private void applySplash(List statusEffects, @Nullable Entity entity) { +@@ -153,6 +165,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + double d0 = this.distanceToSqr(entityliving); + + if (d0 < 16.0D) { ++ // Paper - diff on change, used when calling the splash event for water splash potions + double d1 = 1.0D - Math.sqrt(d0) / 4.0D; + + if (entityliving == entity) {