diff --git a/gradle.properties b/gradle.properties
index d15b9491e..5ecc802e9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
group = org.purpurmc.purpur
version = 1.18.2-R0.1-SNAPSHOT
-paperCommit = d33cdcf2e6ee53b28cabe99f417c7995bd9b7bc3
+paperCommit = 63aa4d33194cae156d1bed840868336cda6bc52b
org.gradle.caching = true
org.gradle.parallel = true
diff --git a/patches/server/0001-Pufferfish-Server-Changes.patch b/patches/server/0001-Pufferfish-Server-Changes.patch
index 4df6f1dcf..691dfd071 100644
--- a/patches/server/0001-Pufferfish-Server-Changes.patch
+++ b/patches/server/0001-Pufferfish-Server-Changes.patch
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/build.gradle.kts b/build.gradle.kts
-index 4beb35d1e5b013395f5df101e843f41c2ce174ad..27c18ced1ce6d38c9cd05eb4269f25a9d6520030 100644
+index 4beb35d1e5b013395f5df101e843f41c2ce174ad..ecb65e75d19d2f7b72410c89251e56566b7b6dc0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,8 +18,12 @@ repositories {
@@ -31,7 +31,7 @@ index 4beb35d1e5b013395f5df101e843f41c2ce174ad..27c18ced1ce6d38c9cd05eb4269f25a9
- implementation(project(":paper-mojangapi"))
+ implementation(project(":pufferfish-api")) // Pufferfish // Paper
+ // Pufferfish start
-+ implementation("io.papermc.paper:paper-mojangapi:1.18.1-R0.1-SNAPSHOT") {
++ implementation("io.papermc.paper:paper-mojangapi:1.18.2-R0.1-SNAPSHOT") {
+ exclude("io.papermc.paper", "paper-api")
+ }
+ // Pufferfish end
@@ -555,7 +555,7 @@ index 0000000000000000000000000000000000000000..a7f297ebb569f7c1f205e967ca485be7
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java
new file mode 100644
-index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f74732f4ab6ea
+index 0000000000000000000000000000000000000000..e164237e749bcc43466d4ed7aeada5ab9fddf8a6
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java
@@ -0,0 +1,68 @@
@@ -583,7 +583,7 @@ index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f7473
+ this.usageMessage = "/pufferfish [reload | version]";
+ this.setPermission("bukkit.command.pufferfish");
+ }
-+
++
+ public static void init() {
+ MinecraftServer.getServer().server.getCommandMap().register("pufferfish", "Pufferfish", new PufferfishCommand());
+ }
@@ -629,7 +629,7 @@ index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f7473
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
new file mode 100644
-index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c6633c8bb
+index 0000000000000000000000000000000000000000..cd03fed32807fd943fdeee8571c1648b8bd567d0
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
@@ -0,0 +1,291 @@
@@ -665,10 +665,10 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+import java.util.Collections;
+
+public class PufferfishConfig {
-+
++
+ private static final YamlFile config = new YamlFile();
+ private static int updates = 0;
-+
++
+ private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) {
+ ConfigurationSection newSection = new MemoryConfiguration();
+ for (String key : section.getKeys(false)) {
@@ -680,18 +680,18 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ }
+ return newSection;
+ }
-+
++
+ public static ConfigurationSection getConfigCopy() {
+ return convertToBukkit(config);
+ }
-+
++
+ public static int getUpdates() {
+ return updates;
+ }
-+
++
+ public static void load() throws IOException {
+ File configFile = new File("pufferfish.yml");
-+
++
+ if (configFile.exists()) {
+ try {
+ config.load(configFile);
@@ -699,14 +699,14 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ throw new IOException(e);
+ }
+ }
-+
++
+ getString("info.version", "1.0");
+ setComment("info",
+ "Pufferfish Configuration",
+ "Check out Pufferfish Host for maximum performance server hosting: https://pufferfish.host",
+ "Join our Discord for support: https://discord.gg/reZw4vQV9H",
+ "Download new builds at https://ci.pufferfish.host/job/Pufferfish");
-+
++
+ for (Method method : PufferfishConfig.class.getDeclaredMethods()) {
+ if (Modifier.isStatic(method.getModifiers()) && Modifier.isPrivate(method.getModifiers()) && method.getParameterCount() == 0 &&
+ method.getReturnType() == Void.TYPE && !method.getName().startsWith("lambda")) {
@@ -718,17 +718,17 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ }
+ }
+ }
-+
++
+ updates++;
-+
++
+ config.save(configFile);
-+
++
+ // Attempt to detect vectorization
+ try {
+ SIMDDetection.isEnabled = SIMDDetection.canEnable();
+ SIMDDetection.versionLimited = SIMDDetection.getJavaVersion() != 17;
+ } catch (NoClassDefFoundError | Exception ignored) {}
-+
++
+ if (SIMDDetection.isEnabled) {
+ PufferfishLogger.LOGGER.info("SIMD operations detected as functional. Will replace some operations with faster versions.");
+ } else if (SIMDDetection.versionLimited) {
@@ -738,76 +738,76 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ PufferfishLogger.LOGGER.warning("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\".");
+ }
+ }
-+
++
+ private static void setComment(String key, String... comment) {
+ if (config.contains(key)) {
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
-+
++
+ private static void ensureDefault(String key, Object defaultValue, String... comment) {
+ if (!config.contains(key)) {
+ config.set(key, defaultValue);
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
-+
++
+ private static boolean getBoolean(String key, boolean defaultValue, String... comment) {
+ return getBoolean(key, null, defaultValue, comment);
+ }
-+
++
+ private static boolean getBoolean(String key, @Nullable String oldKey, boolean defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getBoolean(key, defaultValue);
+ }
-+
++
+ private static int getInt(String key, int defaultValue, String... comment) {
+ return getInt(key, null, defaultValue, comment);
+ }
-+
++
+ private static int getInt(String key, @Nullable String oldKey, int defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getInt(key, defaultValue);
+ }
-+
++
+ private static double getDouble(String key, double defaultValue, String... comment) {
+ return getDouble(key, null, defaultValue, comment);
+ }
-+
++
+ private static double getDouble(String key, @Nullable String oldKey, double defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getDouble(key, defaultValue);
+ }
-+
++
+ private static String getString(String key, String defaultValue, String... comment) {
+ return getOldString(key, null, defaultValue, comment);
+ }
-+
++
+ private static String getOldString(String key, @Nullable String oldKey, String defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getString(key, defaultValue);
+ }
-+
++
+ private static List getStringList(String key, List defaultValue, String... comment) {
+ return getStringList(key, null, defaultValue, comment);
+ }
-+
++
+ private static List getStringList(String key, @Nullable String oldKey, List defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getStringList(key);
+ }
-+
++
+ public static String sentryDsn;
+ private static void sentry() {
+ String sentryEnvironment = System.getenv("SENTRY_DSN");
+ String sentryConfig = getString("sentry-dsn", "", "Sentry DSN for improved error logging, leave blank to disable", "Obtain from https://sentry.io/");
-+
++
+ sentryDsn = sentryEnvironment == null ? sentryConfig : sentryEnvironment;
+ if (sentryDsn != null && !sentryDsn.isBlank()) {
+ gg.pufferfish.pufferfish.sentry.SentryManager.init();
+ }
+ }
-+
++
+ public static boolean enableBooks;
+ private static void books() {
+ enableBooks = getBoolean("enable-books", true,
@@ -816,7 +816,7 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ "disabling this option.",
+ "This can be overridden per-player with the permission pufferfish.usebooks");
+ }
-+
++
+ public static boolean enableSuffocationOptimization;
+ private static void suffocationOptimization() {
+ enableSuffocationOptimization = getBoolean("enable-suffocation-optimization", true,
@@ -825,7 +825,7 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ "be left enabled on most servers, but is provided as a",
+ "configuration option if the vanilla deviation is undesirable.");
+ }
-+
++
+ public static boolean enableAsyncMobSpawning;
+ public static boolean asyncMobSpawningInitialized;
+ private static void asyncMobSpawning() {
@@ -835,14 +835,14 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+ "paper's per-player-mob-spawns setting set to true for this to work.",
+ "One quick note - this does not actually spawn mobs async (that would be very unsafe).",
+ "This just offloads some expensive calculations that are required for mob spawning.");
-+
++
+ // This prevents us from changing the value during a reload.
+ if (!asyncMobSpawningInitialized) {
+ asyncMobSpawningInitialized = true;
+ enableAsyncMobSpawning = temp;
+ }
+ }
-+
++
+ public static int maxProjectileLoadsPerTick;
+ public static int maxProjectileLoadsPerProjectile;
+ private static void projectileLoading() {
@@ -884,7 +884,7 @@ index 0000000000000000000000000000000000000000..56330536c52fa327ef89d7a08e72557c
+
+ setComment("dab", "Optimizes entity brains when", "they're far away from the player");
+ }
-+
++
+ public static boolean throttleInactiveGoalSelectorTick;
+ private static void inactiveGoalSelectorThrottle() {
+ getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", true,
@@ -948,7 +948,7 @@ index 0000000000000000000000000000000000000000..53f2df00c6809618a9ee3d2ea72e85e8
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java
new file mode 100644
-index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231a0133101
+index 0000000000000000000000000000000000000000..461022af9ad85fe00329678f0f61d684d291c628
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java
@@ -0,0 +1,136 @@
@@ -980,46 +980,46 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+import org.jetbrains.annotations.Nullable;
+
+public class PufferfishVersionFetcher implements VersionFetcher {
-+
++
+ private static final Logger LOGGER = Logger.getLogger("PufferfishVersionFetcher");
+ private static final HttpClient client = HttpClient.newHttpClient();
-+
++
+ private static final URI JENKINS_URI = URI.create("https://ci.pufferfish.host/job/Pufferfish-1.18/lastSuccessfulBuild/buildNumber");
+ private static final String GITHUB_FORMAT = "https://api.github.com/repos/pufferfish-gg/Pufferfish/compare/ver/1.18...%s";
-+
++
+ private static final HttpResponse.BodyHandler JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers
+ .mapping(
+ HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
+ string -> new Gson().fromJson(string, JsonObject.class)
+ );
-+
++
+ @Override
+ public long getCacheTime() {
+ return TimeUnit.MINUTES.toMillis(30);
+ }
-+
++
+ @Override
+ public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
+ final String[] parts = CraftServer.class.getPackage().getImplementationVersion().split("-");
+ @NotNull Component component;
-+
++
+ if (parts.length != 3) {
+ component = text("Unknown server version.", RED);
+ } else {
+ final String versionString = parts[2];
-+
++
+ try {
+ component = this.fetchJenkinsVersion(Integer.parseInt(versionString));
+ } catch (NumberFormatException e) {
+ component = this.fetchGithubVersion(versionString.substring(1, versionString.length() - 1));
+ }
+ }
-+
++
+ final @Nullable Component history = this.getHistory();
+ return history != null ? Component
+ .join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component;
+ }
-+
++
+ private @NotNull Component fetchJenkinsVersion(final int versionNumber) {
+ final HttpRequest request = HttpRequest.newBuilder(JENKINS_URI).build();
+ try {
@@ -1027,7 +1027,7 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
-+
++
+ int latestVersionNumber;
+ try {
+ latestVersionNumber = Integer.parseInt(response.body());
@@ -1035,7 +1035,7 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+ LOGGER.log(Level.WARNING, "Received invalid response from Jenkins \"" + response.body() + "\".");
+ return text("Received invalid response from server.", RED);
+ }
-+
++
+ final int versionDiff = latestVersionNumber - versionNumber;
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
@@ -1043,7 +1043,7 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
-+
++
+ // Based off code contributed by Techcable in Paper/GH-65
+ private @NotNull Component fetchGithubVersion(final @NotNull String hash) {
+ final URI uri = URI.create(String.format(GITHUB_FORMAT, hash));
@@ -1053,17 +1053,17 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
-+
++
+ final JsonObject obj = response.body();
+ final int versionDiff = obj.get("behind_by").getAsInt();
-+
++
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e);
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
-+
++
+ private @NotNull Component getResponseMessage(final int versionDiff) {
+ return switch (Math.max(-1, Math.min(1, versionDiff))) {
+ case -1 -> text("You are running an unsupported version of Pufferfish.", RED);
@@ -1073,18 +1073,18 @@ index 0000000000000000000000000000000000000000..adafc4fd661cf080b004b86c3eaed231
+ RED);
+ };
+ }
-+
++
+ private @Nullable 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);
+ }
+}
@@ -1784,7 +1784,7 @@ index 0000000000000000000000000000000000000000..db15d3fbe2b65fc8035573f5fdbea382
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
new file mode 100644
-index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621
+index 0000000000000000000000000000000000000000..d04a8a4336566dbe6e1b9ec0d574cff43e003fa8
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
@@ -0,0 +1,135 @@
@@ -1808,14 +1808,14 @@ index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834
+import org.apache.logging.log4j.core.filter.AbstractFilter;
+
+public class PufferfishSentryAppender extends AbstractAppender {
-+
++
+ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class);
+ private static final Gson GSON = new Gson();
-+
++
+ public PufferfishSentryAppender() {
+ super("PufferfishSentryAdapter", new SentryFilter(), null);
+ }
-+
++
+ @Override
+ public void append(LogEvent logEvent) {
+ if (logEvent.getThrown() != null && logEvent.getLevel().isMoreSpecificThan(Level.WARN)) {
@@ -1832,55 +1832,55 @@ index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834
+ }
+ }
+ }
-+
++
+ private void logException(LogEvent e) {
+ SentryEvent event = new SentryEvent(e.getThrown());
-+
++
+ Message sentryMessage = new Message();
+ sentryMessage.setMessage(e.getMessage().getFormattedMessage());
-+
++
+ event.setThrowable(e.getThrown());
+ event.setLevel(getLevel(e.getLevel()));
+ event.setLogger(e.getLoggerName());
+ event.setTransaction(e.getLoggerName());
+ event.setExtra("thread_name", e.getThreadName());
-+
++
+ boolean hasContext = e.getContextData() != null;
-+
++
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) {
+ User user = new User();
+ user.setId(e.getContextData().getValue("pufferfishsentry_playerid"));
+ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername"));
+ event.setUser(user);
+ }
-+
++
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) {
+ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname"));
+ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion"));
+ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname"));
+ }
-+
++
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) {
+ Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken