diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..210d3ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.java] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2fb638f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto + +*.sh text eol=lf +gradlew text eol=lf +*.bat text eol=crlf + +*.jar binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65db637 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +.gradle/ +build/ + +# Eclipse stuff +.classpath +.project +.settings/ + +# VSCode stuff +.vscode/ + +# netbeans +nbproject/ +nbactions.xml + +# we use maven! +build.xml + +# maven +target/ +dependency-reduced-pom.xml + +# vim +.*.sw[a-p] + +# various other potential build files +build/ +bin/ +dist/ +manifest.mf + +# Mac filesystem dust +.DS_Store/ +.DS_Store + +# intellij +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Linux temp files +*~ + +# other stuff +run/ + +Sakura-Server +Sakura-API + +!gradle/wrapper/gradle-wrapper.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..e28144c --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Sakura +=========== +This is a fork of Paper to optimise cannoning intended for cannon and factions servers on modern versions. + +--- + +Initially this repository will be publicised with just some cannon optimisations. +Later updates will bring feature parity with [Blossom](https://github.com/Samsuik/Blossom) and the private Sakura builds is planned. + diff --git a/build-data/dev-imports.txt b/build-data/dev-imports.txt new file mode 100644 index 0000000..f8483bf --- /dev/null +++ b/build-data/dev-imports.txt @@ -0,0 +1,12 @@ +# You can use this file to import files from minecraft libraries into the project +# format: +# +# both fully qualified and a file based syntax are accepted for : +# authlib com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +# datafixerupper com.mojang.datafixers.DataFixerBuilder +# datafixerupper com/mojang/datafixers/util/Either.java +# To import classes from the vanilla Minecraft jar use `minecraft` as the artifactId: +# minecraft net.minecraft.world.level.entity.LevelEntityGetterAdapter +# minecraft net/minecraft/world/level/entity/LevelEntityGetter.java +minecraft net.minecraft.world.level.block.piston.PistonStructureResolver +minecraft net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..c0aff74 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,61 @@ +import io.papermc.paperweight.util.constants.PAPERCLIP_CONFIG + +plugins { + java + id("com.github.johnrengelman.shadow") version "8.1.1" apply false + id("io.papermc.paperweight.patcher") version "1.5.7" +} + +repositories { + mavenCentral() + maven("https://papermc.io/repo/repository/maven-public/") { + content { onlyForConfigurations(PAPERCLIP_CONFIG) } + } +} + +dependencies { + remapper("net.fabricmc:tiny-remapper:0.8.6:fat") + decompiler("net.minecraftforge:forgeflower:2.0.627.2") + paperclip("io.papermc:paperclip:3.0.3") +} + +subprojects { + apply(plugin = "java") + + java { + toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } + } + + tasks.withType().configureEach { + options.encoding = "UTF-8" + options.release.set(17) + } + + repositories { + mavenCentral() + maven("https://oss.sonatype.org/content/groups/public/") + maven("https://papermc.io/repo/repository/maven-public/") + maven("https://ci.emc.gs/nexus/content/groups/aikar/") + maven("https://repo.aikar.co/content/groups/aikar") + maven("https://repo.md-5.net/content/repositories/releases/") + maven("https://hub.spigotmc.org/nexus/content/groups/public/") + maven("https://jitpack.io") + } +} + +paperweight { + serverProject.set(project(":sakura-server")) + + remapRepo.set("https://maven.fabricmc.net/") + decompileRepo.set("https://files.minecraftforge.net/maven/") + + usePaperUpstream(providers.gradleProperty("paperRef")) { + withPaperPatcher { + apiPatchDir.set(layout.projectDirectory.dir("patches/api")) + apiOutputDir.set(layout.projectDirectory.dir("sakura-api")) + + serverPatchDir.set(layout.projectDirectory.dir("patches/server")) + serverOutputDir.set(layout.projectDirectory.dir("sakura-server")) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..361002e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +group=me.samsuik.sakura +version=1.20.2-R0.1-SNAPSHOT + +mcVersion=1.20.2 +paperRef=08c0b488b9f1e56a4465cfabe6212d0351b69e59 + +org.gradle.jvmargs=-Xmx2G + +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.daemon=true +# org.gradle.configureondemand=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1bef7e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/patches/api/0001-Customise-Version-Command.patch b/patches/api/0001-Customise-Version-Command.patch new file mode 100644 index 0000000..6873f9e --- /dev/null +++ b/patches/api/0001-Customise-Version-Command.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Wed, 2 Aug 2023 18:00:04 +0100 +Subject: [PATCH] Customise Version Command + + +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index f78b5fd3c3347d28da58777bff88903d2eb140f6..5999909827ede80c14becb058cde3189f10fb3ef 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -127,6 +127,20 @@ public final class Bukkit { + // Paper end + } + ++ // Sakura start - expose git info ++ @NotNull ++ public static String getGitInformation() { ++ final var manifest = JarManifests.manifest(Bukkit.getServer().getClass()); ++ final String gitBranch = manifest == null ? null : manifest.getMainAttributes().getValue("Git-Branch"); ++ final String gitCommit = manifest == null ? null : manifest.getMainAttributes().getValue("Git-Commit"); ++ String branchMsg = " on " + gitBranch; ++ if ("master".equals(gitBranch) || "main".equals(gitBranch)) { ++ branchMsg = ""; // Don't show branch on main/master ++ } ++ return "(Git: " + gitCommit + branchMsg + ")"; ++ } ++ // Sakura end ++ + /** + * Gets the name of this server implementation. + * +diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +index fd5d9881abfd930bb883120f018f76dc78b62b14..0cf55a5a4afc8d2eccd781a613b407c857cfe13d 100644 +--- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +@@ -17,6 +17,15 @@ import java.util.HashSet; + import java.util.List; + import java.util.Set; + import java.util.concurrent.locks.ReentrantLock; ++ ++import io.papermc.paper.util.JarManifests; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.event.HoverEvent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.Tag; ++import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; + import org.bukkit.Bukkit; + import org.bukkit.ChatColor; + import org.bukkit.command.CommandSender; +@@ -49,17 +58,29 @@ public class VersionCommand extends BukkitCommand { + this.description = "Gets the version of this server including any plugins in use"; + this.usageMessage = "/version [plugin name]"; + this.setPermission("bukkit.command.version"); +- this.setAliases(Arrays.asList("ver", "about")); ++ this.setAliases(Arrays.asList("ver", "about", "sver")); // Sakura + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { +- if (!testPermission(sender)) return true; ++ // Sakura - move down into else + + if (args.length == 0) { + //sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); // Paper - moved to setVersionMessage +- sendVersion(sender); +- } else { ++ // Sakura start ++ sender.sendMessage(MiniMessage.miniMessage().deserialize(""" ++ . ++ | This server is running Sakura ++ | Commit: \\<> targeting (MC: ) ++ | Github: \\<link> ++ '""", ++ TagResolver.resolver("phase", Tag.preProcessParsed("")), ++ TagResolver.resolver("commit", Tag.selfClosingInserting(Component.text("hover", NamedTextColor.YELLOW) ++ .hoverEvent(HoverEvent.showText(Component.text(Bukkit.getGitInformation()))))), ++ TagResolver.resolver("version", Tag.preProcessParsed(Bukkit.getMinecraftVersion())) ++ )); ++ } else if (testPermission(sender)) { ++ // Sakura end + StringBuilder name = new StringBuilder(); + + for (String arg : args) { diff --git a/patches/api/0002-Player-GUI-API.patch b/patches/api/0002-Player-GUI-API.patch new file mode 100644 index 0000000..9db8b52 --- /dev/null +++ b/patches/api/0002-Player-GUI-API.patch @@ -0,0 +1,135 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 12 Jan 2023 22:59:28 +0000 +Subject: [PATCH] Player GUI API + + +diff --git a/src/main/java/me/samsuik/sakura/player/gui/ItemIcon.java b/src/main/java/me/samsuik/sakura/player/gui/ItemIcon.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cc0645aa27e6e96922f26242e71fef5d6c06fcfd +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/player/gui/ItemIcon.java +@@ -0,0 +1,9 @@ ++package me.samsuik.sakura.player.gui; ++ ++import org.bukkit.entity.Player; ++import org.bukkit.inventory.ItemStack; ++ ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++public record ItemIcon(Function itemUpdate, Consumer onClick, int slot) {} +diff --git a/src/main/java/me/samsuik/sakura/player/gui/PlayerGUI.java b/src/main/java/me/samsuik/sakura/player/gui/PlayerGUI.java +new file mode 100644 +index 0000000000000000000000000000000000000000..55d914153ca3f6458ee52223665516d5735a7c34 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/player/gui/PlayerGUI.java +@@ -0,0 +1,108 @@ ++package me.samsuik.sakura.player.gui; ++ ++import org.bukkit.entity.Player; ++import org.bukkit.event.EventPriority; ++import org.bukkit.event.inventory.InventoryClickEvent; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.InventoryHolder; ++import org.bukkit.plugin.EventExecutor; ++import org.bukkit.plugin.RegisteredListener; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++ ++public abstract class PlayerGUI { ++ ++ private static final List REGISTERED_GUIS = new ArrayList<>(); ++ protected static final Consumer NOTHING = (p) -> {}; ++ ++ private final Map iconMap = new HashMap<>(); ++ private final Holder holder = new Holder(); ++ private final BiFunction base; ++ private boolean registered = false; ++ ++ public PlayerGUI(BiFunction base) { ++ this.base = base; ++ } ++ ++ protected void registerIcon(ItemIcon icon) { ++ iconMap.put(icon.slot(), icon); ++ } ++ ++ protected abstract void register(); ++ ++ public void unregister() { ++ REGISTERED_GUIS.remove(this); ++ registered = false; ++ } ++ ++ @ApiStatus.Internal ++ public static void onWindowClick(InventoryClickEvent event) { ++ for (PlayerGUI ui : REGISTERED_GUIS) { ++ var inventory = event.getClickedInventory(); ++ ++ if (inventory != null && inventory.getHolder() == ui.holder) { ++ ui.handleSlotClick(event, inventory); ++ } ++ } ++ } ++ ++ private void handleSlotClick(InventoryClickEvent event, Inventory inventory) { ++ event.setCancelled(true); ++ ++ var player = (Player) event.getWhoClicked(); ++ var slot = event.getSlot(); ++ var icon = iconMap.get(slot); ++ ++ if (icon != null) { ++ icon.onClick() ++ .andThen((p) -> updateIcon(player, icon, inventory)) ++ .accept(player); ++ } ++ } ++ ++ private void updateIcon(Player player, ItemIcon icon, Inventory inventory) { ++ var item = icon.itemUpdate().apply(player); ++ inventory.setItem(icon.slot(), item); ++ } ++ ++ public void showTo(Player player) { ++ player.openInventory(createInventory(player)); ++ } ++ ++ private Inventory createInventory(Player player) { ++ if (!registered) { ++ registerUI(); ++ } ++ ++ var inventory = base.apply(player, holder); ++ ++ for (var icon : iconMap.values()) { ++ updateIcon(player, icon, inventory); ++ } ++ ++ return inventory; ++ } ++ ++ private void registerUI() { ++ register(); ++ REGISTERED_GUIS.add(this); ++ registered = true; ++ } ++ ++ protected static class Holder implements InventoryHolder { ++ private Holder() {} ++ ++ @Override ++ public @NotNull Inventory getInventory() { ++ return null; ++ } ++ } ++ ++} diff --git a/patches/api/0003-Visibility-API.patch b/patches/api/0003-Visibility-API.patch new file mode 100644 index 0000000..1ef7b15 --- /dev/null +++ b/patches/api/0003-Visibility-API.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Tue, 21 Sep 2021 23:55:45 +0100 +Subject: [PATCH] Visibility API + + +diff --git a/src/main/java/me/samsuik/sakura/player/visibility/Visibility.java b/src/main/java/me/samsuik/sakura/player/visibility/Visibility.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8d9a5ffe7dfe00ff2d3deb6bc94b5dc53af15cd4 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/player/visibility/Visibility.java +@@ -0,0 +1,142 @@ ++package me.samsuik.sakura.player.visibility; ++ ++import java.util.EnumSet; ++import java.util.Locale; ++ ++public class Visibility { ++ ++ private final EnumSet settings = EnumSet.noneOf(Setting.class); ++ ++ /** ++ * Checks if the provided setting has been modified. ++ * ++ * @param setting provided ++ * @return the setting has been modified ++ */ ++ public boolean isToggled(Setting setting) { ++ return settings.contains(setting); ++ } ++ ++ /** ++ * Checks if the provided setting is enabled. ++ * ++ * @param setting provided ++ * @return the setting is enabled ++ */ ++ public boolean isEnabled(Setting setting) { ++ return setting.defaultValue != isToggled(setting); ++ } ++ ++ /** ++ * Checks if the provided setting is disabled. ++ * ++ * @param setting provided ++ * @return the setting is disabled ++ */ ++ public boolean isDisabled(Setting setting) { ++ return !isEnabled(setting); ++ } ++ ++ /** ++ * Toggles the provided settings state. ++ * [ true -> false, false -> true ] ++ * NOTE: the default value can vary. ++ * ++ * @param setting provided ++ */ ++ public void toggle(Setting setting) { ++ if (settings.contains(setting)) { ++ settings.remove(setting); ++ } else { ++ settings.add(setting); ++ } ++ } ++ ++ /** ++ * Toggles the provided settings state if it matches the modified state. ++ * ++ * @param setting provided ++ * @param modified fun ++ */ ++ public void toggle(Setting setting, boolean modified) { ++ if (isToggled(setting) == modified) { ++ toggle(setting); ++ } ++ } ++ ++ /** ++ * Sets the provided settings state. ++ * ++ * @param setting provided ++ * @param enable whether to enable this setting ++ */ ++ public void set(Setting setting, boolean enable) { ++ if (isEnabled(setting) && !enable || isDisabled(setting) && enable) { ++ toggle(setting); ++ } ++ } ++ ++ /** ++ * Toggles all settings based on the logic below. ++ * IF: settings are untouched or have been modified ++ * THEN: disable all settings ++ * IF: all settings are disabled ++ * THEN: enable all settings ++ */ ++ public void toggleAll() { ++ var toggled = true; // true == all toggled ++ ++ // If something is enabled set state to true ++ for (var setting : Setting.values()) { ++ toggled &= isToggled(setting); ++ } ++ ++ // apply the opposite of the state so that it toggles ++ for (var setting : Setting.values()) { ++ toggle(setting, toggled); ++ } ++ } ++ ++ /** ++ * Whether the settings have been modified. ++ * This is exposed to allow for quick return optimisations. ++ * ++ * @return settings have been modified ++ */ ++ public boolean isModified() { ++ return !settings.isEmpty(); ++ } ++ ++ public enum Setting { ++ TNT_VISIBILITY("TNT Visibility", true), ++ SAND_VISIBILITY("Sand Visibility", true), ++ MINIMAL("Minimal TNT/Sand", false), ++ FLASHING_TNT("Flashing TNT", true), ++ EXPLOSIONS("Explosion Particles", true), ++ ENCHANTMENT_GLINT("Enchantment Glint", true), ++ SPAWNERS("Spawner Visibility", true), ++ REDSTONE("Redstone Animations", true), ++ PISTONS("Piston Animations", true); ++ ++ private final String friendlyName; ++ private final boolean defaultValue; ++ ++ Setting(String friendlyName, boolean defaultValue) { ++ this.friendlyName = friendlyName; ++ this.defaultValue = defaultValue; ++ } ++ ++ public boolean getDefault() { ++ return defaultValue; ++ } ++ ++ public String friendlyName() { ++ return friendlyName; ++ } ++ ++ public String basicName() { ++ return name().replace("_", "").toLowerCase(Locale.ROOT); ++ } ++ } ++ ++} +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index 20fa1024f9ad8f478a347be5c554b5e45b398a1c..b6d299b2fa9bb30d0a3b8735417dfa9be897d355 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -53,6 +53,15 @@ import org.jetbrains.annotations.Nullable; + */ + public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer, com.destroystokyo.paper.network.NetworkClient { // Paper + ++ // Sakura start ++ /** ++ * Visibility API for FPS settings. ++ * ++ * @return visibility api ++ */ ++ me.samsuik.sakura.player.visibility.Visibility getVisibility(); ++ // Sakura end ++ + // Paper start + @Override + default net.kyori.adventure.identity.@NotNull Identity identity() { diff --git a/patches/api/0004-Merge-Cannon-Entities.patch b/patches/api/0004-Merge-Cannon-Entities.patch new file mode 100644 index 0000000..edde5d7 --- /dev/null +++ b/patches/api/0004-Merge-Cannon-Entities.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Thu, 5 Oct 2023 14:34:30 +0100 +Subject: [PATCH] Merge Cannon Entities + + +diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java +new file mode 100644 +index 0000000000000000000000000000000000000000..16e68720eaf2eeedb2ba9bb906c21a16fc818e89 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java +@@ -0,0 +1,65 @@ ++package me.samsuik.sakura.entity.merge; ++ ++import java.util.Locale; ++ ++public enum MergeLevel { ++ /** ++ * Disabled. ++ */ ++ NONE(-1), ++ /** ++ * Merge entities with the same OOE, properties and position. ++ * This is safe for all use cases and won't cause breakage. ++ */ ++ STRICT(1), ++ /** ++ * Merge entities with the same properties and position. ++ * If the entities are known to have merged in the past ++ * this will be able to merge them together regardless of OOE. ++ */ ++ NON_STRICT(2), ++ /** ++ * This will make sure of Non-Strict as a base, once the cannon ++ * has past the "on spawn" merge threshold it will be deemed safe ++ * for on spawn merging. This will merge all entities together on ++ * spawn that have merged together in the past. ++ */ ++ SPAWN(3); ++ ++ private final int level; ++ ++ MergeLevel(int level) { ++ this.level = level; ++ } ++ ++ public boolean atLeast(MergeLevel level) { ++ return getLevel() >= level.getLevel(); ++ } ++ ++ public int getLevel() { ++ return level; ++ } ++ ++ public static MergeLevel from(int of) { ++ for (MergeLevel t : values()) { ++ if (t.getLevel() == of) { ++ return t; ++ } ++ } ++ ++ return NONE; ++ } ++ ++ public static MergeLevel from(String string) { ++ try { ++ return from(Integer.parseInt(string)); ++ } catch (NumberFormatException ignored) {} ++ ++ try { ++ var name = string.toUpperCase(Locale.ROOT); ++ return valueOf(name); ++ } catch (IllegalArgumentException ignored) {} ++ ++ return NONE; ++ } ++} +diff --git a/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java b/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c241a3a359cad1d7c2cdb690649e0cacc491508b +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java +@@ -0,0 +1,13 @@ ++package me.samsuik.sakura.entity.merge; ++ ++import org.jetbrains.annotations.NotNull; ++ ++public interface Mergeable { ++ @NotNull MergeLevel getMergeLevel(); ++ ++ void setMergeLevel(@NotNull MergeLevel level); ++ ++ int getStacked(); ++ ++ void setStacked(int stacked); ++} +diff --git a/src/main/java/org/bukkit/entity/FallingBlock.java b/src/main/java/org/bukkit/entity/FallingBlock.java +index 95e75f5a4ccdedd3b26f8639f37de9450ed63d6b..be52a64e73e7880c6a28d8a6912923973b7cb13b 100644 +--- a/src/main/java/org/bukkit/entity/FallingBlock.java ++++ b/src/main/java/org/bukkit/entity/FallingBlock.java +@@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; + /** + * Represents a falling block + */ +-public interface FallingBlock extends Entity { ++public interface FallingBlock extends Entity, me.samsuik.sakura.entity.merge.Mergeable { // Sakura + + /** + * Get the Material of the falling block +diff --git a/src/main/java/org/bukkit/entity/TNTPrimed.java b/src/main/java/org/bukkit/entity/TNTPrimed.java +index 0813bd913c8fdb2001963ce3e82c07c2af105418..0bc227b6ba953c778ac950ec40f99276c77e880c 100644 +--- a/src/main/java/org/bukkit/entity/TNTPrimed.java ++++ b/src/main/java/org/bukkit/entity/TNTPrimed.java +@@ -6,7 +6,7 @@ import org.jetbrains.annotations.Nullable; + /** + * Represents a Primed TNT. + */ +-public interface TNTPrimed extends Explosive { ++public interface TNTPrimed extends Explosive, me.samsuik.sakura.entity.merge.Mergeable { // Sakura + + /** + * Set the number of ticks until the TNT blows up after being primed. diff --git a/patches/api/0005-isPushedByFluid-API.patch b/patches/api/0005-isPushedByFluid-API.patch new file mode 100644 index 0000000..e14cb86 --- /dev/null +++ b/patches/api/0005-isPushedByFluid-API.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Fri, 13 Oct 2023 20:01:48 +0100 +Subject: [PATCH] isPushedByFluid API + + +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index d340ddcf6924cc834455de3acbbac91ab9c66e39..7170c875d81a5e234ee952062b6197dab088241f 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -108,6 +108,23 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + */ + public boolean isInWater(); + ++ // Sakura start ++ ++ /** ++ * Gets if the entity will be pushed by fluid ++ * ++ * @return ahem ++ */ ++ boolean isPushedByFluid(); ++ ++ /** ++ * Sets if the entity will be pushed by fluid ++ * ++ * @param push pushed by fluid ++ */ ++ void setPushedByFluid(boolean push); ++ // Sakura end ++ + /** + * Gets the current world this entity resides in + * diff --git a/patches/api/0006-Falling-Block-Parity-API.patch b/patches/api/0006-Falling-Block-Parity-API.patch new file mode 100644 index 0000000..7d1633e --- /dev/null +++ b/patches/api/0006-Falling-Block-Parity-API.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Sun, 15 Oct 2023 22:53:38 +0100 +Subject: [PATCH] Falling Block Parity API + + +diff --git a/src/main/java/org/bukkit/entity/FallingBlock.java b/src/main/java/org/bukkit/entity/FallingBlock.java +index be52a64e73e7880c6a28d8a6912923973b7cb13b..265a6029d65216fac7d309dda431a2b2ff1786b8 100644 +--- a/src/main/java/org/bukkit/entity/FallingBlock.java ++++ b/src/main/java/org/bukkit/entity/FallingBlock.java +@@ -156,4 +156,19 @@ public interface FallingBlock extends Entity, me.samsuik.sakura.entity.merge.Mer + */ + void shouldAutoExpire(boolean autoExpires); + // Paper End - Auto expire setting ++ // Sakura start ++ /** ++ * Gets if falling block has height parity ++ * ++ * @return parity ++ */ ++ boolean getHeightParity(); ++ ++ /** ++ * Sets falling block height parity ++ * ++ * @param parity value ++ */ ++ void setHeightParity(boolean parity); ++ // Sakura end + } diff --git a/patches/server/0001-Branding-changes.patch b/patches/server/0001-Branding-changes.patch new file mode 100644 index 0000000..c26edd0 --- /dev/null +++ b/patches/server/0001-Branding-changes.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Sat, 12 Jun 2021 16:40:34 +0200 +Subject: [PATCH] Branding changes + +From ForkPaper. + +diff --git a/build.gradle.kts b/build.gradle.kts +index c187641f0ec6444a10e0e1583e1697d07e8f0267..db5d5682a068518d9d5f0b232e2a582e3a8c4aee 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -13,8 +13,12 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) { + val alsoShade: Configuration by configurations.creating + + dependencies { +- implementation(project(":paper-api")) +- implementation(project(":paper-mojangapi")) ++ // Sakura start ++ implementation(project(":sakura-api")) ++ implementation("io.papermc.paper:paper-mojangapi:1.20.1-R0.1-SNAPSHOT") { ++ exclude("io.papermc.paper", "paper-api") ++ } ++ // Sakura end + // Paper start + implementation("org.jline:jline-terminal-jansi:3.21.0") + implementation("net.minecrell:terminalconsoleappender:1.3.0") +@@ -72,7 +76,7 @@ tasks.jar { + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", + "Implementation-Title" to "CraftBukkit", +- "Implementation-Version" to "git-Paper-$implementationVersion", ++ "Implementation-Version" to "git-Sakura-$implementationVersion", // Sakura + "Implementation-Vendor" to date, // Paper + "Specification-Title" to "Bukkit", + "Specification-Version" to project.version, +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 97745f0bab8d82d397c6c2a5775aed92bca0a034..188da766381393afe643de28dccaa6aa7c9130a0 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1697,7 +1697,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Spigot - Spigot > // CraftBukkit - cb > vanilla! ++ return "Sakura"; // Sakura - Sakura > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + } + + public SystemReport fillSystemReport(SystemReport details) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b7e7e6ed60f55d2ab5e4fcefb3638ad1768c3b7f..28a1e39ed1b7fd10f4c01e076258cb8689b4e5bf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -267,7 +267,7 @@ 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 serverName = "Sakura"; // Sakura // 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/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 774556a62eb240da42e84db4502e2ed43495be17..1941fd2dbdc7a10ddf17e2543a038dbd7fe8c88c 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/io.papermc.paper/paper-api/pom.properties"); ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/me.samsuik.sakura/sakura-api/pom.properties"); // Sakura + Properties properties = new Properties(); + + if (stream != null) { diff --git a/patches/server/0002-Sakura-Utils.patch b/patches/server/0002-Sakura-Utils.patch new file mode 100644 index 0000000..cfaa6d5 --- /dev/null +++ b/patches/server/0002-Sakura-Utils.patch @@ -0,0 +1,167 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Tue, 23 May 2023 23:07:20 +0100 +Subject: [PATCH] Sakura Utils + + +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java b/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ff4909a2ba8f451a7c6aa55ee98e33c88dd69e5e +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java +@@ -0,0 +1,51 @@ ++package me.samsuik.sakura.utils.collections; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++ ++import java.util.Arrays; ++import java.util.Comparator; ++ ++public class OrderedComparatorList extends ObjectArrayList { ++ ++ private final Comparator comparator; ++ private boolean binarySearch = true; ++ ++ public OrderedComparatorList(int capacity, Comparator comparator) { ++ super(capacity); ++ this.comparator = Comparator.nullsLast(comparator); ++ } ++ ++ public OrderedComparatorList(Comparator comparator) { ++ this(DEFAULT_INITIAL_CAPACITY, comparator); ++ } ++ ++ private void validateBounds(int index, T t, boolean up) { ++ if (index != 0 && comparator.compare(get(index - 1), t) > 0) { ++ binarySearch = false; ++ } else if (up && index < size() - 1 && comparator.compare(get(index + 1), t) < 0) { ++ binarySearch = false; ++ } ++ } ++ ++ @Override ++ public boolean add(T t) { ++ validateBounds(size(), t, false); ++ return super.add(t); ++ } ++ ++ @Override ++ public void add(int index, T t) { ++ validateBounds(index, t, true); ++ super.add(index, t); ++ } ++ ++ @Override ++ public int indexOf(final Object k) { ++ if (binarySearch) { ++ return Math.max(Arrays.binarySearch(a, (T) k, comparator), -1); ++ } else { ++ return super.indexOf(k); ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f4ac8acd2e0752e7a615d152b8047d790947b9f +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java +@@ -0,0 +1,29 @@ ++package me.samsuik.sakura.utils.collections; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectCollection; ++import net.minecraft.server.level.ChunkMap; ++ ++public class TrackedEntityChunkMap extends Int2ObjectOpenHashMap { ++ ++ private final ObjectArrayList entityList = new UnorderedIndexedList<>(); ++ ++ @Override ++ public ChunkMap.TrackedEntity put(int k, ChunkMap.TrackedEntity trackedEntity) { ++ entityList.add(trackedEntity); ++ return super.put(k, trackedEntity); ++ } ++ ++ @Override ++ public ChunkMap.TrackedEntity remove(int k) { ++ entityList.remove(k); ++ return super.remove(k); ++ } ++ ++ @Override ++ public ObjectCollection values() { ++ return entityList; ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java b/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7bdd93f1078dde97233a9096d0b7738dae061ae4 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java +@@ -0,0 +1,63 @@ ++package me.samsuik.sakura.utils.collections; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++ ++public class UnorderedIndexedList extends ObjectArrayList { ++ ++ private final Int2IntOpenHashMap elementToIndex = new Int2IntOpenHashMap(); ++ ++ { ++ elementToIndex.defaultReturnValue(-1); ++ } ++ ++ public UnorderedIndexedList(int capacity) { ++ super(capacity); ++ } ++ ++ public UnorderedIndexedList() { ++ super(); ++ } ++ ++ @Override ++ public boolean add(final T t) { ++ elementToIndex.put(t.hashCode(), size()); ++ return super.add(t); ++ } ++ ++ @Override ++ public T remove(final int index) { ++ final int tail = size() - 1; ++ final T at = a[index]; ++ ++ if (index != tail) { ++ final T tailObj = a[tail]; ++ if (tailObj != null) ++ elementToIndex.put(tailObj.hashCode(), index); ++ a[index] = tailObj; ++ } ++ ++ if (at != null) ++ elementToIndex.remove(at.hashCode()); ++ a[tail] = null; ++ size--; ++ return at; ++ } ++ ++ @Override ++ public void clear() { ++ elementToIndex.clear(); ++ super.clear(); ++ } ++ ++ @Override ++ public int indexOf(final Object k) { ++ return elementToIndex.get(k.hashCode()); ++ } ++ ++ @Override ++ public void add(final int index, final T t) { ++ throw new UnsupportedOperationException(); ++ } ++ ++} diff --git a/patches/server/0003-MC-Dev-Fixes.patch b/patches/server/0003-MC-Dev-Fixes.patch new file mode 100644 index 0000000..f3117dd --- /dev/null +++ b/patches/server/0003-MC-Dev-Fixes.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 15 Oct 2021 18:32:13 +0100 +Subject: [PATCH] MC-Dev-Fixes + + +diff --git a/src/main/java/net/minecraft/tags/TagKey.java b/src/main/java/net/minecraft/tags/TagKey.java +index 3899e39ef9c602cbe33c71e85af76e72d8938219..b385f7cf774a81d90319e7eb5838a52929c330fb 100644 +--- a/src/main/java/net/minecraft/tags/TagKey.java ++++ b/src/main/java/net/minecraft/tags/TagKey.java +@@ -31,7 +31,7 @@ public record TagKey(ResourceKey> registry, ResourceLoc + } + + public static TagKey create(ResourceKey> registry, ResourceLocation id) { +- return VALUES.intern(new TagKey<>(registry, id)); ++ return (TagKey) VALUES.intern(new TagKey<>(registry, id)); // Sakura - compile error + } + + public boolean isFor(ResourceKey> registryRef) { +@@ -39,7 +39,7 @@ public record TagKey(ResourceKey> registry, ResourceLoc + } + + public Optional> cast(ResourceKey> registryRef) { +- return this.isFor(registryRef) ? Optional.of(this) : Optional.empty(); ++ return this.isFor(registryRef) ? Optional.of((TagKey) this) : Optional.empty(); // Sakura - compile error + } + + @Override +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index 50a9f33aa31e9273c7c52d4bb2b02f0f884f7ba5..5fb7573022c5af775b2e737dcd05c53cd9ae39ec 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -61,7 +61,7 @@ public class ClassInstanceMultiMap extends AbstractCollection { + List list = this.byClass.computeIfAbsent(type, (typeClass) -> { + return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); + }); +- return Collections.unmodifiableCollection(list); ++ return (Collection) Collections.unmodifiableCollection(list); // Sakura - decompile fix + } + } + diff --git a/patches/server/0004-Sakura-Configuration-Files.patch b/patches/server/0004-Sakura-Configuration-Files.patch new file mode 100644 index 0000000..81c1d59 --- /dev/null +++ b/patches/server/0004-Sakura-Configuration-Files.patch @@ -0,0 +1,909 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "kfian294ma4@gmail.com" +Date: Sun, 5 Sep 2021 18:01:34 +0100 +Subject: [PATCH] Sakura Configuration Files + + +diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java +index 9ef6712c70fcd8912a79f3f61e351aac09572cf3..4cb0c8291833cd10d9704cdbe4f0332827c02ae2 100644 +--- a/src/main/java/io/papermc/paper/configuration/Configurations.java ++++ b/src/main/java/io/papermc/paper/configuration/Configurations.java +@@ -88,7 +88,7 @@ public abstract class Configurations { + }; + } + +- static CheckedFunction reloader(Class type, T instance) { ++ public static CheckedFunction reloader(Class type, T instance) { // Sakura - public + return node -> { + ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); + ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); +@@ -206,7 +206,7 @@ public abstract class Configurations { + .path(worldConfigFile) + .build(); + final ConfigurationNode worldNode = worldLoader.load(); +- if (newFile) { // set the version field if new file ++ if (newFile && this instanceof PaperConfigurations) { // Sakura - hack this into working // set the version field if new file + worldNode.node(Configuration.VERSION_FIELD).set(WorldConfiguration.CURRENT_VERSION); + } + this.applyWorldConfigTransformations(contextMap, worldNode); +diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +index 9e8b8de907654050c51400286af971caca87d6bd..8e173ad0adc71c295394e61bbabf11336bd10332 100644 +--- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java ++++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +@@ -449,7 +449,7 @@ public class PaperConfigurations extends Configurations> { ++public final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory> { // Sakura + + private final InnerClassInstanceSupplier instanceSupplier; + private final FieldDiscoverer.MutableInstanceFactory> fallback; + private final AnnotatedType targetType; + +- InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory> fallback, final AnnotatedType targetType) { ++ public InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory> fallback, final AnnotatedType targetType) { // Sakura + this.instanceSupplier = instanceSupplier; + this.fallback = fallback; + this.targetType = targetType; +diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java +index 8d8bc050441c02cf65dfcb6400978363d6b8ef10..5872095a811452037cc0772ba2a31bf9a795cc82 100644 +--- a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java ++++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java +@@ -19,7 +19,7 @@ import static io.leangen.geantyref.GenericTypeReflector.erase; + * {@link ConfigurationPart}. Only 1 instance of each {@link ConfigurationPart} should be present for each instance + * of the field discoverer this is used in. + */ +-final class InnerClassInstanceSupplier implements CheckedFunction, SerializationException> { ++public final class InnerClassInstanceSupplier implements CheckedFunction, SerializationException> { // Sakua - :< + + private final Map, Object> instanceMap = new HashMap<>(); + private final Map, Object> initialOverrides; +@@ -27,7 +27,7 @@ final class InnerClassInstanceSupplier implements CheckedFunction, Object> initialOverrides) { ++ public InnerClassInstanceSupplier(final Map, Object> initialOverrides) { // Sakura + this.initialOverrides = initialOverrides; + } + +diff --git a/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java b/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b5af05f7a4593eb44f36fff90d94e98d6999c7f +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java +@@ -0,0 +1,47 @@ ++package me.samsuik.sakura.command; ++ ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class BaseSubCommand extends Command { ++ ++ public BaseSubCommand(String name) { ++ super(name); ++ this.description = "Sakura Command " + name; ++ this.setPermission("bukkit.command." + name); ++ } ++ ++ public abstract void execute(CommandSender sender, String[] args); ++ ++ public void tabComplete(List list, String[] args) throws IllegalArgumentException {} ++ ++ @Override ++ @Deprecated ++ public final boolean execute(CommandSender sender, String label, String[] args) { ++ if (testPermission(sender)) { ++ execute(sender, args); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ @NotNull ++ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { ++ var completions = new ArrayList(0); ++ ++ if (testPermissionSilent(sender)) { ++ tabComplete(completions, args); ++ } ++ ++ return completions; ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommand.java b/src/main/java/me/samsuik/sakura/command/SakuraCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f4e75ba8f2c53a82cb40868f0e1ab77ffcc19e3e +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/SakuraCommand.java +@@ -0,0 +1,93 @@ ++package me.samsuik.sakura.command; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; ++import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++ ++import javax.annotation.Nullable; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++ ++@DefaultQualifier(NonNull.class) ++public class SakuraCommand extends Command { ++ ++ private static final Component INFORMATION_MESSAGE = MiniMessage.miniMessage().deserialize(""" ++ . ++ | This is the main command for Sakura. ++ | All exclusive commands are listed below.""" ++ ); ++ ++ private static final String COMMAND_MSG = "| * /"; ++ ++ public SakuraCommand(String name) { ++ super(name); ++ ++ this.description = ""; ++ this.usageMessage = "/sakura"; ++ this.setPermission("bukkit.command.sakura"); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (args.length > 0) { ++ var commands = new ArrayList<>(SakuraCommands.COMMANDS.values()); ++ ++ // This part is copied from the VersionCommand SubCommand in paper ++ @Nullable ++ var internalVersion = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); ++ if (internalVersion != null) { ++ commands.add(internalVersion); ++ } ++ ++ for (var base : commands) { ++ if (base.getName().equalsIgnoreCase(args[0])) { ++ return base.execute(sender, commandLabel, Arrays.copyOfRange(args, 1, args.length)); ++ } ++ } ++ } ++ ++ sendHelpMessage(sender); ++ return false; ++ } ++ ++ private void sendHelpMessage(CommandSender sender) { ++ sender.sendMessage(INFORMATION_MESSAGE); ++ ++ var uniqueCommands = SakuraCommands.COMMANDS.values() ++ .stream() ++ .filter(command -> command != this); ++ ++ uniqueCommands.forEach((command) -> { ++ sender.sendMessage(MiniMessage.miniMessage().deserialize(COMMAND_MSG, ++ Placeholder.unparsed("command", command.getName())) ++ ); ++ }); ++ ++ sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE)); ++ } ++ ++ @NotNull ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { ++ if (!testPermissionSilent(sender)) { ++ return Collections.emptyList(); ++ } ++ ++ return SakuraCommands.COMMANDS.values().stream() ++ .filter(command -> command != this) // ahem ++ .map(Command::getName) ++ .filter(name -> args.length <= 1 || name.startsWith(args[args.length - 1])) ++ .toList(); ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2651cac1dcf85fb67fe981b97efee4e56431de2 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +@@ -0,0 +1,26 @@ ++package me.samsuik.sakura.command; ++ ++import io.papermc.paper.command.PaperPluginsCommand; ++import me.samsuik.sakura.command.subcommands.ConfigCommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public class SakuraCommands { ++ ++ static final Map COMMANDS = new HashMap<>(); ++ static { ++ COMMANDS.put("sakura", new SakuraCommand("sakura")); ++ COMMANDS.put("config", new ConfigCommand("config")); ++ } ++ ++ public static void registerCommands(final MinecraftServer server) { ++ COMMANDS.forEach((s, command) -> { ++ server.server.getCommandMap().register(s, "sakura", command); ++ }); ++ server.server.getCommandMap().register("bukkit", new PaperPluginsCommand()); ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b5f50c7447d1c1732a745bae54c4fcd4f45da46 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java +@@ -0,0 +1,45 @@ ++package me.samsuik.sakura.command.subcommands; ++ ++import me.samsuik.sakura.command.BaseSubCommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.Collections; ++import java.util.List; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public class ConfigCommand extends BaseSubCommand { ++ ++ public ConfigCommand(String name) { ++ super(name); ++ this.description = "Command for reloading the sakura configuration file"; ++ } ++ ++ @Override ++ public void execute(CommandSender sender, String[] args) { ++ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); ++ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); ++ ++ MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); ++ server.sakuraConfigurations.reloadConfigs(server); ++ server.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN)); ++ } ++ ++ @NotNull ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java b/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc6db446dca3024b8f6d3f24499242775dd02219 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java +@@ -0,0 +1,42 @@ ++package me.samsuik.sakura.configuration; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.configuration.Configuration; ++import io.papermc.paper.configuration.ConfigurationPart; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.objectmapping.meta.Setting; ++ ++@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) ++public class GlobalConfiguration extends ConfigurationPart { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ static final int CURRENT_VERSION = 1;// (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs ++ ++ private static GlobalConfiguration instance; ++ public static GlobalConfiguration get() { ++ return instance; ++ } ++ ++ static void set(GlobalConfiguration instance) { ++ GlobalConfiguration.instance = instance; ++ } ++ ++ @Setting(Configuration.VERSION_FIELD) ++ public int version = CURRENT_VERSION; ++ ++ public Fps fps; ++ public class Fps extends ConfigurationPart { ++ public String message = "(S) "; ++ public String material = "pink_stained_glass_pane"; ++ } ++ ++ public Cannons cannons; ++ public class Cannons extends ConfigurationPart { ++ public Explosion explosion = new Explosion(); ++ ++ public class Explosion extends ConfigurationPart { ++ public boolean reducedSearchRays; ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java b/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java +new file mode 100644 +index 0000000000000000000000000000000000000000..37f41089a2fb7ad11ceda07cf5de0fc8b34b8db2 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java +@@ -0,0 +1,220 @@ ++package me.samsuik.sakura.configuration; ++ ++import com.google.common.collect.Table; ++import com.mojang.logging.LogUtils; ++import io.leangen.geantyref.TypeToken; ++import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.Configurations; ++import io.papermc.paper.configuration.NestedSetting; ++import io.papermc.paper.configuration.PaperConfigurations; ++import io.papermc.paper.configuration.serializer.*; ++import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer; ++import io.papermc.paper.configuration.serializer.collections.MapSerializer; ++import io.papermc.paper.configuration.serializer.collections.TableSerializer; ++import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; ++import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; ++import io.papermc.paper.configuration.type.BooleanOrDefault; ++import io.papermc.paper.configuration.type.Duration; ++import io.papermc.paper.configuration.type.DurationOrDisabled; ++import io.papermc.paper.configuration.type.EngineMode; ++import io.papermc.paper.configuration.type.number.DoubleOr; ++import io.papermc.paper.configuration.type.number.IntOr; ++import it.unimi.dsi.fastutil.objects.Reference2IntMap; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2LongMap; ++import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import me.samsuik.sakura.configuration.mapping.InnerClassFieldDiscoverer; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.ConfigurationOptions; ++import org.spongepowered.configurate.objectmapping.ObjectMapper; ++import org.spongepowered.configurate.yaml.YamlConfigurationLoader; ++ ++import java.io.IOException; ++import java.lang.reflect.Type; ++import java.nio.file.Path; ++import java.util.function.Function; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++@SuppressWarnings("Convert2Diamond") ++public class SakuraConfigurations extends Configurations { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ static final String GLOBAL_CONFIG_FILE_NAME = "sakura-global.yml"; ++ static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "sakura-world-defaults.yml"; ++ static final String WORLD_CONFIG_FILE_NAME = "sakura-world.yml"; ++ public static final String CONFIG_DIR = "config"; ++ ++ private static final String GLOBAL_HEADER = String.format(""" ++ This is the global configuration file for Sakura. ++ As you can see, there's a lot to configure. Some options may impact gameplay, so use ++ with caution, and make sure you know what each option does before configuring. ++ ++ The world configuration options have been moved inside ++ their respective world folder. The files are named %s""", WORLD_CONFIG_FILE_NAME); ++ ++ private static final String WORLD_DEFAULTS_HEADER = """ ++ This is the world defaults configuration file for Sakura. ++ As you can see, there's a lot to configure. Some options may impact gameplay, so use ++ with caution, and make sure you know what each option does before configuring. ++ ++ Configuration options here apply to all worlds, unless you specify overrides inside ++ the world-specific config file inside each world folder."""; ++ ++ private static final Function WORLD_HEADER = map -> String.format(""" ++ This is a world configuration file for Sakura. ++ This file may start empty but can be filled with settings to override ones in the %s/%s ++ ++ World: %s (%s)""", ++ SakuraConfigurations.CONFIG_DIR, ++ SakuraConfigurations.WORLD_DEFAULTS_CONFIG_FILE_NAME, ++ map.require(WORLD_NAME), ++ map.require(WORLD_KEY) ++ ); ++ ++ public SakuraConfigurations(final Path globalFolder) { ++ super(globalFolder, GlobalConfiguration.class, WorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createLoaderBuilder() { ++ return super.createLoaderBuilder() ++ .defaultOptions(SakuraConfigurations::defaultOptions); ++ } ++ ++ private static ConfigurationOptions defaultOptions(ConfigurationOptions options) { ++ return options.serializers(builder -> builder ++ .register(MapSerializer.TYPE, new MapSerializer(false)) ++ .register(new EnumValueSerializer()) ++ .register(new ComponentSerializer()) ++ ); ++ } ++ ++ @Override ++ protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() { ++ return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder()); ++ } ++ ++ private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) { ++ return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig()); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() { ++ return super.createGlobalLoaderBuilder() ++ .defaultOptions(SakuraConfigurations::defaultGlobalOptions); ++ } ++ ++ private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { ++ return options ++ .header(GLOBAL_HEADER) ++ .serializers(builder -> builder ++ .register(new PacketClassSerializer()) ++ .register(IntOr.Default.SERIALIZER) ++ ); ++ } ++ ++ @Override ++ public GlobalConfiguration initializeGlobalConfiguration() throws ConfigurateException { ++ GlobalConfiguration configuration = super.initializeGlobalConfiguration(); ++ GlobalConfiguration.set(configuration); ++ return configuration; ++ } ++ ++ @Override ++ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { ++ return super.createWorldObjectMapperFactoryBuilder(contextMap) ++ .addNodeResolver(new NestedSetting.Factory()) ++ .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap))); ++ } ++ ++ private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) { ++ return new WorldConfiguration( ++ contextMap.require(Configurations.WORLD_KEY) ++ ); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { ++ return super.createWorldConfigLoaderBuilder(contextMap) ++ .defaultOptions(options -> options ++ .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap)) ++ .serializers(serializers -> serializers ++ .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2IntOpenHashMap::new, Integer.TYPE)) ++ .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2LongOpenHashMap::new, Long.TYPE)) ++ .register(new TypeToken>() {}, new TableSerializer()) ++ .register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer()) ++ .register(IntOr.Default.SERIALIZER) ++ .register(IntOr.Disabled.SERIALIZER) ++ .register(DoubleOr.Default.SERIALIZER) ++ .register(BooleanOrDefault.SERIALIZER) ++ .register(Duration.SERIALIZER) ++ .register(DurationOrDisabled.SERIALIZER) ++ .register(EngineMode.SERIALIZER) ++ .register(NbtPathSerializer.SERIALIZER) ++ .register(new RegistryValueSerializer<>(new TypeToken>() {}, Registries.ENTITY_TYPE, true)) ++ .register(new RegistryValueSerializer<>(Item.class, Registries.ITEM, true)) ++ .register(new RegistryHolderSerializer<>(new TypeToken>() {}, Registries.CONFIGURED_FEATURE, false)) ++ .register(new RegistryHolderSerializer<>(Item.class, Registries.ITEM, true)) ++ ) ++ ); ++ } ++ ++ @Override ++ public WorldConfiguration createWorldConfig(final ContextMap contextMap) { ++ final String levelName = contextMap.require(WORLD_NAME); ++ try { ++ return super.createWorldConfig(contextMap); ++ } catch (IOException exception) { ++ throw new RuntimeException("Could not create world config for " + levelName, exception); ++ } ++ } ++ ++ @Override ++ protected boolean isConfigType(final Type type) { ++ return ConfigurationPart.class.isAssignableFrom(erase(type)); ++ } ++ ++ public void reloadConfigs(MinecraftServer server) { ++ try { ++ this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get())); ++ this.initializeWorldDefaultsConfiguration(); ++ for (ServerLevel level : server.getAllLevels()) { ++ this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.sakuraConfig())); ++ } ++ } catch (Exception ex) { ++ throw new RuntimeException("Could not reload paper configuration files", ex); ++ } ++ } ++ ++ private static ContextMap createWorldContextMap(ServerLevel level) { ++ return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location()); ++ } ++ ++ public static ContextMap createWorldContextMap(Path dir, String levelName, ResourceLocation worldKey) { ++ return ContextMap.builder() ++ .put(WORLD_DIRECTORY, dir) ++ .put(WORLD_NAME, levelName) ++ .put(WORLD_KEY, worldKey) ++ .build(); ++ } ++ ++ public static SakuraConfigurations setup(final Path configDir) { ++ try { ++ PaperConfigurations.createDirectoriesSymlinkAware(configDir); ++ return new SakuraConfigurations(configDir); ++ } catch (final IOException ex) { ++ throw new RuntimeException("Could not setup PaperConfigurations", ex); ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2617fb071a48f1b8b328b12041feda293e2824c +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java +@@ -0,0 +1,63 @@ ++package me.samsuik.sakura.configuration; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.configuration.Configuration; ++import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.PaperConfigurations; ++import me.samsuik.sakura.entity.merge.MergeLevel; ++import net.minecraft.resources.ResourceLocation; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.objectmapping.meta.Setting; ++ ++@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) ++public class WorldConfiguration extends ConfigurationPart { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ static final int CURRENT_VERSION = 1; // (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs ++ ++ private transient final ResourceLocation worldKey; ++ WorldConfiguration(ResourceLocation worldKey) { ++ this.worldKey = worldKey; ++ } ++ ++ public boolean isDefault() { ++ return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY); ++ } ++ ++ @Setting(Configuration.VERSION_FIELD) ++ public int version = CURRENT_VERSION; ++ ++ public Cannons cannons; ++ public class Cannons extends ConfigurationPart { ++ public MergeLevel mergeLevel = MergeLevel.STRICT; ++ ++ public Tnt tnt = new Tnt(); ++ public class Tnt extends ConfigurationPart { ++ public boolean loadsChunks; ++ public boolean forcePositionUpdates; ++ } ++ ++ public Sand sand = new Sand(); ++ public class Sand extends ConfigurationPart { ++ public boolean loadsChunks; ++ } ++ ++ public Explosion explosion = new Explosion(); ++ public class Explosion extends ConfigurationPart { ++ public boolean optimiseProtectedRegions = true; ++ public boolean avoidRedundantBlockSearches = true; ++ } ++ ++ public Mechanics mechanics = new Mechanics(); ++ public class Mechanics extends ConfigurationPart { ++ public TNTSpread tntSpread = TNTSpread.ALL; ++ public boolean tntFlowsInWater = true; ++ public boolean fallingBlockParity = false; ++ ++ public enum TNTSpread { ++ ALL, Y, NONE; ++ } ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/configuration/mapping/InnerClassFieldDiscoverer.java b/src/main/java/me/samsuik/sakura/configuration/mapping/InnerClassFieldDiscoverer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..96080117b5ac9dea6b9eeb7489dc0c87d2c5f8a1 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/configuration/mapping/InnerClassFieldDiscoverer.java +@@ -0,0 +1,55 @@ ++package me.samsuik.sakura.configuration.mapping; ++ ++import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.mapping.InnerClassInstanceFactory; ++import io.papermc.paper.configuration.mapping.InnerClassInstanceSupplier; ++import me.samsuik.sakura.configuration.WorldConfiguration; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.spongepowered.configurate.objectmapping.FieldDiscoverer; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++import java.lang.reflect.AnnotatedType; ++import java.lang.reflect.Field; ++import java.util.Collections; ++import java.util.Map; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++public final class InnerClassFieldDiscoverer implements FieldDiscoverer> { ++ ++ private final InnerClassInstanceSupplier instanceSupplier; ++ private final FieldDiscoverer> delegate; ++ ++ @SuppressWarnings("unchecked") ++ public InnerClassFieldDiscoverer(final Map, Object> initialOverrides) { ++ this.instanceSupplier = new InnerClassInstanceSupplier(initialOverrides); ++ this.delegate = (FieldDiscoverer>) FieldDiscoverer.object(this.instanceSupplier); ++ } ++ ++ @Override ++ public @Nullable InstanceFactory> discover(final AnnotatedType target, final FieldCollector, V> collector) throws SerializationException { ++ final Class clazz = erase(target.getType()); ++ if (ConfigurationPart.class.isAssignableFrom(clazz)) { ++ final @Nullable InstanceFactory> instanceFactoryDelegate = this.delegate.discover(target, (name, type, annotations, deserializer, serializer) -> { ++ if (!erase(type.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes ++ collector.accept(name, type, annotations, deserializer, serializer); ++ } ++ }); ++ if (instanceFactoryDelegate instanceof MutableInstanceFactory> mutableInstanceFactoryDelegate) { ++ return new InnerClassInstanceFactory(this.instanceSupplier, mutableInstanceFactoryDelegate, target); ++ } ++ } ++ return null; ++ } ++ ++ public static FieldDiscoverer worldConfig(WorldConfiguration worldConfiguration) { ++ final Map, Object> overrides = Map.of( ++ WorldConfiguration.class, worldConfiguration ++ ); ++ return new InnerClassFieldDiscoverer(overrides); ++ } ++ ++ public static FieldDiscoverer globalConfig() { ++ return new InnerClassFieldDiscoverer(Collections.emptyMap()); ++ } ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/VariousHelpers.java b/src/main/java/me/samsuik/sakura/utils/VariousHelpers.java +new file mode 100644 +index 0000000000000000000000000000000000000000..141b15887ca075fef5d36ff15125b3e071049917 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/VariousHelpers.java +@@ -0,0 +1,61 @@ ++package me.samsuik.sakura.utils; ++ ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.world.entity.EntityType; ++ ++import java.util.List; ++import java.util.regex.Pattern; ++import org.apache.commons.lang.StringUtils; ++ ++public class VariousHelpers { ++ ++ public static List> getEntityTypes(String from) { ++ Pattern pattern = VariousHelpers.wildcardPattern(from); ++ ++ return BuiltInRegistries.ENTITY_TYPE.stream() ++ .filter((type) -> pattern.matcher(type.getDescriptionId()).find() ++ || pattern.matcher(type.getCategory().getName()).find()) ++ .toList(); ++ } ++ ++ public static Pattern wildcardPattern(String string) { ++ String included = string.replaceAll("\\*{2}", ".*?"); ++ return Pattern.compile("^(%s)$".formatted(included)); ++ } ++ ++ public static String capitalise(String string) { ++ StringBuilder builder = new StringBuilder(string.length()); ++ boolean capitalise = true; ++ ++ for (char character : string.toCharArray()) { ++ if (!Character.isAlphabetic(character)) { ++ capitalise = true; ++ } else if (capitalise) { ++ character = Character.toUpperCase(character); ++ capitalise = false; ++ } ++ ++ builder.append(character); ++ } ++ ++ return builder.toString(); ++ } ++ ++ public static int sequential(String of, String provided) { ++ for (int i = 0; i < of.length() && i < provided.length(); ++i) { ++ if (provided.charAt(i) != of.charAt(i)) { ++ return i; ++ } ++ } ++ ++ return Math.min(of.length(), provided.length()); ++ } ++ ++ public static double similarity(String a, String b) { ++ double distance = StringUtils.getLevenshteinDistance(a, b); ++ int highest = Math.max(a.length(), b.length()); ++ return 1 - (distance / highest); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 188da766381393afe643de28dccaa6aa7c9130a0..2a3af1a071766c30567cdf1681059cdce967afff 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -300,6 +300,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor ++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), () -> minecraftserver.sakuraConfigurations.createWorldConfig(me.samsuik.sakura.configuration.SakuraConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location())), executor); // Sakura // Paper - Async-Anti-Xray - Pass executor + this.pvpMode = minecraftserver.isPvpAllowed(); + this.convertable = convertable_conversionsession; + this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index ea8a0961190e9aafda4fed6fecd85097c141040a..8006ec1eece1e1c5c0b18bc2b5190bbb43e9d4c3 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -174,6 +174,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.paperConfig; + } + // Paper end ++ // Sakura start ++ private final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig; ++ public me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig() { ++ return this.sakuraConfig; ++ } ++ // Sakura end + + public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final co.aikar.timings.WorldTimingsHandler timings; // Paper +@@ -210,9 +216,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public abstract ResourceKey getTypeKey(); + +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper ++ this.sakuraConfig = sakuraWorldConfigCreator.get(); // Sakura + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 28a1e39ed1b7fd10f4c01e076258cb8689b4e5bf..feacca7ca9838a3811d09c617fa609fdef1e6da7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1039,6 +1039,7 @@ public final class CraftServer implements Server { + + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); ++ this.console.sakuraConfigurations.reloadConfigs(this.console); // Sakura - missing comment above + for (ServerLevel world : this.console.getAllLevels()) { + // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty + world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) +@@ -1069,6 +1070,7 @@ public final class CraftServer implements Server { + this.reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot + io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper ++ me.samsuik.sakura.command.SakuraCommands.registerCommands(this.console); // Sakura + this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); + this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index e4cacb17f56c618bef19e1165c07aac86af61150..2c327be8efba101bb76482967a7171d01ecce4bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -173,6 +173,14 @@ public class Main { + .describedAs("Jar file"); + // Paper end + ++ // Sakura start ++ acceptsAll(asList("sakura-dir", "sakura-settings-directory"), "Directory for Sakura settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File(me.samsuik.sakura.configuration.SakuraConfigurations.CONFIG_DIR)) ++ .describedAs("Config directory"); ++ // Sakura end ++ + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() diff --git a/patches/server/0005-Visibility-API-and-Command.patch b/patches/server/0005-Visibility-API-and-Command.patch new file mode 100644 index 0000000..f941cff --- /dev/null +++ b/patches/server/0005-Visibility-API-and-Command.patch @@ -0,0 +1,619 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Tue, 21 Sep 2021 23:54:25 +0100 +Subject: [PATCH] Visibility API and Command + + +diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +index c2651cac1dcf85fb67fe981b97efee4e56431de2..d7d0c49cc5d576c594dee16ddba037cd147e11fa 100644 +--- a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java ++++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +@@ -2,6 +2,9 @@ package me.samsuik.sakura.command; + + import io.papermc.paper.command.PaperPluginsCommand; + import me.samsuik.sakura.command.subcommands.ConfigCommand; ++import me.samsuik.sakura.command.subcommands.FPSCommand; ++import me.samsuik.sakura.command.subcommands.VisualCommand; ++import me.samsuik.sakura.player.visibility.Visibility; + import net.minecraft.server.MinecraftServer; + import org.bukkit.command.Command; + +@@ -14,6 +17,10 @@ public class SakuraCommands { + static { + COMMANDS.put("sakura", new SakuraCommand("sakura")); + COMMANDS.put("config", new ConfigCommand("config")); ++ COMMANDS.put("fps", new FPSCommand("fps")); ++ COMMANDS.put("tntvisibility", new VisualCommand(Visibility.Setting.TNT_VISIBILITY, "tnttoggle")); ++ COMMANDS.put("sandvisibility", new VisualCommand(Visibility.Setting.SAND_VISIBILITY, "sandtoggle")); ++ COMMANDS.put("minimal", new VisualCommand(Visibility.Setting.MINIMAL, "minimaltnt", "tntlag")); + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..23a12e9a8697a39951f9e8d841a119c14e06e02e +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java +@@ -0,0 +1,26 @@ ++package me.samsuik.sakura.command.subcommands; ++ ++import me.samsuik.sakura.command.BaseSubCommand; ++import me.samsuik.sakura.player.visibility.ui.VisibilityGUI; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class FPSCommand extends BaseSubCommand { ++ ++ private final VisibilityGUI VISIBILITY_GUI = new VisibilityGUI(); ++ ++ public FPSCommand(String name) { ++ super(name); ++ } ++ ++ @Override ++ public void execute(CommandSender sender, String[] args) { ++ if (sender instanceof Player player) { ++ VISIBILITY_GUI.showTo(player); ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6027e4741e2de7c6d3bd7b094c196a212e34e2f5 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java +@@ -0,0 +1,45 @@ ++package me.samsuik.sakura.command.subcommands; ++ ++import me.samsuik.sakura.command.BaseSubCommand; ++import me.samsuik.sakura.configuration.GlobalConfiguration; ++import me.samsuik.sakura.player.visibility.Visibility; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import java.util.Arrays; ++ ++@DefaultQualifier(NonNull.class) ++public class VisualCommand extends BaseSubCommand { ++ ++ private final Visibility.Setting type; ++ ++ public VisualCommand(Visibility.Setting type, String... aliases) { ++ super(type.basicName()); ++ this.setAliases(Arrays.asList(aliases)); ++ this.type = type; ++ } ++ ++ @Override ++ public void execute(CommandSender sender, String[] args) { ++ if (!(sender instanceof Player player)) { ++ return; ++ } ++ ++ var visibility = player.getVisibility(); ++ ++ // Toggle clicked setting visibility ++ visibility.toggle(type); ++ ++ // Send message to player ++ var state = visibility.isEnabled(type) ? "Enabled" : "Disabled"; ++ player.sendMessage(MiniMessage.miniMessage().deserialize(GlobalConfiguration.get().fps.message, ++ Placeholder.unparsed("name", type.friendlyName()), ++ Placeholder.unparsed("state", state)) ++ ); ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/player/visibility/ui/VisibilityGUI.java b/src/main/java/me/samsuik/sakura/player/visibility/ui/VisibilityGUI.java +new file mode 100644 +index 0000000000000000000000000000000000000000..725dd617641f7344d18d100dfa487430080e1d93 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/player/visibility/ui/VisibilityGUI.java +@@ -0,0 +1,108 @@ ++package me.samsuik.sakura.player.visibility.ui; ++ ++import me.samsuik.sakura.configuration.GlobalConfiguration; ++import me.samsuik.sakura.player.gui.ItemIcon; ++import me.samsuik.sakura.player.gui.PlayerGUI; ++import me.samsuik.sakura.player.visibility.Visibility; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; ++import org.bukkit.Bukkit; ++import org.bukkit.Material; ++import org.bukkit.entity.Player; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.ItemStack; ++ ++import java.util.function.BiFunction; ++ ++public class VisibilityGUI extends PlayerGUI { ++ ++ private static final BiFunction CREATE_INVENTORY = (player, holder) -> { ++ var inventory = Bukkit.createInventory(holder, 45, Component.text("FPS GUI")); ++ ++ for (var i = 0; i < inventory.getSize(); ++i) { ++ var column = i % 9; ++ var row = (i + 1) / 9; ++ ++ var background = column > 0 && column < 8 ? (row > 0 && row < 4 || column > 1 && column < 7) ++ ? Material.matchMaterial(GlobalConfiguration.get().fps.material) ++ : Material.WHITE_STAINED_GLASS_PANE ++ : Material.BLACK_STAINED_GLASS_PANE; ++ ++ var itemstack = new ItemStack(background); ++ var meta = itemstack.getItemMeta(); ++ meta.displayName(Component.space()); ++ itemstack.setItemMeta(meta); ++ ++ inventory.setItem(i, itemstack); ++ } ++ ++ return inventory; ++ }; ++ ++ public VisibilityGUI() { ++ super(CREATE_INVENTORY); ++ } ++ ++ @Override ++ protected void register() { ++ registerFPSIcon(Visibility.Setting.TNT_VISIBILITY, Material.TNT, NamedTextColor.RED, 12); ++ registerFPSIcon(Visibility.Setting.SAND_VISIBILITY, Material.SAND, NamedTextColor.YELLOW, 14); ++ registerFPSIcon(Visibility.Setting.MINIMAL, Material.NETHER_BRICK_SLAB, NamedTextColor.GOLD, 13); ++ registerFPSIcon(Visibility.Setting.SPAWNERS, Material.SPAWNER, NamedTextColor.DARK_GRAY, 20); ++ registerFPSIcon(Visibility.Setting.FLASHING_TNT, Material.REDSTONE_LAMP, NamedTextColor.RED, 22); ++ registerFPSIcon(Visibility.Setting.EXPLOSIONS, Material.COBWEB, NamedTextColor.WHITE, 24); ++ registerFPSIcon(Visibility.Setting.PISTONS, Material.PISTON, NamedTextColor.GOLD, 30); ++ registerFPSIcon(Visibility.Setting.REDSTONE, Material.REDSTONE, NamedTextColor.DARK_RED, 31); ++ registerFPSIcon(Visibility.Setting.ENCHANTMENT_GLINT, Material.ENCHANTED_BOOK, NamedTextColor.DARK_PURPLE, 32); ++ ++ registerIcon(new ItemIcon( ++ (player) -> new ItemStack(Material.EMERALD_BLOCK), ++ (player) -> player.getVisibility().toggleAll(), ++ 26 ++ )); ++ } ++ ++ private void registerFPSIcon(Visibility.Setting setting, Material material, TextColor colour, int slot) { ++ registerIcon(new ItemIcon((player) -> { ++ var visibility = player.getVisibility(); ++ ++ // Get the current state as a string ++ var state = visibility.isEnabled(setting) ++ ? "Enabled" : "Disabled"; ++ ++ // Friendly name as a component ++ var title = Component.text(setting.friendlyName()).color(colour); ++ var itemstack = new ItemStack(material); ++ var meta = itemstack.getItemMeta(); ++ ++ // Display names are italic by default ++ title = title.decoration(TextDecoration.ITALIC, false); ++ ++ // Set the display name ++ meta.displayName(title.append(Component.space()) ++ .append(Component.text(state).color(NamedTextColor.GRAY))); ++ ++ itemstack.setItemMeta(meta); ++ return itemstack; ++ }, (player) -> { ++ var visibility = player.getVisibility(); ++ ++ // Toggle clicked setting visibility ++ visibility.toggle(setting); ++ ++ // Send message to player ++ var state = visibility.isEnabled(setting) ? "Enabled" : "Disabled"; ++ player.sendMessage(MiniMessage.miniMessage().deserialize(GlobalConfiguration.get().fps.message, ++ Placeholder.unparsed("name", setting.friendlyName()), ++ Placeholder.unparsed("state", state)) ++ ); ++ }, ++ slot ++ )); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +index e3f355c85eb7cc8c1683e3009502c10a5ed32daa..349e56d5caad3fc38e83eac6ffff83e2fb30eaa7 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +@@ -16,7 +16,7 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet public + + public ClientboundSectionBlocksUpdatePacket(SectionPos sectionPos, ShortSet positions, LevelChunkSection section) { + this.sectionPos = sectionPos; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 2a3af1a071766c30567cdf1681059cdce967afff..4535beab09310915b0239b01c1b807f1ca25d959 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1575,6 +1575,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop createEntityPacket(Entity entity, ServerPlayer entityplayer) { ++ if (entity.isPrimedTNT && entityplayer.visibility.isToggled(Visibility.Setting.FLASHING_TNT)) { ++ return new ClientboundAddEntityPacket(entity.getId(), entity.getUUID(), ++ entity.getX(), entity.getY(), entity.getZ(), 0, 0, EntityType.FALLING_BLOCK, ++ Block.getId(Blocks.TNT.defaultBlockState()), entity.getDeltaMovement(), entity.getYHeadRot()); ++ } else { ++ return entity.getAddEntityPacket(); ++ } ++ } ++ // Sakura end ++ + public void sendPairingData(ServerPlayer player, Consumer> sender) { + if (this.entity.isRemoved()) { + // CraftBukkit start - Remove useless error spam, just return +@@ -292,12 +311,19 @@ public class ServerEntity { + // CraftBukkit end + } + +- Packet packet = this.entity.getAddEntityPacket(); ++ Packet packet = this.createEntityPacket(this.entity, player); // Sakura - visibility api + + this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F); + sender.accept(packet); + if (this.trackedDataValues != null) { +- sender.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); ++ // Sakura start - visibility api ++ if (this.entity.isPrimedTNT && player.visibility.isToggled(Visibility.Setting.FLASHING_TNT)) { ++ // Could modifying this break something elsewhere? ++ trackedDataValues.removeIf((data) -> data.id() == 8); ++ } ++ ++ sender.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), trackedDataValues)); ++ // Sakura end + } + + boolean flag = this.trackDelta; +@@ -367,6 +393,32 @@ public class ServerEntity { + } + + } ++ ++ // Sakura start - visibility api ++ private void broadcastEntityData(List> packedValues) { ++ Packet packet0 = new ClientboundSetEntityDataPacket(this.entity.getId(), packedValues); ++ Packet packet1 = null; ++ ++ if (this.entity.isPrimedTNT) { ++ var copyOfDirtyItems = Lists.newArrayList(packedValues); ++ copyOfDirtyItems.removeIf((data) -> data.id() == 8); ++ ++ if (!copyOfDirtyItems.isEmpty()) { ++ packet1 = new ClientboundSetEntityDataPacket(this.entity.getId(), copyOfDirtyItems); ++ } ++ } ++ ++ for (var connection : trackedPlayers) { ++ var player = connection.getPlayer(); ++ ++ if (!this.entity.isPrimedTNT || !player.visibility.isToggled(Visibility.Setting.FLASHING_TNT)) { ++ connection.send(packet0); ++ } else if (packet1 != null) { ++ connection.send(packet1); ++ } ++ } ++ } ++ // Sakura end + + private void sendDirtyEntityData() { + SynchedEntityData datawatcher = this.entity.getEntityData(); +@@ -374,7 +426,7 @@ public class ServerEntity { + + if (list != null) { + this.trackedDataValues = datawatcher.getNonDefaultValues(); +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); ++ this.broadcastEntityData(list); // Sakura - visibility api + } + + if (this.entity instanceof LivingEntity) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 80c571f73057cfeb39e550aeabd4647f323de9c1..a9cd388c7ebb377511f59398b8f31a04d7abe493 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1931,7 +1931,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (entityplayer.distanceToSqr(x, y, z) < 4096.0D) { ++ if (entityplayer.distanceToSqr(x, y, z) < 4096.0D && !entityplayer.visibility.isToggled(me.samsuik.sakura.player.visibility.Visibility.Setting.EXPLOSIONS)) { // Sakura - visibility api + entityplayer.connection.send(new ClientboundExplodePacket(x, y, z, power, explosion.getToBlow(), (Vec3) explosion.getHitPlayers().get(entityplayer))); + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d4aec99cac3f83d764e21946cc904c00e084704e..eef39746b355d72376651c3a6ba2f8101849141a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -254,6 +254,7 @@ public class ServerPlayer extends Player { + public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper + // Paper end - mob spawning rework + public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff ++ public final me.samsuik.sakura.player.visibility.Visibility visibility = new me.samsuik.sakura.player.visibility.Visibility(); // Sakura - visiblity api + + // CraftBukkit start + public String displayName; +@@ -559,6 +560,15 @@ public class ServerPlayer extends Player { + this.respawnDimension = (ResourceKey) dataresult1.resultOrPartial(logger1::error).orElse(Level.OVERWORLD); + } + } ++ // Sakura start - visibility api ++ CompoundTag tag = nbt.getCompound("Sakura.Visuals"); ++ ++ for (me.samsuik.sakura.player.visibility.Visibility.Setting setting : me.samsuik.sakura.player.visibility.Visibility.Setting.values()) { ++ if (tag.getBoolean(setting.name())) { ++ visibility.toggle(setting); ++ } ++ } ++ // Sakura end + + } + +@@ -625,6 +635,13 @@ public class ServerPlayer extends Player { + }); + } + this.getBukkitEntity().setExtraData(nbt); // CraftBukkit ++ // Sakura start - visibility api ++ CompoundTag tag = new CompoundTag(); ++ for (me.samsuik.sakura.player.visibility.Visibility.Setting setting : me.samsuik.sakura.player.visibility.Visibility.Setting.values()) { ++ tag.putBoolean(setting.name(), visibility.isToggled(setting)); ++ } ++ nbt.put("Sakura.Visuals", tag); ++ // Sakura end + + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 231150bac0ae61e9722c2cdfd70d6f7d254681e4..a091a2c7cb755607f7be30eec4844b0571e8b7f8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -40,6 +40,23 @@ import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.PlayerKickEvent; + import org.bukkit.event.player.PlayerResourcePackStatusEvent; + // CraftBukkit end ++// Sakura start ++import com.mojang.datafixers.util.Pair; ++import me.samsuik.sakura.player.visibility.Visibility.Setting; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; ++import net.minecraft.network.protocol.game.ClientboundBlockEventPacket; ++import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityTagOutput; ++import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; ++import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; ++import net.minecraft.world.level.block.DiodeBlock; ++import net.minecraft.world.level.block.entity.BlockEntityType; ++import net.minecraft.world.level.block.RedStoneWireBlock; ++import net.minecraft.world.level.block.piston.PistonBaseBlock; ++import net.minecraft.world.level.block.piston.PistonHeadBlock; ++// Sakura end + + public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener { + +@@ -242,6 +259,61 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) { + ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet; + this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld()); ++ // Sakura start - visibility api ++ } else if (!player.visibility.isModified()) { ++ // Skip players that haven't modified their settings ++ } else if (packet instanceof ClientboundSetEquipmentPacket equipment ++ && player.visibility.isToggled(Setting.ENCHANTMENT_GLINT)) { ++ var slots = new java.util.ArrayList>(); ++ ++ for (int i = 0; i < equipment.getSlots().size(); i++) { ++ var pair = equipment.getSlots().get(i); ++ var itemstack = pair.getSecond(); ++ ++ if (itemstack.isEnchanted()) { ++ var copy = itemstack.copy(); ++ copy.getTag().remove("ench"); ++ itemstack = copy; ++ } ++ ++ slots.add(new Pair<>(pair.getFirst(), itemstack)); ++ } ++ ++ packet = new ClientboundSetEquipmentPacket(equipment.getEntity(), slots); ++ } else if (packet instanceof ClientboundBlockEntityDataPacket blockdata ++ && player.visibility.isToggled(Setting.SPAWNERS) ++ && player.level().getBlockIfLoaded(blockdata.getPos()) == Blocks.SPAWNER) { ++ packet = new ClientboundBlockUpdatePacket(blockdata.getPos(), Blocks.BLACK_STAINED_GLASS.defaultBlockState()); ++ } else if (packet instanceof ClientboundBlockUpdatePacket updatePacket) { ++ if (player.visibility.isToggled(Setting.SPAWNERS) && updatePacket.blockState.getBlock() == Blocks.SPAWNER) { ++ packet = new ClientboundBlockUpdatePacket(updatePacket.getPos(), Blocks.BLACK_STAINED_GLASS.defaultBlockState()); ++ } else if (player.visibility.isToggled(Setting.REDSTONE) ++ && (updatePacket.blockState.getBlock() instanceof DiodeBlock ++ || updatePacket.blockState.getBlock() instanceof RedStoneWireBlock)) { ++ return; ++ } else if (player.visibility.isToggled(Setting.PISTONS) ++ && (updatePacket.blockState.getBlock() instanceof PistonBaseBlock ++ || updatePacket.blockState.getBlock() instanceof PistonHeadBlock)) { ++ return; ++ } ++ } else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionPacket) { ++ for (var state : sectionPacket.states) { ++ if (player.visibility.isToggled(Setting.REDSTONE) ++ && (state.getBlock() instanceof DiodeBlock ++ || state.getBlock() instanceof RedStoneWireBlock)) { ++ return; ++ } else if (player.visibility.isToggled(Setting.PISTONS) ++ && (state.getBlock() instanceof PistonBaseBlock ++ || state.getBlock() instanceof PistonHeadBlock)) { ++ return; ++ } ++ } ++ } else if (packet instanceof ClientboundBlockEventPacket blockevent ++ && player.visibility.isToggled(Setting.PISTONS) ++ && (blockevent.getBlock() instanceof PistonBaseBlock ++ || blockevent.getBlock() instanceof PistonHeadBlock)) { ++ return; ++ // Sakura end + } + // CraftBukkit end + boolean flag = !this.suspendFlushingOnServerThread || !this.server.isSameThread(); +@@ -252,8 +324,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + CrashReport crashreport = CrashReport.forThrowable(throwable, "Sending packet"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Packet being sent"); + ++ // Sakura start - this has to be effectively final as we're modifying the packet above ++ var packetFinal = packet; + crashreportsystemdetails.setDetail("Packet class", () -> { +- return packet.getClass().getCanonicalName(); ++ return packetFinal.getClass().getCanonicalName(); ++ // Sakura end + }); + throw new ReportedException(crashreport); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8bd243a8d5a4be54f907af2b02e96ea833cee62f..946e01733d85b119abe99910efe30029a344185b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3098,6 +3098,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + event.setCancelled(cancelled); + AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224 ++ me.samsuik.sakura.player.gui.PlayerGUI.onWindowClick(event); // Sakura - visibility gui + cserver.getPluginManager().callEvent(event); + if (this.player.containerMenu != oldContainer) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f20ae9153b7098980ce6c0e75fcbbb4da652661b..0ebeac99c589ca70c26fa7db55f0c8f9a0e5fcd1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -528,6 +528,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.teleportTo(worldserver, null); + } + // Paper end - make end portalling safe ++ public boolean isPrimedTNT; // Sakura ++ public boolean isFallingBlock; // Sakura + + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); +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 9105418b29c89f092378da11b14e3d324332a2ba..d5560231aa398d56d1de06b19946bfcfe003df00 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -73,6 +73,7 @@ public class FallingBlockEntity extends Entity { + this.blockState = Blocks.SAND.defaultBlockState(); + this.dropItem = true; + this.fallDamageMax = 40; ++ this.isFallingBlock = true; // Sakura + } + + public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) { +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 4ce3e69970dd9eb251d0538a2d233ca30e9e5e47..d14a8e2cf748cb3784253d99d1bf3c8f9eb2089c 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -31,6 +31,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + public PrimedTnt(EntityType type, Level world) { + super(type, world); + this.blocksBuilding = true; ++ this.isPrimedTNT = true; // Sakura + } + + public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 8006ec1eece1e1c5c0b18bc2b5190bbb43e9d4c3..9e49196f7038c710e850005b6ad96c4eafca0f2e 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -216,6 +216,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public abstract ResourceKey getTypeKey(); + ++ public final it.unimi.dsi.fastutil.longs.Long2IntMap minimalTNT = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); // Sakura - visibility api ++ + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e188bb3ba5d2ec28421947c0b66b25eecb569bfe..9ac026a464f8b5db5fe7543b71cd5bcaa3f65f70 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -489,6 +489,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().displayName = name == null ? getName() : name; + } + ++ // Sakura start - visiblity api ++ @Override ++ public me.samsuik.sakura.player.visibility.Visibility getVisibility() { ++ return getHandle().visibility; ++ } ++ // Sakura end ++ + // Paper start + @Override + public void playerListName(net.kyori.adventure.text.Component name) { diff --git a/patches/server/0006-Optimise-rayTracing.patch b/patches/server/0006-Optimise-rayTracing.patch new file mode 100644 index 0000000..f3fc357 --- /dev/null +++ b/patches/server/0006-Optimise-rayTracing.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Tue, 1 Mar 2022 18:40:09 +0000 +Subject: [PATCH] Optimise rayTracing + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 45243249a561440512ef2a620c60b02e159c80e2..54a7678c807e4954e6b56e59e49bab53a88a4860 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -281,7 +281,7 @@ public class Explosion { + } + } + +- if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) { ++ if (!collision.isEmpty() && collision.clipDirect(from, to, currPos)) { // Sakura + return true; + } + } +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 6bd6385ad82481a099f3556ed2dbd3744888fc34..157dc1c815cb15818fd6fb103a9e806ca2f3d68c 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -695,6 +695,110 @@ public abstract class VoxelShape { + // Paper end - optimise collisions + } + ++ // Sakura start ++ // As of 1.20.2 paper has their own version of the pufferfish patch that this patch expanded on. ++ // A bit this patch is now obsolete such as simple AABB clipping. ++ // We will still use our method when a detailed hit result isn't required. ++ public boolean clipDirect(Vec3 start, Vec3 end, BlockPos pos) { ++ if (this.isEmpty) { ++ return false; ++ } ++ ++ double vec3_x = end.x - start.x; ++ double vec3_y = end.y - start.y; ++ double vec3_z = end.z - start.z; ++ double vec3_lengthSqr = (vec3_x * vec3_x) + (vec3_y * vec3_y) + (vec3_z * vec3_z); ++ ++ if (vec3_lengthSqr < 1.0E-7D) { ++ return false; ++ } ++ ++ AABB singleAABB = this.singleAABBRepresentation; ++ //noinspection ConstantValue ++ if (singleAABB != null) { ++ return clipWithBBDirect(singleAABB, vec3_x, vec3_y, vec3_z, start, pos); ++ } ++ ++ return clipWithBBsDirect(vec3_x, vec3_y, vec3_z, start, pos); ++ } ++ ++ protected boolean clipWithBBDirect(AABB single, double deltaX, double deltaY, double deltaZ, Vec3 from, BlockPos pos) { ++ double posX = pos.getX(); ++ double posY = pos.getY(); ++ double posZ = pos.getZ(); ++ ++ return clipPointBB(single, from, posX, posY, posZ, deltaX, deltaY, deltaZ) ++ || clipInsideDirectBB(single, deltaX, deltaY, deltaZ, from, pos); ++ } ++ ++ protected boolean clipWithBBsDirect(double deltaX, double deltaY, double deltaZ, Vec3 from, BlockPos pos) { ++ double posX = pos.getX(); ++ double posY = pos.getY(); ++ double posZ = pos.getZ(); ++ ++ for (AABB bb : toAabbs()) { // err ++ if (clipPointBB(bb, from, posX, posY, posZ, deltaX, deltaY, deltaZ)) { ++ return true; ++ } ++ } ++ ++ return clipInsideDirectBBs(deltaX, deltaY, deltaZ, from, pos); ++ } ++ ++ @SuppressWarnings("SuspiciousNameCombination") ++ protected static boolean clipPointBB(AABB box, Vec3 p, double posX, double posY, double posZ, double deltaX, double deltaY, double deltaZ) { ++ double minX = box.minX + posX; ++ double minY = box.minY + posY; ++ double minZ = box.minZ + posZ; ++ double maxX = box.maxX + posX; ++ double maxY = box.maxY + posY; ++ double maxZ = box.maxZ + posZ; ++ ++ // todo: this could be simplified by using the centre of the bb ++ // if the bb dimensions are not the same then either scale or subtract from the result. ++ double closestX = deltaX > 1.0E-7D ? minX : maxX; ++ double closestY = deltaY > 1.0E-7D ? minY : maxY; ++ double closestZ = deltaZ > 1.0E-7D ? minZ : maxZ; ++ ++ return clipPoint(deltaX, deltaY, deltaZ, closestX, minY, maxY, minZ, maxZ, p.x, p.y, p.z) ++ || clipPoint(deltaY, deltaZ, deltaX, closestY, minZ, maxZ, minX, maxX, p.y, p.z, p.x) ++ || clipPoint(deltaZ, deltaX, deltaY, closestZ, minX, maxX, minY, maxY, p.z, p.x, p.y); ++ } ++ ++ private static boolean clipPoint(double deltaX, double deltaY, double deltaZ, double begin, double minX, double maxX, double minZ, double maxZ, double startX, double startY, double startZ) { ++ double d = (begin - startX) / deltaX; ++ double e = startY + d * deltaY; ++ double f = startZ + d * deltaZ; ++ return (d > 0.0D && d < 1.0) && (minX - 1.0E-7D < e && maxX + 1.0E-7D > e) && (minZ - 1.0E-7D < f && maxZ + 1.0E-7D > f); ++ } ++ ++ // Absolutely horrendous code that takes a toll on all clip misses. ++ // This cannot be removed to maintain edge cases caused by this code existing in vanilla. ++ protected boolean clipInsideDirectBB(AABB single, double vec3_x, double vec3_y, double vec3_z, Vec3 start, BlockPos pos) { ++ double fromBehindX = start.x + (vec3_x * 0.001D); ++ double fromBehindY = start.y + (vec3_y * 0.001D); ++ double fromBehindZ = start.z + (vec3_z * 0.001D); ++ ++ double fromBehindOffsetX = fromBehindX - (double) pos.getX(); ++ double fromBehindOffsetY = fromBehindY - (double) pos.getY(); ++ double fromBehindOffsetZ = fromBehindZ - (double) pos.getZ(); ++ ++ return single.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ); ++ } ++ ++ protected boolean clipInsideDirectBBs(double vec3_x, double vec3_y, double vec3_z, Vec3 start, BlockPos pos) { ++ double fromBehindX = start.x + (vec3_x * 0.001D); ++ double fromBehindY = start.y + (vec3_y * 0.001D); ++ double fromBehindZ = start.z + (vec3_z * 0.001D); ++ ++ int indexX = this.findIndex(Direction.Axis.X, fromBehindX - (double)pos.getX()); ++ int indexY = this.findIndex(Direction.Axis.Y, fromBehindY - (double)pos.getY()); ++ int indexZ = this.findIndex(Direction.Axis.Z, fromBehindZ - (double)pos.getZ()); ++ ++ return this.shape.isFullWide(indexX, indexY, indexZ); ++ } ++ // Sakura end ++ + public Optional closestPointTo(Vec3 target) { + // Paper start - optimise collisions + if (this.isEmpty) { diff --git a/patches/server/0007-Reduce-deltaMovement-Allocations.patch b/patches/server/0007-Reduce-deltaMovement-Allocations.patch new file mode 100644 index 0000000..ebf461c --- /dev/null +++ b/patches/server/0007-Reduce-deltaMovement-Allocations.patch @@ -0,0 +1,247 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 24 Mar 2023 16:29:21 +0000 +Subject: [PATCH] Reduce deltaMovement Allocations + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0ebeac99c589ca70c26fa7db55f0c8f9a0e5fcd1..514c34e25b6e42fa591ccbcd134149e64235b899 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1196,7 +1196,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.tryCheckInsideBlocks(); + float f = this.getBlockSpeedFactor(); + +- this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f)); ++ this.multiplyDeltaMovement((double) f, 1.0D, (double) f); // Sakura - reduce movement allocations + // Paper start - remove expensive streams from here + boolean noneMatch = true; + AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); +@@ -2016,6 +2016,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public void moveTo(double x, double y, double z, float yaw, float pitch) { + // Paper - cancel entity velocity if teleported + if (!preserveMotion) { ++ this.movementDirty = false; // Sakura + this.deltaMovement = Vec3.ZERO; + } else { + this.preserveMotion = false; +@@ -3389,29 +3390,33 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void onAboveBubbleCol(boolean drag) { +- Vec3 vec3d = this.getDeltaMovement(); ++ // Sakura start - reduce movement allocations ++ // Vec3 vec3d = this.getDeltaMovement(); ++ this.syncDeltaMovement(); + double d0; + + if (drag) { +- d0 = Math.max(-0.9D, vec3d.y - 0.03D); ++ d0 = Math.max(-0.9D, movementY - 0.03D); + } else { +- d0 = Math.min(1.8D, vec3d.y + 0.1D); ++ d0 = Math.min(1.8D, movementY + 0.1D); + } + +- this.setDeltaMovement(vec3d.x, d0, vec3d.z); ++ this.setDeltaMovement(movementX, d0, movementZ); + } + + public void onInsideBubbleColumn(boolean drag) { +- Vec3 vec3d = this.getDeltaMovement(); ++ // Vec3 vec3d = this.getDeltaMovement(); ++ this.syncDeltaMovement(); + double d0; + + if (drag) { +- d0 = Math.max(-0.3D, vec3d.y - 0.03D); ++ d0 = Math.max(-0.3D, movementY - 0.03D); + } else { +- d0 = Math.min(0.7D, vec3d.y + 0.06D); ++ d0 = Math.min(0.7D, movementY + 0.06D); + } + +- this.setDeltaMovement(vec3d.x, d0, vec3d.z); ++ this.setDeltaMovement(movementX, d0, movementZ); ++ // Sakura end + this.resetFallDistance(); + } + +@@ -4385,16 +4390,19 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + vec3d = vec3d.normalize(); + } + +- Vec3 vec3d2 = this.getDeltaMovement(); ++ // Sakura start - reduce movement allocations ++ // Vec3 vec3d2 = this.getDeltaMovement(); ++ this.syncDeltaMovement(); + + vec3d = vec3d.scale(speed * 1.0D); + double d3 = 0.003D; + +- if (Math.abs(vec3d2.x) < 0.003D && Math.abs(vec3d2.z) < 0.003D && vec3d.length() < 0.0045000000000000005D) { ++ if (Math.abs(movementX) < 0.003D && Math.abs(movementZ) < 0.003D && vec3d.length() < 0.0045000000000000005D) { + vec3d = vec3d.normalize().scale(0.0045000000000000005D); + } + +- this.setDeltaMovement(this.getDeltaMovement().add(vec3d)); ++ this.addDeltaMovement(vec3d.x, vec3d.y, vec3d.z); ++ // Sakura end + } + + this.fluidHeight.put(tag, d1); +@@ -4465,11 +4473,53 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return this.chunkPosition; + } + ++ // Sakura start - reduce movement allocations ++ private double movementX; ++ private double movementY; ++ private double movementZ; ++ private boolean movementDirty; ++ ++ public void addDeltaMovement(double x, double y, double z) { ++ syncDeltaMovement(); ++ movementX += x; ++ movementY += y; ++ movementZ += z; ++ } ++ ++ public void scaleDeltaMovement(double n) { ++ syncDeltaMovement(); ++ movementX *= n; ++ movementY *= n; ++ movementZ *= n; ++ } ++ ++ public void multiplyDeltaMovement(double x, double y, double z) { ++ syncDeltaMovement(); ++ movementX *= x; ++ movementY *= y; ++ movementZ *= z; ++ } ++ ++ private void updateDeltaMovement() { ++ if (movementDirty) { ++ deltaMovement = new Vec3(movementX, movementY, movementZ); ++ movementDirty = false; ++ } ++ } ++ ++ private void syncDeltaMovement() { ++ if (!movementDirty) { ++ setDeltaMovement(deltaMovement.x, deltaMovement.y, deltaMovement.z); ++ } ++ } ++ + public Vec3 getDeltaMovement() { ++ updateDeltaMovement(); + return this.deltaMovement; + } + + public void setDeltaMovement(Vec3 velocity) { ++ movementDirty = false; + synchronized (this.posLock) { // Paper + this.deltaMovement = velocity; + } // Paper +@@ -4480,7 +4530,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void setDeltaMovement(double x, double y, double z) { +- this.setDeltaMovement(new Vec3(x, y, z)); ++ movementX = x; ++ movementY = y; ++ movementZ = z; ++ movementDirty = true; ++ // Sakura end + } + + public final int getBlockX() { +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 d5560231aa398d56d1de06b19946bfcfe003df00..240a762b91a3c570583ee65bfe55b9677c4f63be 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -145,7 +145,7 @@ public class FallingBlockEntity extends Entity { + + ++this.time; + if (!this.isNoGravity()) { +- this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.04D, 0.0D)); ++ this.addDeltaMovement(0.0D, -0.04D, 0.0D); // Sakura - reduce movement allocations + } + + this.move(MoverType.SELF, this.getDeltaMovement()); +@@ -192,7 +192,7 @@ public class FallingBlockEntity extends Entity { + } else { + BlockState iblockdata = this.level().getBlockState(blockposition); + +- this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); ++ this.multiplyDeltaMovement(0.7D, -0.5D, 0.7D); // Sakura - reduce movement allocations + if (!iblockdata.is(Blocks.MOVING_PISTON)) { + if (!this.cancelDrop) { + boolean flag2 = iblockdata.canBeReplaced((BlockPlaceContext) (new DirectionalPlaceContext(this.level(), blockposition, Direction.DOWN, ItemStack.EMPTY, Direction.UP))); +@@ -259,7 +259,7 @@ public class FallingBlockEntity extends Entity { + } + } + +- this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); ++ this.scaleDeltaMovement(0.98D); // Sakura - reduce movement allocations + } + } + +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 d14a8e2cf748cb3784253d99d1bf3c8f9eb2089c..369cfaeba980d1d98d0dcefffb584dcbbe144b1d 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -66,7 +66,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + public void tick() { + if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot + if (!this.isNoGravity()) { +- this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.04D, 0.0D)); ++ this.addDeltaMovement(0.0D, -0.04D, 0.0D); // Sakura - reduce movement allocations + } + + this.move(MoverType.SELF, this.getDeltaMovement()); +@@ -76,9 +76,9 @@ public class PrimedTnt extends Entity implements TraceableEntity { + return; + } + // Paper end +- this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); ++ this.scaleDeltaMovement(0.98D); // Sakura - reduce movement allocations + if (this.onGround()) { +- this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); ++ this.multiplyDeltaMovement(0.7D, -0.5D, 0.7D); // Sakura - reduce movement allocations + } + + int i = this.getFuse() - 1; +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 54a7678c807e4954e6b56e59e49bab53a88a4860..1b335111bd9eb90bbda87225b740768705f26193 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -600,10 +600,12 @@ public class Explosion { + d8 *= d14; + d9 *= d14; + d10 *= d14; +- Vec3 vec3d1 = new Vec3(d8, d9, d10); + +- entity.setDeltaMovement(entity.getDeltaMovement().add(vec3d1)); ++ // Sakura start - reduce deltamovement allocations ++ entity.addDeltaMovement(d8, d9, d10); + if (entity instanceof Player) { ++ Vec3 vec3d1 = new Vec3(d8, d9, d10); ++ // Sakura end + Player entityhuman = (Player) entity; + + if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Disable explosion knockback +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 d4cbff18adb62073a1dceb189043789620af6877..26cc4c10d441a800623fd5b76a9c31a57291cdba 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -453,7 +453,7 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public void updateEntityAfterFallOn(BlockGetter world, Entity entity) { +- entity.setDeltaMovement(entity.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); ++ entity.multiplyDeltaMovement(1.0D, 0.0D, 1.0D); // Sakura + } + + public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) { diff --git a/patches/server/0008-Optional-Force-Position-Updates.patch b/patches/server/0008-Optional-Force-Position-Updates.patch new file mode 100644 index 0000000..6351f5d --- /dev/null +++ b/patches/server/0008-Optional-Force-Position-Updates.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Sat, 11 Sep 2021 22:23:39 +0100 +Subject: [PATCH] Optional Force Position Updates + + +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 369cfaeba980d1d98d0dcefffb584dcbbe144b1d..5b4e1d56f63935c5b6506dc94d174e8efebd260f 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -99,6 +99,14 @@ public class PrimedTnt extends Entity implements TraceableEntity { + } + } + ++ // Sakura start - configurable force position updates ++ if (level().sakuraConfig().cannons.tnt.forcePositionUpdates) { ++ forcePositionUpdate(); ++ } ++ } ++ ++ private void forcePositionUpdate() { ++ // Sakura end + // Paper start - Optional prevent TNT from moving in water + if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) { + /* diff --git a/patches/server/0009-Load-Chunks-on-Movement.patch b/patches/server/0009-Load-Chunks-on-Movement.patch new file mode 100644 index 0000000..5cbd3a8 --- /dev/null +++ b/patches/server/0009-Load-Chunks-on-Movement.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Sat, 11 Sep 2021 19:19:41 +0100 +Subject: [PATCH] Load Chunks on Movement + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 514c34e25b6e42fa591ccbcd134149e64235b899..c35ca0785ecc268fb7e20dee56d42bdadca41ee6 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -397,6 +397,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled + public boolean persistentInvisibility = false; ++ public boolean loadChunks = false; // Sakura - load chunks + public BlockPos lastLavaContact; + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); +@@ -1457,7 +1458,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + io.papermc.paper.util.CollisionUtil.getCollisions( + world, this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, +- (0), ++ (0) | (loadChunks ? io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS : 0), // Sakura + null, null + ); + +@@ -4800,7 +4801,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + @Override + public boolean isAlwaysTicking() { +- return false; ++ return loadChunks; // Sakura - always tick in chunks + } + + public boolean mayInteract(Level world, BlockPos pos) { +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 240a762b91a3c570583ee65bfe55b9677c4f63be..2e0f38ffad09b6afbea74205b89beb774e779545 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -74,6 +74,7 @@ public class FallingBlockEntity extends Entity { + this.dropItem = true; + this.fallDamageMax = 40; + this.isFallingBlock = true; // Sakura ++ this.loadChunks = world.sakuraConfig().cannons.sand.loadsChunks; // Sakura - load chunks + } + + public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) { +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 5b4e1d56f63935c5b6506dc94d174e8efebd260f..4c328a511ff6c0e6b73ef9701c82373e02c12830 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -32,6 +32,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + super(type, world); + this.blocksBuilding = true; + this.isPrimedTNT = true; // Sakura ++ this.loadChunks = world.sakuraConfig().cannons.tnt.loadsChunks; // Sakura - load chunks + } + + public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { diff --git a/patches/server/0010-TPS-Graph-Command.patch b/patches/server/0010-TPS-Graph-Command.patch new file mode 100644 index 0000000..8a77006 --- /dev/null +++ b/patches/server/0010-TPS-Graph-Command.patch @@ -0,0 +1,509 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Sun, 19 Sep 2021 01:10:02 +0100 +Subject: [PATCH] TPS Graph Command + + +diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +index d7d0c49cc5d576c594dee16ddba037cd147e11fa..2ed50a4fc9cddc036adc5b4288bd5d83442b1572 100644 +--- a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java ++++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +@@ -4,6 +4,7 @@ import io.papermc.paper.command.PaperPluginsCommand; + import me.samsuik.sakura.command.subcommands.ConfigCommand; + import me.samsuik.sakura.command.subcommands.FPSCommand; + import me.samsuik.sakura.command.subcommands.VisualCommand; ++import me.samsuik.sakura.command.subcommands.TPSCommand; + import me.samsuik.sakura.player.visibility.Visibility; + import net.minecraft.server.MinecraftServer; + import org.bukkit.command.Command; +@@ -21,6 +22,7 @@ public class SakuraCommands { + COMMANDS.put("tntvisibility", new VisualCommand(Visibility.Setting.TNT_VISIBILITY, "tnttoggle")); + COMMANDS.put("sandvisibility", new VisualCommand(Visibility.Setting.SAND_VISIBILITY, "sandtoggle")); + COMMANDS.put("minimal", new VisualCommand(Visibility.Setting.MINIMAL, "minimaltnt", "tntlag")); ++ COMMANDS.put("tps", new TPSCommand("tps")); + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b9e254f821145369dc9c382029e8585693fa29c +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java +@@ -0,0 +1,69 @@ ++package me.samsuik.sakura.command.subcommands; ++ ++import me.samsuik.sakura.command.BaseSubCommand; ++import me.samsuik.sakura.utils.tps.TPSGraph; ++import me.samsuik.sakura.utils.tps.TickTracking; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.Tag; ++import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.util.Mth; ++import org.bukkit.command.CommandSender; ++ ++public class TPSCommand extends BaseSubCommand { ++ ++ public TPSCommand(String name) { ++ super(name); ++ this.description = "Gets the current ticks per second for the server"; ++ } ++ ++ @Override ++ public void execute(CommandSender sender, String[] args) { ++ var tracking = MinecraftServer.tickTracking; ++ ++ var average = tracking.averageTps(10) * 1.75; ++ int lines = 10; ++ ++ try { ++ average = Double.parseDouble(args[0]); ++ lines = Mth.clamp(Integer.parseInt(args[1]), 1, 18); ++ } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {} ++ ++ sender.sendMessage(Component.text(".", NamedTextColor.DARK_PURPLE)); ++ sender.sendMessage(createInformationComponent(tracking)); ++ ++ // Create the graph ++ var graph = new TPSGraph(tracking, lines, 75, average); ++ graph.map(); ++ ++ // Send the graph to the user ++ for (var component : graph.components()) { ++ sender.sendMessage(Component.text("| ", NamedTextColor.DARK_PURPLE).append(component)); ++ } ++ ++ sender.sendMessage(Component.text("| ", NamedTextColor.DARK_PURPLE).append(Component.text(" ".repeat(75), NamedTextColor.GRAY, TextDecoration.STRIKETHROUGH))); ++ sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE)); ++ } ++ ++ private Component createInformationComponent(TickTracking tracking) { ++ return MiniMessage.miniMessage().deserialize("| -------------- ( Now: , Mem: % ) ---------------", ++ TagResolver.resolver("tps", Tag.selfClosingInserting( ++ Component.text("%.1f".formatted(tracking.averageTps(1)), TPSGraph.colour(tracking.averageTps(1) / 20.0)) ++ )), ++ TagResolver.resolver("memory", Tag.selfClosingInserting( ++ Component.text("%.1f".formatted(memoryUsage() * 100), TPSGraph.colour(1.0 - memoryUsage())) ++ ))); ++ } ++ ++ private double memoryUsage() { ++ Runtime runtime = Runtime.getRuntime(); ++ double free = runtime.freeMemory(); ++ double max = runtime.maxMemory(); ++ double alloc = runtime.totalMemory(); ++ return (alloc - free) / max; ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/tps/TPSGraph.java b/src/main/java/me/samsuik/sakura/utils/tps/TPSGraph.java +new file mode 100644 +index 0000000000000000000000000000000000000000..efbf8360657c862dd522d0264aa1c5d8f73bd8b5 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/tps/TPSGraph.java +@@ -0,0 +1,255 @@ ++package me.samsuik.sakura.utils.tps; ++ ++import me.samsuik.sakura.utils.tps.TickTracking.Point; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.event.HoverEvent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; ++import net.minecraft.util.Mth; ++ ++public class TPSGraph { ++ ++ private final Parts[][] parts; ++ private final TickTracking tracked; ++ private final int lines; ++ private final int length; ++ private final double ceiling; ++ ++ public TPSGraph(TickTracking tracked, int lines, int length, double ceiling) { ++ this.parts = new Parts[lines][length]; ++ this.tracked = tracked; ++ this.length = length; ++ this.lines = lines; ++ this.ceiling = ceiling; ++ } ++ ++ public void map() { ++ // Create the background ++ for (var line = 0; line < lines; ++line) { ++ for (var column = 0; column < length; ++column) { ++ parts[line][column] = Parts.BACKGROUND; ++ } ++ } ++ ++ // Create normal points on the graph ++ for (var column = 0; column < length; ++column) { ++ var tps = tracked.point(column).tps(); ++ // Likely the server has just started ++ if (tps == 0.0) break; ++ // Create normal point for column ++ var line = getLine(tps); ++ var lineParts = parts[line]; ++ lineParts[column] = Parts.NORMAL; ++ } ++ ++ // Create spikes on the graph, what is a spike? ++ // By spike I am referring to a situation where ++ // the normal point would be "moving" more than ++ // 2 lines, which looks terrible. ++ for (var column = 0; column < length; ++column) { ++ var nextTps = tracked.point(column + 1).tps(); ++ // Likely the server has just started ++ if (nextTps == 0.0) break; ++ var curr = getLine(tracked.point(column).tps()); ++ var prev = getLine(tracked.point(Math.max(column - 1, 0)).tps()); ++ var next = getLine(nextTps); ++ var min = Math.min(curr, next); ++ var max = Math.max(curr, next); ++ ++ if (max - min < 2) { ++ continue; ++ } ++ ++ // Create vertical parts between the two points ++ for (var line = min; line < max; ++line) { ++ parts[line][column] = Parts.VERTICAL; ++ } ++ ++ parts[min][column] = Parts.TOP; ++ parts[max][column] = Parts.BOTTOM; ++ ++ if (column + 1 < length) { ++ // dodgy hack because the previous value can change +/- 1 ++ // causing the spike to not correctly line up. ++ // this isn't a problem in the other direction ++ if (prev == curr + 1 && next < curr) { ++ parts[max][column] = Parts.SPIKE_TOP_RIGHT; ++ } ++ ++ if (min == next) { ++ parts[min][column] = Parts.SPIKE_BOTTOM_LEFT; // '! ++ } else if (prev <= min) { // has to be <= due to the issue I noted above ++ parts[min][column] = Parts.SPIKE_BOTTOM_RIGHT; // !' ++ } ++ ++ // cone on the top of a spike ++ if (max == curr && Math.abs(next - max) > 1 && Math.abs(prev - max) > 1 && prev < max) { ++ parts[max][column - 1] = Parts.SPIKE_TOP_LEFT; ++ parts[max][column] = Parts.SPIKE_TOP_RIGHT; ++ } ++ ++ // cone on the bottom of a spike ++ if (min == curr && Math.abs(next - min) > 1 && Math.abs(prev - min) > 1 && prev > min) { ++ parts[min][column - 1] = Parts.SPIKE_BOTTOM_LEFT; ++ parts[min][column] = Parts.SPIKE_BOTTOM_RIGHT; ++ } ++ } ++ } ++ ++ // Create special points on the graph, this ++ // is to handle slopes and slight differences ++ // that show up, anything else should be too ++ // extreme and get treated as a spike instead. ++ for (var column = 0; column < length; ++column) { ++ var tps = tracked.point(column).tps(); ++ // Likely the server has just started ++ if (tps == 0.0) continue; ++ var curr = getLine(tps); ++ var prev = getLine(tracked.point(Math.max(column - 1, 0)).tps()); ++ var next = getLine(tracked.point(column + 1).tps()); ++ ++ // Ignore spikes, it'd mess up and be a waste of time ++ if (Math.abs(curr - next) >= 2) { ++ continue; ++ } ++ ++ // SANITY: positive = rising, negative = declining ++ var direction = next - prev; ++ var change = Math.abs(direction); ++ ++ if (change >= 2 && Math.max(next, prev) == curr + 1) { ++ // Create slopes, only requirement is that the highest point ++ // and the current are one line away from one another. ++ if (direction < 0) { ++ parts[curr][column] = Parts.DECLINING; ++ } else { ++ parts[curr][column] = Parts.RISING; ++ } ++ } else if (Math.abs(curr - next) == 1 || direction == 0) { ++ // if we have no direction always do this as a special case for slight dips and rises ++ if (curr < next) { ++ parts[curr][column] = Parts.TOP; ++ } else if (curr > next) { ++ parts[curr][column] = Parts.BOTTOM; ++ } ++ } else if (Math.abs(curr - prev) == 1) { ++ if (prev > curr) { ++ parts[curr][column] = Parts.TOP; ++ } else if (prev < curr) { ++ parts[curr][column] = Parts.BOTTOM; ++ } ++ } ++ } ++ } ++ ++ private int getLine(double tps) { ++ var per = (ceiling / 10); // How many lines 1 tick should display ++ var line = (int) (tps / per); ++ return Mth.clamp(line, 0, lines - 1); ++ } ++ ++ public Component[] components() { ++ // Create graph component array ++ var graph = new TextComponent.Builder[lines]; ++ ++ // Initialise graph components ++ for (var line = 0; line < graph.length; ++line) { ++ graph[line] = Component.text(); ++ } ++ ++ // Write the graph ++ for (var line = 0; line < parts.length; line++) { ++ writeLine(graph[line], parts[parts.length - line - 1]); ++ } ++ ++ // Create built component array ++ var parts = new Component[graph.length]; ++ ++ // Store built components ++ for (var i = 0; i < graph.length; i++) { ++ parts[i] = graph[i].build(); ++ } ++ ++ return parts; ++ } ++ ++ private void writeLine(TextComponent.Builder builder, Parts[] lineParts) { ++ for (var column = 0; column < lineParts.length; column++) { ++ var point = tracked.point(column); ++ var part = lineParts[column]; ++ ++ var colour = colour(point.tps() / 20.0); ++ var component = part.getPart(); ++ ++ if (part != Parts.BACKGROUND) { ++ component = component.color(colour); ++ } ++ ++ // Display information from the current point ++ builder.append(appendHoverEvent(component, point, colour)); ++ } ++ } ++ ++ private Component appendHoverEvent(Component in, Point point, TextColor colour) { ++ return in.hoverEvent(HoverEvent.showText(MiniMessage.miniMessage().deserialize(""" ++ TPS: ++ MS: (, ) ++ Chunks: ++ Entities: """, ++ Placeholder.component("tps", Component.text("%.1f".formatted(point.tps()), colour)), ++ Placeholder.component("ms", Component.text("%.1f".formatted(point.mspt()), colour)), ++ Placeholder.component("highest", Component.text("%.1f".formatted(point.highest()), colour)), ++ Placeholder.unparsed("chunks", String.valueOf(point.chunks())), ++ Placeholder.unparsed("entities", String.valueOf(point.entities())) ++ ))); ++ } ++ ++ public static TextColor colour(double percentage) { ++ if (percentage > 0.75) return shift(percentage, 1.0, 0.75, NamedTextColor.GREEN, NamedTextColor.YELLOW); ++ if (percentage > 0.6) return shift(percentage, 0.75, 0.6, NamedTextColor.YELLOW, NamedTextColor.GOLD); ++ if (percentage > 0.4) return shift(percentage, 0.6, 0.4, NamedTextColor.GOLD, NamedTextColor.RED); ++ if (percentage > 0.2) return shift(percentage, 0.4, 0.2, NamedTextColor.RED, NamedTextColor.DARK_GRAY); ++ return shift(percentage, 0.2, 0.0, NamedTextColor.DARK_GRAY, NamedTextColor.BLACK); ++ } ++ ++ private static TextColor shift(double percentage, double from, double to, TextColor fromColour, TextColor toColour) { ++ var f = (float) ((percentage - from) / (to - from)); ++ return TextColor.lerp(Math.max(f, 0.0f), fromColour, toColour); ++ } ++ ++ public static double format(double num, int decimals) { ++ var pow = Math.pow(10, decimals); ++ return Math.round(num * pow) / pow; ++ } ++ ++ private enum Parts { ++ // Adventure doesn't have a Component.text(String, TextDecoration) ++ BACKGROUND(Component.text("::", NamedTextColor.BLACK)), ++ NORMAL(Component.text(" ").decorate(TextDecoration.STRIKETHROUGH)), ++ VERTICAL(Component.text("||")), ++ RISING(Component.text(".").decorate(TextDecoration.STRIKETHROUGH).append(Component.text("'").decoration(TextDecoration.STRIKETHROUGH, false))), ++ DECLINING(Component.text("'").append(Component.text(".").decorate(TextDecoration.STRIKETHROUGH))), ++ SPIKE_TOP_LEFT(Component.text(".!")), ++ SPIKE_TOP_RIGHT(Component.text("!.")), ++ SPIKE_BOTTOM_LEFT(Component.text("'!")), ++ SPIKE_BOTTOM_RIGHT(Component.text("!'")), ++ TOP(Component.text("''")), ++ BOTTOM(Component.text("..")), ++ ; ++ ++ private final Component part; ++ ++ Parts(Component component) { ++ part = component; ++ } ++ ++ public Component getPart() { ++ return part; ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/tps/TickTracking.java b/src/main/java/me/samsuik/sakura/utils/tps/TickTracking.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8a94b1a2cb1ff57664c97a7b471c99ec391103ae +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/tps/TickTracking.java +@@ -0,0 +1,53 @@ ++package me.samsuik.sakura.utils.tps; ++ ++import net.minecraft.server.level.ServerLevel; ++ ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.List; ++import java.util.stream.IntStream; ++ ++public class TickTracking { ++ ++ private final List history = new ArrayList<>(); ++ private final double[] msptSamples = new double[20]; ++ ++ public TickTracking(int samples) { ++ for (int i = 0; i < samples; ++i) { ++ history.add(new Point(0.0, 0.0,0.0, 0, 0)); ++ } ++ } ++ ++ Point point(int at) { ++ return history.get(at); ++ } ++ ++ public double averageTps(int samples) { ++ return IntStream.range(0, samples) ++ .mapToDouble(i -> history.get(i).tps) ++ .average() ++ .orElse(0.0); ++ } ++ ++ public void secondSample(Collection server, double tps) { ++ history.remove(history.size() - 1); ++ ++ int entities = server.stream().mapToInt((world) -> world.entityTickList.entityCount()).sum(); ++ int loaded = server.stream().mapToInt((world) -> world.chunkSource.actualLoadedChunkCount()).sum(); ++ ++ double mspt = Arrays.stream(msptSamples).average().orElse(0.0); ++ double max = Arrays.stream(msptSamples).max().orElse(0.0); ++ ++ history.add(0, new Point(tps, mspt, max, entities, loaded)); ++ } ++ ++ public void tickSample(long mspt) { ++ for (int i = msptSamples.length - 2; i >= 0; --i) ++ msptSamples[i + 1] = msptSamples[i]; ++ msptSamples[0] = mspt; ++ } ++ ++ public record Point(double tps, double mspt, double highest, int entities, int chunks) {} ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4535beab09310915b0239b01c1b807f1ca25d959..eef411f838ecdeff0d4052fac22900e4ad87ceb5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1080,6 +1080,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entityManager; // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +index 4cdfc433df67afcd455422e9baf56f167dd712ae..1fcce60790cab6e7b137464ccd87e6782a5ae98c 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +@@ -10,6 +10,12 @@ import net.minecraft.world.entity.Entity; + public class EntityTickList { + private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? + ++ // Sakura start ++ public int entityCount() { ++ return entities.size(); ++ } ++ // Sakura end ++ + private void ensureActiveIsNotIterated() { + // Paper - replace with better logic, do not delay removals + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 612c3169c3463d702b85975e1db79ae6e47d60d0..2063a6c6ab0786aee51be027950c2a936f593b55 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -283,7 +283,7 @@ public class SpigotConfig + + private static void tpsCommand() + { +- SpigotConfig.commands.put( "tps", new TicksPerSecondCommand( "tps" ) ); ++ // SpigotConfig.commands.put( "tps", new TicksPerSecondCommand( "tps" ) ); // Sakura - TPS Graph + } + + public static int playerSample; diff --git a/patches/server/0011-Optimise-New-Liquid-Level.patch b/patches/server/0011-Optimise-New-Liquid-Level.patch new file mode 100644 index 0000000..00eecbd --- /dev/null +++ b/patches/server/0011-Optimise-New-Liquid-Level.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Wed, 6 Oct 2021 17:25:27 +0100 +Subject: [PATCH] Optimise New Liquid Level + + +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 e21f4c5aff3a8e97101f6efc1349fbecf326b5ea..6d59f8b68d644cb43939bcdf5239fa1caf54ed47 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -137,7 +137,7 @@ public abstract class FlowingFluid extends Fluid { + BlockState iblockdata = world.getBlockState(fluidPos); + BlockPos blockposition1 = fluidPos.below(); + BlockState iblockdata1 = world.getBlockState(blockposition1); +- FluidState fluid1 = this.getNewLiquid(world, blockposition1, iblockdata1); ++ FluidState fluid1 = this.getLiquid(world, blockposition1, iblockdata1, fluidPos, iblockdata); // Sakura - optimise liquid level + + if (this.canSpreadTo(world, fluidPos, iblockdata, Direction.DOWN, blockposition1, iblockdata1, world.getFluidState(blockposition1), fluid1.getType())) { + // CraftBukkit start +@@ -197,6 +197,25 @@ public abstract class FlowingFluid extends Fluid { + } + + protected FluidState getNewLiquid(Level world, BlockPos pos, BlockState state) { ++ // Sakura start - optimise liquid level ++ BlockPos blockposition2 = pos.above(); ++ BlockState iblockdata3 = world.getBlockState(blockposition2); ++ ++ return getLiquid(world, pos, state, blockposition2, iblockdata3); ++ } ++ ++ // SANITY: world, pos, state, above pos, above state ++ protected FluidState getLiquid(Level world, BlockPos pos, BlockState state, BlockPos blockposition2, BlockState iblockdata3) { ++ FluidState fluid2 = iblockdata3.getFluidState(); ++ ++ if (!fluid2.isEmpty() && fluid2.getType().isSame(this) && this.canPassThroughWall(Direction.UP, world, pos, state, blockposition2, iblockdata3)) { ++ return this.getFlowing(8, true); ++ } else { ++ return this.getLiquidFromSurroundings(world, pos, state); ++ } ++ } ++ ++ protected FluidState getLiquidFromSurroundings(Level world, BlockPos pos, BlockState state) { + int i = 0; + int j = 0; + Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); +@@ -227,17 +246,10 @@ public abstract class FlowingFluid extends Fluid { + } + } + +- BlockPos blockposition2 = pos.above(); +- BlockState iblockdata3 = world.getBlockState(blockposition2); +- FluidState fluid2 = iblockdata3.getFluidState(); ++ int k = i - this.getDropOff(world); + +- if (!fluid2.isEmpty() && fluid2.getType().isSame(this) && this.canPassThroughWall(Direction.UP, world, pos, state, blockposition2, iblockdata3)) { +- return this.getFlowing(8, true); +- } else { +- int k = i - this.getDropOff(world); +- +- return k <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(k, false); +- } ++ return k <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(k, false); ++ // Sakura end + } + + private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { diff --git a/patches/server/0012-Slice-Packet-obfuscation-and-reduction.patch b/patches/server/0012-Slice-Packet-obfuscation-and-reduction.patch new file mode 100644 index 0000000..3f0dde4 --- /dev/null +++ b/patches/server/0012-Slice-Packet-obfuscation-and-reduction.patch @@ -0,0 +1,291 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Wed, 6 Oct 2021 11:03:01 -0500 +Subject: [PATCH] (Slice) Packet obfuscation and reduction + +Minecraft is overzealous about packet updates for Entities. In Loka's case, we want to reduce as many unnecessary +packet updates as possible. This patch is likely to be updated over and over in terms of reducing packet sends. + +In summary, this patch creates the concept of a "foreignValue" of a packet's data. We treat packets in two ways: +1) The packet sent to the player itself (the normal way). This always has all of the values as usual. +2) The packet data as seen by any other (foreign) players. + +This patch adds the ability to set a "foreignValue" for an entity value so as to obfuscate data received by other players. +The current packets modified/obfuscated are the following: + + # This reduces the amount of health packet updates as well which is great for players in combat. + + # Air level packets are sent PER-TICK, and as such a player with any change in air level will only spam themselves + # with packets instead of every single player within tracking distance + +diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +index 5dfb35117c285e0b202dc9c088ad5848beb8d054..e24dd4c7108a014533e5731ad2dafc51a55ebf6d 100644 +--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +@@ -39,6 +39,7 @@ public class SynchedEntityData { + private static final int GROW_FACTOR = 8; + private SynchedEntityData.DataItem[] itemsArray = new SynchedEntityData.DataItem[DEFAULT_ENTRY_COUNT]; + // Paper end ++ private boolean isForeignDirty; // Slice + + public SynchedEntityData(Entity trackedEntity) { + this.entity = trackedEntity; +@@ -165,6 +166,16 @@ public class SynchedEntityData { + } + + public void set(EntityDataAccessor key, T value, boolean force) { ++ // Slice start ++ this.set(key, value, null, force); ++ } ++ ++ public void set(EntityDataAccessor key, T value, T foreignValue) { ++ this.set(key, value, foreignValue, false); ++ } ++ ++ public void set(EntityDataAccessor key, T value, T foreignValue, boolean force) { ++ // Slice end + SynchedEntityData.DataItem datawatcher_item = this.getItem(key); + + if (force || ObjectUtils.notEqual(value, datawatcher_item.getValue())) { +@@ -174,6 +185,12 @@ public class SynchedEntityData { + this.isDirty = true; + } + ++ // Slice start ++ if (foreignValue != null && ObjectUtils.notEqual(foreignValue, datawatcher_item.getForeignValue())) { ++ datawatcher_item.setForeignValue(foreignValue); ++ this.isForeignDirty = true; ++ } ++ // Slice end + } + + // CraftBukkit start - add method from above +@@ -183,6 +200,12 @@ public class SynchedEntityData { + } + // CraftBukkit end + ++ // Slice start ++ public boolean isForeignDirty() { ++ return this.isForeignDirty; ++ } ++ // Slice end ++ + public boolean isDirty() { + return this.isDirty; + } +@@ -215,6 +238,29 @@ public class SynchedEntityData { + return list; + } + ++ // Slice start ++ @Nullable ++ public List> packForeignDirty(List> unpackedData) { ++ List> list = null; ++ ++ for (DataValue dataItem : unpackedData) { ++ DataItem item = itemsById.get(dataItem.id()); ++ if (item.isDirty(true)) { ++ item.setForeignDirty(false); ++ ++ if (list == null) { ++ list = new ArrayList<>(); ++ } ++ ++ list.add(item.copy(true)); ++ } ++ } ++ ++ this.isForeignDirty = false; ++ return list; ++ } ++ // Slice end ++ + @Nullable + public List> getNonDefaultValues() { + List> list = null; +@@ -336,11 +382,14 @@ public class SynchedEntityData { + T value; + private final T initialValue; + private boolean dirty; ++ @Nullable T foreignValue = null; // Slice ++ private boolean foreignDirty; // Slice + + public DataItem(EntityDataAccessor data, T value) { + this.accessor = data; + this.initialValue = value; + this.value = value; ++ this.foreignDirty = true; // Slice + } + + public EntityDataAccessor getAccessor() { +@@ -367,6 +416,35 @@ public class SynchedEntityData { + return this.initialValue.equals(this.value); + } + ++ // Slice start ++ public void setForeignValue(T foreignValue) { ++ this.foreignValue = foreignValue; ++ this.foreignDirty = true; ++ } ++ ++ public @Nullable T getForeignValue() { ++ return foreignValue; ++ } ++ ++ public boolean isDirty(boolean foreign) { ++ if (foreign) { ++ //There must be a foreign value in order for this to be dirty, otherwise we consider this a normal ++ //value and check the normal dirty flag. ++ return foreignValue == null || this.foreignDirty; ++ } ++ ++ return this.dirty; ++ } ++ ++ public void setForeignDirty(boolean dirty) { ++ this.foreignDirty = dirty; ++ } ++ ++ public SynchedEntityData.DataValue copy(boolean foreign) { ++ return SynchedEntityData.DataValue.create(this.accessor, this.accessor.getSerializer().copy((foreign && this.foreignValue != null ? this.foreignValue : this.value))); ++ } ++ // Slice end ++ + public SynchedEntityData.DataValue value() { + return SynchedEntityData.DataValue.create(this.accessor, this.value); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 83c4639c2bdca4dc4281d9f5eca104af3063bfa5..f7d8aaededd39ce52a9d0105f66fd759635b5288 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -148,7 +148,7 @@ public class ServerEntity { + } + } + +- if (this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { ++ if (this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isForeignDirty()) { // Slice + int i; + int j; + +@@ -395,11 +395,23 @@ public class ServerEntity { + } + + // Sakura start - visibility api +- private void broadcastEntityData(List> packedValues) { ++ private void broadcastEntityData(SynchedEntityData datawatcher, List> packedValues) { + Packet packet0 = new ClientboundSetEntityDataPacket(this.entity.getId(), packedValues); + Packet packet1 = null; + +- if (this.entity.isPrimedTNT) { ++ // Slice start ++ if (this.entity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.connection.send(packet0); ++ } ++ ++ packedValues = datawatcher.packForeignDirty(packedValues); ++ ++ if (packedValues != null) { ++ packet0 = new ClientboundSetEntityDataPacket(this.entity.getId(), packedValues); ++ } ++ ++ if (packedValues != null && this.entity.isPrimedTNT) { ++ // Slice end + var copyOfDirtyItems = Lists.newArrayList(packedValues); + copyOfDirtyItems.removeIf((data) -> data.id() == 8); + +@@ -426,7 +438,7 @@ public class ServerEntity { + + if (list != null) { + this.trackedDataValues = datawatcher.getNonDefaultValues(); +- this.broadcastEntityData(list); // Sakura - visibility api ++ this.broadcastEntityData(datawatcher, list); // Sakura - visibility api + } + + if (this.entity instanceof LivingEntity) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c35ca0785ecc268fb7e20dee56d42bdadca41ee6..217eba9f4860cf1e67d307ba8cdb99430c7469f8 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3321,7 +3321,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID); + return; + } +- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount()); ++ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount(), getMaxAirSupply()); // Slice + // CraftBukkit end + } + +@@ -3330,7 +3330,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void setTicksFrozen(int frozenTicks) { +- this.entityData.set(Entity.DATA_TICKS_FROZEN, frozenTicks); ++ this.entityData.set(Entity.DATA_TICKS_FROZEN, (frozenTicks / 10) * 10); // Slice + } + + public float getPercentFrozen() { +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 9a7956befc346e1b58f064213800fd099a052fc6..6a794d672621d31f4fc7b3c44907fe3976420ca1 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -427,7 +427,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Override + public void setRemainingPersistentAngerTime(int angerTime) { +- this.entityData.set(Bee.DATA_REMAINING_ANGER_TIME, angerTime); ++ this.entityData.set(Bee.DATA_REMAINING_ANGER_TIME, angerTime, (angerTime / 20) * 20); // Slice + } + + @Nullable +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 4c328a511ff6c0e6b73ef9701c82373e02c12830..fbeb52a49b791f992af19c7d69ba44b820541b09 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -172,7 +172,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + } + + public void setFuse(int fuse) { +- this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse); ++ this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse, (fuse / 10) * 10); // Slice + } + + public int getFuse() { +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 d58b4c0dbe651b5068212e5f14dce3164ee520f5..c69660c3c49e6d21e8d31dbe16906bd152b61d3f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -649,7 +649,7 @@ public abstract class Player extends LivingEntity { + public void increaseScore(int score) { + int j = this.getScore(); + +- this.entityData.set(Player.DATA_SCORE_ID, j + score); ++ this.entityData.set(Player.DATA_SCORE_ID, j + score, 0); // Slice + } + + public void startAutoSpinAttack(int riptideTicks) { +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 5c07da62c82bc70138f6cb5007629d6974be69ac..974563607f6731e5c352fd03663d069ea888b7ef 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -973,7 +973,7 @@ public class Boat extends Entity implements VariantHolder { + } + + private void setBubbleTime(int wobbleTicks) { +- this.entityData.set(Boat.DATA_ID_BUBBLE_TIME, wobbleTicks); ++ this.entityData.set(Boat.DATA_ID_BUBBLE_TIME, wobbleTicks, (wobbleTicks / 5) * 5); // Slice + } + + private int getBubbleTime() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 9ac026a464f8b5db5fe7543b71cd5bcaa3f65f70..fab6b511319a9891277fd001cfa06a6c802c45bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2606,7 +2606,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.sendHealthUpdate(); + } + } +- this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth()); ++ this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth(), isDead() ? 0f : 20f); // Slice + + this.getHandle().maxHealthCache = getMaxHealth(); + } diff --git a/patches/server/0013-Use-Optimised-TrackedEntityMap.patch b/patches/server/0013-Use-Optimised-TrackedEntityMap.patch new file mode 100644 index 0000000..2d701cc --- /dev/null +++ b/patches/server/0013-Use-Optimised-TrackedEntityMap.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Thu, 3 Aug 2023 12:54:52 +0100 +Subject: [PATCH] Use Optimised TrackedEntityMap + + +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java +index 8f4ac8acd2e0752e7a615d152b8047d790947b9f..1394adbe98a24f74fc7892e1b39ab1502fe082c1 100644 +--- a/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java ++++ b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java +@@ -17,8 +17,9 @@ public class TrackedEntityChunkMap extends Int2ObjectOpenHashMap +Date: Thu, 14 Oct 2021 19:16:49 +0100 +Subject: [PATCH] Copy EntityList methods to BasicEntityList + + +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index 7e8dc9e8f381abfdcce2746edc93122d623622d1..2d79633d86007c7d40eecf5f9271fa3f351b72b5 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -25,6 +25,8 @@ import java.util.Iterator; + import java.util.List; + import java.util.function.Predicate; + ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; // Sakura ++ + public final class ChunkEntitySlices { + + protected final int minSection; +@@ -303,6 +305,13 @@ public final class ChunkEntitySlices { + + protected static final class BasicEntityList { + ++ // Sakura start - copy entitylist methods across ++ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ { ++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ // Sakura end ++ + protected static final Entity[] EMPTY = new Entity[0]; + protected static final int DEFAULT_CAPACITY = 4; + +@@ -325,55 +334,52 @@ public final class ChunkEntitySlices { + return this.size; + } + +- private void resize() { +- if (this.storage == EMPTY) { +- this.storage = (E[])new Entity[DEFAULT_CAPACITY]; +- } else { +- this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); +- } +- } +- ++ // Sakura start - copy entitylist methods across + public void add(final E entity) { +- final int idx = this.size++; +- if (idx >= this.storage.length) { +- this.resize(); +- this.storage[idx] = entity; +- } else { +- this.storage[idx] = entity; ++ final int count = this.size; ++ final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return; // already in this list + } +- } + +- public int indexOf(final E entity) { +- final E[] storage = this.storage; ++ E[] list = this.storage; + +- for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { +- if (storage[i] == entity) { +- return i; +- } ++ if (list.length == count) { ++ // resize required ++ list = this.storage = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + +- return -1; ++ list[count] = entity; ++ this.size = count + 1; ++ } ++ ++ public int indexOf(final E entity) { ++ return this.entityToIndex.getOrDefault(entity.getId(), -1); + } + + public boolean remove(final E entity) { +- final int idx = this.indexOf(entity); +- if (idx == -1) { ++ final int index = this.entityToIndex.remove(entity.getId()); ++ if (index == Integer.MIN_VALUE) { + return false; + } + +- final int size = --this.size; +- final E[] storage = this.storage; +- if (idx != size) { +- System.arraycopy(storage, idx + 1, storage, idx, size - idx); ++ // move the entity at the end to this index ++ final int endIndex = --this.size; ++ final E end = this.storage[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.entityToIndex.put(end.getId(), index); // update index + } +- +- storage[size] = null; ++ this.storage[index] = end; ++ this.storage[endIndex] = null; + + return true; + } + + public boolean has(final E entity) { +- return this.indexOf(entity) != -1; ++ return this.entityToIndex.containsKey(entity.getId()); ++ // Sakura end + } + } + diff --git a/patches/server/0015-Optimise-ClassInstanceMultiMap-removals.patch b/patches/server/0015-Optimise-ClassInstanceMultiMap-removals.patch new file mode 100644 index 0000000..d0865b6 --- /dev/null +++ b/patches/server/0015-Optimise-ClassInstanceMultiMap-removals.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 15 Oct 2021 18:49:48 +0100 +Subject: [PATCH] Optimise ClassInstanceMultiMap removals + + +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index 5fb7573022c5af775b2e737dcd05c53cd9ae39ec..a3e5bf8355bbe8d4b8ee687abdb49a04fb39ae75 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -15,7 +15,7 @@ import java.util.stream.Collectors; + public class ClassInstanceMultiMap extends AbstractCollection { + private final Map, List> byClass = Maps.newHashMap(); + private final Class baseClass; +- private final List allInstances = Lists.newArrayList(); ++ private final List allInstances = new me.samsuik.sakura.utils.collections.UnorderedIndexedList<>(); // Sakura + + public ClassInstanceMultiMap(Class elementType) { + this.baseClass = elementType; +@@ -59,7 +59,15 @@ public class ClassInstanceMultiMap extends AbstractCollection { + throw new IllegalArgumentException("Don't know how to search for " + type); + } else { + List list = this.byClass.computeIfAbsent(type, (typeClass) -> { +- return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); ++ // Sakura start ++ List result = new me.samsuik.sakura.utils.collections.UnorderedIndexedList<>(); ++ for (T allInstance : this.allInstances) { ++ if (typeClass.isInstance(allInstance)) { ++ result.add(allInstance); ++ } ++ } ++ return result; ++ // Sakura end + }); + return (Collection) Collections.unmodifiableCollection(list); // Sakura - decompile fix + } diff --git a/patches/server/0016-Add-utility-methods-to-EntitySlices.patch b/patches/server/0016-Add-utility-methods-to-EntitySlices.patch new file mode 100644 index 0000000..9e254f5 --- /dev/null +++ b/patches/server/0016-Add-utility-methods-to-EntitySlices.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Thu, 3 Aug 2023 13:48:27 +0100 +Subject: [PATCH] Add utility methods to EntitySlices + + +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index 2d79633d86007c7d40eecf5f9271fa3f351b72b5..d917a19c838ed3d74322abd85e1f737e852b5d7b 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -251,6 +251,12 @@ public final class ChunkEntitySlices { + + return true; + } ++ ++ // Sakura start ++ public Entity[] getSectionEntities(int sectionY) { ++ return this.allEntities.getSectionEntities(sectionY); ++ } ++ // Sakura end + + public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + this.hardCollidingEntities.getEntities(except, box, into, predicate); +@@ -429,6 +435,18 @@ public final class ChunkEntitySlices { + this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); + } + } ++ ++ // Sakura start ++ public Entity[] getSectionEntities(int sectionY) { ++ var list = entitiesBySection[sectionY - this.manager.minSection]; ++ ++ if (list != null) { ++ return list.storage; ++ } ++ ++ return new Entity[0]; ++ } ++ // Sakura end + + public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + if (this.count == 0) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9e49196f7038c710e850005b6ad96c4eafca0f2e..a29d41bf162c923e3b3b15e7500e0c8693908866 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -218,6 +218,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public final it.unimi.dsi.fastutil.longs.Long2IntMap minimalTNT = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); // Sakura - visibility api + ++ // Sakura start ++ public Entity[] getSectionEntities(int chunkX, int chunkY, int chunkZ) { ++ var slices = ((ServerLevel)this).getEntityLookup().getChunk(chunkX, chunkZ); ++ if (slices == null) { ++ return new Entity[0]; ++ } ++ return slices.getSectionEntities(chunkY); ++ } ++ // Sakura end ++ + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper diff --git a/patches/server/0017-Store-Entity-Data-State.patch b/patches/server/0017-Store-Entity-Data-State.patch new file mode 100644 index 0000000..9d5a2b1 --- /dev/null +++ b/patches/server/0017-Store-Entity-Data-State.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Wed, 16 Aug 2023 22:34:49 +0100 +Subject: [PATCH] Store Entity Data/State + + +diff --git a/src/main/java/me/samsuik/sakura/entity/EntityState.java b/src/main/java/me/samsuik/sakura/entity/EntityState.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c9f2c5ae57878283e8c8bc3847fe63b98f4e8d10 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/EntityState.java +@@ -0,0 +1,41 @@ ++package me.samsuik.sakura.entity; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++ ++import java.util.Optional; ++ ++public record EntityState(Vec3 position, Vec3 momentum, AABB bb, Vec3 stuckSpeed, Optional supportingPos, boolean onGround, float fallDistance) { ++ ++ public static EntityState of(Entity entity) { ++ return new EntityState(entity.position(), entity.getDeltaMovement(), entity.getBoundingBox(), entity.stuckSpeedMultiplier(), entity.mainSupportingBlockPos, entity.onGround(), entity.fallDistance); ++ } ++ ++ public void apply(Entity entity) { ++ entity.setPos(position); ++ entity.setDeltaMovement(momentum); ++ entity.setBoundingBox(bb); ++ // null here is only safe for our use case (tnt and sand) ++ //noinspection DataFlowIssue ++ entity.makeStuckInBlock(null, stuckSpeed); ++ entity.onGround = onGround; ++ entity.mainSupportingBlockPos = supportingPos; ++ entity.fallDistance = fallDistance; ++ } ++ ++ public void position(Entity entity) { ++ entity.setPos(position); ++ entity.setBoundingBox(bb); ++ } ++ ++ public boolean isCurrentState(Entity entity) { ++ return entity.position().equals(position) ++ && entity.getDeltaMovement().equals(momentum); ++ // 1.14+ versions seem to correct morphed bounding boxes after a gametick. ++ // If there are any related issues uncomment this line of code. ++ // && entity.getBoundingBox().equals(bb); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 217eba9f4860cf1e67d307ba8cdb99430c7469f8..7e906ad87ae48942445b6ad1fa22ab9442c8943e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -531,6 +531,34 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // Paper end - make end portalling safe + public boolean isPrimedTNT; // Sakura + public boolean isFallingBlock; // Sakura ++ // Sakura start - entity state (from start of tick) ++ private @Nullable me.samsuik.sakura.entity.EntityState entityState = null; ++ ++ public Vec3 stuckSpeedMultiplier() { ++ return stuckSpeedMultiplier; ++ } ++ ++ public void storeEntityState() { ++ entityState = me.samsuik.sakura.entity.EntityState.of(this); ++ } ++ ++ public @Nullable me.samsuik.sakura.entity.EntityState entityState() { ++ return entityState; ++ } ++ ++ public boolean compareState(Entity to) { ++ return to.entityState() != null && to.entityState().isCurrentState(this); ++ } ++ ++ public long getPackedOrigin() { ++ var v = getOriginVector(); ++ if (v == null) return Long.MIN_VALUE; ++ // Note: vector#getBlockN may not be 100% exact ++ // If there's any future issues at let's say 0.999999... ++ // giving an incorrect position change it to Mth instead. ++ return BlockPos.asLong(v.getBlockX(), v.getBlockY(), v.getBlockZ()); ++ } ++ // Sakura end + + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index a29d41bf162c923e3b3b15e7500e0c8693908866..6d9a15a1c1faff57103757b6ff71f38e4713ef71 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1325,6 +1325,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public void guardEntityTick(Consumer tickConsumer, T entity) { + try { ++ entity.storeEntityState(); // Sakura - store entity state + tickConsumer.accept(entity); + MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick + } catch (Throwable throwable) { diff --git a/patches/server/0018-Merge-Cannon-Entities.patch b/patches/server/0018-Merge-Cannon-Entities.patch new file mode 100644 index 0000000..40558ad --- /dev/null +++ b/patches/server/0018-Merge-Cannon-Entities.patch @@ -0,0 +1,619 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Sat, 9 Sep 2023 18:39:15 +0100 +Subject: [PATCH] Merge Cannon Entities + + +diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..794547b36f0780b4dd300fc162cd9b7018c38edb +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java +@@ -0,0 +1,155 @@ ++package me.samsuik.sakura.entity.merge; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.entity.Entity; ++ ++public class MergeHistory { ++ ++ // packed position -> known merging information ++ private final Long2ObjectMap mergeDataMap = new Long2ObjectOpenHashMap<>(); ++ private MergeData mergeData = null; ++ ++ public MergeData retrievePositions(Entity entity) { ++ var origin = entity.getPackedOrigin(); ++ ++ if (mergeData != null && mergeData.knownPositions().contains(origin)) { ++ return mergeData; ++ } ++ ++ return mergeData = mergeDataMap.get(origin); ++ } ++ ++ public void markPositions(Entity entity) { ++ var mergeList = entity.getMergeList(); ++ var origin = entity.getPackedOrigin(); ++ ++ // I apologise for the lambda parameter name in advance ++ var data = mergeDataMap.computeIfAbsent(origin, (OwO) -> new MergeData( ++ // Known entity positions that have been able to merge ++ // This is used for non-strict merging. ++ new LongOpenHashSet(), ++ // First copy of the previous positions that is retained. ++ // This is used for on spawn (aot) merging. ++ // Retaining means if the collection you're comparing doesn't the same elements the rest gets yeeted. ++ // We also make use of a _reasonable_ threshold before on spawn merging to reduce abuse and breakage. ++ new LongOpenHashSet(), ++ new EntityTable(Math.min(mergeList.size() * 2, 512)), ++ // todo: allow configuring expiry and threshold ++ new Expiry(MinecraftServer.currentTickLong, 200), ++ new Threshold(MinecraftServer.currentTickLong, 12, 200) ++ )); ++ ++ // Refresh expiry ++ data.expiry().refresh(MinecraftServer.currentTickLong); ++ ++ var insert = data.knownPositions().isEmpty(); ++ var positions = new LongOpenHashSet((mergeList.size() + 1) / 2); ++ ++ positions.add(entity.getPackedOrigin()); ++ ++ for (var mergedEntity : mergeList) { ++ positions.add(mergedEntity.getPackedOrigin()); ++ } ++ ++ // todo: if tnt spread is enabled double the threshold above then make the first half of the threshold inserting known positions. ++ // ^ This can allow better merging of randomised tnt for the compromise of it taking longer to merge on spawn. ++ // ^ There is an uncommon design that uses a single booster at the back and pushes all the tnt forward. ++ // ^ Using a chest as an offset means tnt alignment doesn't matter so people get away with spread but can make merging difficult. ++ if (insert) { ++ data.retainedPositions().addAll(positions); ++ } else { ++ data.retainedPositions().retainAll(positions); ++ } ++ ++ data.knownPositions().addAll(positions); ++ } ++ ++ public void expire(long tick) { ++ // clear this every tick ++ mergeData = null; ++ ++ // only expire every 20 ticks ++ if (tick % 20 != 0) return; ++ ++ // using a linked hashmap isn't applicable here as an optimisation ++ // because we allow the spawn positions to "refresh" this would create a memory leak ++ mergeDataMap.values().removeIf((data) -> data.expiry().isExpired(tick)); ++ } ++ ++ public record MergeData(LongSet knownPositions, LongSet retainedPositions, EntityTable table, Expiry expiry, Threshold threshold) { ++ public boolean hasPassed() { ++ return threshold.hasPassed(MinecraftServer.currentTickLong); ++ } ++ ++ public Entity findFirstAtPosition(Entity entity) { ++ var found = table.locate(entity); ++ ++ if (found != null && found.getId() < entity.getId() && knownPositions.contains(found.getPackedOrigin()) && !found.isRemoved() && entity.compareState(found)) { ++ return found; ++ } ++ ++ return null; ++ } ++ } ++ ++ private static class EntityTable { ++ private final Entity[] entities; ++ private final int mask; ++ ++ EntityTable(int size) { ++ var n = HashCommon.nextPowerOfTwo(size - 1); ++ entities = new Entity[n]; ++ mask = n - 1; ++ } ++ ++ Entity locate(Entity entity) { ++ var pos = entity.blockPosition().hashCode(); ++ var key = pos & mask; ++ var found = entities[key]; ++ entities[key] = entity; ++ return found; ++ } ++ } ++ ++ private static class Threshold { ++ private final long existence; // tick when this was created ++ private final int thresholdAttempts; ++ private final long thresholdAge; ++ private int attempts; ++ ++ Threshold(long tick, int attempts, long age) { ++ existence = tick; ++ thresholdAttempts = attempts; ++ thresholdAge = age; ++ } ++ ++ boolean hasPassed(long tick) { ++ return ++attempts >= thresholdAttempts ++ || tick - existence >= thresholdAge; ++ } ++ } ++ ++ private static class Expiry { ++ private long expireAt; ++ private final int inc; ++ ++ Expiry(long tick, int inc) { ++ expireAt = tick + inc; ++ this.inc = inc; ++ } ++ ++ void refresh(long tick) { ++ expireAt = tick + inc; ++ } ++ ++ boolean isExpired(long tick) { ++ return tick >= expireAt; ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java b/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f43264f436f5cea06b892e11b7f4864d321c49e +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java +@@ -0,0 +1,30 @@ ++package me.samsuik.sakura.utils.collections; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import net.minecraft.world.entity.Entity; ++ ++import java.util.Arrays; ++ ++public class EntityTable { ++ ++ private final Entity[] entities; ++ private final int mask; ++ ++ public EntityTable(int capacity) { ++ capacity = HashCommon.nextPowerOfTwo(capacity - 1); ++ entities = new Entity[capacity]; ++ mask = capacity - 1; ++ } ++ ++ public Entity put(Entity entity) { ++ var pos = entity.blockPosition().hashCode() & mask; ++ var found = entities[pos]; ++ entities[pos] = entity; ++ return found; ++ } ++ ++ public void clear() { ++ Arrays.fill(entities, null); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index eef411f838ecdeff0d4052fac22900e4ad87ceb5..4bc68b3145f42f5a432e1e897b3f41606735afd1 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1579,6 +1579,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + if (!entity.isRemoved()) { + if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed +@@ -896,6 +897,15 @@ public class ServerLevel extends Level implements WorldGenLevel { + entity.stopRiding(); + } + ++ // Sakura start ++ var previous = previousEntity[0]; ++ if (entity.isMergeableType(previous) && entity.tryMergeInto(previous)) { ++ return; ++ } else { ++ previousEntity[0] = entity; ++ } ++ // Sakura end ++ + gameprofilerfiller.push("tick"); + this.guardEntityTick(this::tickNonPassenger, entity); + gameprofilerfiller.pop(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7e906ad87ae48942445b6ad1fa22ab9442c8943e..311891f079d2136a18f62478bd289c91ff515772 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -559,6 +559,105 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return BlockPos.asLong(v.getBlockX(), v.getBlockY(), v.getBlockZ()); + } + // Sakura end ++ // Sakura start - cannon entity merging ++ // List of merged entities, should be naturally sorted (oldest -> youngest) ++ private final List mergeList = new java.util.ArrayList<>(1); ++ private @Nullable me.samsuik.sakura.entity.merge.MergeHistory.MergeData originData = null; ++ private me.samsuik.sakura.entity.merge.MergeLevel mergeLevel; ++ protected int stacked = 1; // default ++ ++ public final me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() { ++ return mergeLevel; ++ } ++ ++ public final void setMergeLevel(me.samsuik.sakura.entity.merge.MergeLevel level) { ++ mergeLevel = level; ++ } ++ ++ public final int getStacked() { ++ return stacked; ++ } ++ ++ public final void setStacked(int stack) { ++ stacked = stack; ++ } ++ ++ public List getMergeList() { ++ return mergeList; ++ } ++ ++ private boolean isSafeToSpawnMerge(Entity entity) { ++ return tickCount == 1 && originData != null ++ && originData.hasPassed() // on spawn safety delay has passed ++ && originData == entity.originData // make sure it's the same group ++ && originData.retainedPositions().contains(entity.getPackedOrigin()); ++ } ++ ++ public boolean isMergeableType(@Nullable Entity previous) { ++ return false; ++ } ++ ++ public final boolean tryMergeInto(@Nullable Entity entity) { ++ if (mergeLevel.atLeast(me.samsuik.sakura.entity.merge.MergeLevel.NON_STRICT) && tickCount == 0) { ++ originData = level.mergeHistory.retrievePositions(this); ++ } ++ ++ Entity mergeEntity = null; ++ ++ if (entity == null || entity.getType() != getType()) { ++ // first entity in the tick loop, we have to let it into this method so that we can retrieve the originData ++ return false; ++ } else if (mergeLevel.atLeast(me.samsuik.sakura.entity.merge.MergeLevel.SPAWN) && entity.isSafeToSpawnMerge(this)) { ++ // On spawn merging, this merges entities immediately upon spawning after ++ // it is considered "safe". We try to make sure it is safe by only retaining ++ // positions that do not change when we're collecting information. ++ mergeEntity = entity; ++ } else { ++ // Strict, simple merging ++ // This merges entities that are in the exact same state and sequential. ++ // Sane for most use cases but as it is merging entities plugins may misbehave. ++ if (mergeLevel.atLeast(me.samsuik.sakura.entity.merge.MergeLevel.STRICT) && compareState(entity)) { ++ mergeEntity = entity; ++ } ++ ++ // Non strict merging algorithm uses information collected after entities die ++ // to be able to perform more aggressive merging by already knowing the OOE. ++ if (mergeLevel.atLeast(me.samsuik.sakura.entity.merge.MergeLevel.NON_STRICT) && mergeEntity == null && originData != null) { ++ mergeEntity = originData.findFirstAtPosition(this); ++ } ++ } ++ ++ if (mergeEntity != null && isSafeToMergeInto(mergeEntity)) { ++ mergeInto(mergeEntity); ++ return true; ++ } ++ ++ return false; ++ } ++ ++ protected void respawn() {} ++ ++ protected boolean isSafeToMergeInto(Entity entity) { ++ return false; ++ } ++ ++ private void mergeInto(Entity entity) { ++ entity.mergeList.add(this); ++ entity.mergeList.addAll(mergeList); ++ entity.stacked += stacked; ++ ++ mergeList.clear(); // clear the list to stop our tracking when merging ++ stacked = 0; // prevent any possible duplication ++ ++ // update api handle, this is so cannondebug can function ++ //noinspection ConstantValue ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(entity); ++ } ++ ++ discard(); ++ } ++ // Sakura end + + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); +@@ -607,6 +706,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.getEntityData().registrationLocked = true; // Spigot + this.setPos(0.0D, 0.0D, 0.0D); + this.eyeHeight = this.getEyeHeight(net.minecraft.world.entity.Pose.STANDING, this.dimensions); ++ this.mergeLevel = level.sakuraConfig().cannons.mergeLevel; // Sakura + } + + public boolean isColliding(BlockPos pos, BlockState state) { +@@ -2454,6 +2554,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + nbt.putBoolean("Paper.FreezeLock", true); + } + // Paper end ++ // Sakura start ++ if (stacked > 0) { ++ nbt.putInt("Sakura.Stacked", stacked); ++ } ++ // Sakura end + return nbt; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); +@@ -2622,6 +2727,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + freezeLocked = nbt.getBoolean("Paper.FreezeLock"); + } + // Paper end ++ // Sakura start ++ if (nbt.contains("Sakura.Stacked")) { ++ stacked = nbt.getInt("Sakura.Stacked"); ++ } ++ // Sakura end + + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); +@@ -4784,6 +4894,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return; + } + // Paper end - rewrite chunk system ++ // Sakura start ++ if (reason == RemovalReason.DISCARDED && !mergeList.isEmpty()) { ++ level.mergeHistory.markPositions(this); ++ } ++ // Sakura end + final boolean alreadyRemoved = this.removalReason != null; + if (this.removalReason == null) { + this.removalReason = reason; +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 2e0f38ffad09b6afbea74205b89beb774e779545..2afcb3ebdfba545d7c1d73fd0aed486c1f8bf6ae 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -132,6 +132,58 @@ public class FallingBlockEntity extends Entity { + return !this.isRemoved(); + } + ++ // Sakura start - cannon entity merging ++ @Override ++ public boolean isMergeableType(@Nullable Entity previous) { ++ return previous == null || !isRemoved() && !previous.isRemoved(); ++ } ++ ++ @Override ++ protected boolean isSafeToMergeInto(Entity entity) { ++ return entity instanceof FallingBlockEntity fbe ++ && fbe.blockState.equals(blockState) ++ && fbe.time - 1 == time; // todo: special case in case on spawn isn't used ++ } ++ ++ @Override ++ protected void respawn() { ++ while (stacked-- > 1) { ++ // create a temporary falling block entity ++ var fallingBlock = new FallingBlockEntity(EntityType.FALLING_BLOCK, level()); ++ ++ // use our the previous state ++ entityState().apply(fallingBlock); ++ fallingBlock.time = time - 1; ++ ++ // and tick ++ fallingBlock.tick(); ++ ++ // Well, this can actually happen. ++ // If you horizontal or rectangle stack sand into a b36 this condition will be met. ++ // This could break some suspicious render queuing setups relying on horizontal stacking ++ // and keeping sand in b36 using pistons pushing back and forth. ++ if (!fallingBlock.isRemoved()) { ++ fallingBlock.stacked = stacked; ++ level().addFreshEntity(fallingBlock); ++ break; ++ } ++ } ++ } ++ ++ @Nullable ++ public ItemEntity spawnAtLocation(ItemLike item) { ++ // This is to prevent sand continuing to respawn incase it broke. ++ ItemEntity itemEntity = null; ++ ++ for (int i = 0; i < stacked; ++i) { ++ itemEntity = super.spawnAtLocation(item); ++ } ++ ++ stacked = 1; ++ return itemEntity; ++ } ++ // Sakura end ++ + @Override + public void tick() { + // Paper start - fix sand duping +@@ -214,6 +266,7 @@ public class FallingBlockEntity extends Entity { + if (this.level().setBlock(blockposition, this.blockState, 3)) { + ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition))); + this.discard(); ++ this.respawn(); // Sakura + if (block instanceof Fallable) { + ((Fallable) block).onLand(this.level(), blockposition, this.blockState, iblockdata, this); + } +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 fbeb52a49b791f992af19c7d69ba44b820541b09..02ef6ca32f3de52e921fdcf3f0f572ce7afef318 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -63,6 +63,60 @@ public class PrimedTnt extends Entity implements TraceableEntity { + return !this.isRemoved(); + } + ++ // Sakura start - cannon entity merging ++ @Override ++ public boolean isMergeableType(@Nullable Entity previous) { ++ return previous == null || !isRemoved() && !previous.isRemoved(); ++ } ++ ++ @Override ++ protected boolean isSafeToMergeInto(Entity entity) { ++ return entity instanceof PrimedTnt tnt ++ && tnt.getFuse() + 1 == getFuse() ++ // required to prevent issues with powdered snow ++ && (tnt.entityState().fallDistance() == 0.0f && fallDistance == 0.0f ++ || tnt.entityState().fallDistance() > 2.5f && fallDistance > 2.5f); ++ } ++ ++ @Override ++ protected void respawn() { ++ if (stacked <= 1) return; ++ ++ // we create a temporary entity that will be affected by each explosion ++ // this allows us to only keep one entity in the world in an attempt to ++ // minimise complexity of stacked tnt explosions. ++ var tnt = new PrimedTnt(level(), 0, 0, 0, owner); ++ ++ // Copy our pre-tick state to the temporary entity ++ entityState().apply(tnt); ++ ++ // add the entity to the world and chunk ++ level().addFreshEntity(tnt); ++ ++ // Some bad plugins may change tnt momentum while we are respawning ++ // ex: a plugin that sets tnt momentum to 0 upon spawning ++ tnt.setDeltaMovement(entityState().momentum()); ++ ++ for (int i = stacked - 1; i >= 1; --i) { ++ // make sure this entity cannot explode unexpectedly ++ setFuse(100); ++ stacked = 0; ++ ++ // explode! ++ explode(); ++ ++ // clone state from temporary entity ++ tnt.storeEntityState(); ++ tnt.entityState().apply(this); ++ ++ // tick, this is only to move the entity and apply physics. ++ tick(); ++ } ++ ++ tnt.discard(); ++ } ++ // Sakura end ++ + @Override + public void tick() { + if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot +@@ -88,6 +142,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + if (i <= 0) { + // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event + // this.discard(); ++ this.respawn(); // Sakura + if (!this.level().isClientSide) { + this.explode(); + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 6d9a15a1c1faff57103757b6ff71f38e4713ef71..2f72a059b051bb3d35e0844c6b7ae3b6e2655e36 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -227,6 +227,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return slices.getSectionEntities(chunkY); + } + // Sakura end ++ public final me.samsuik.sakura.entity.merge.MergeHistory mergeHistory = new me.samsuik.sakura.entity.merge.MergeHistory(); // Sakura + + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index a39694a27e362312eb42a29fd7c833f9c7437d46..55bfb0afc0e4e9f1ce2dd15f729bee61822c5afc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -14,6 +14,28 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + super(server, entity); + } + ++ // Sakura start ++ @Override ++ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() { ++ return getHandle().getMergeLevel(); ++ } ++ ++ @Override ++ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) { ++ getHandle().setMergeLevel(level); ++ } ++ ++ @Override ++ public int getStacked() { ++ return getHandle().getStacked(); ++ } ++ ++ @Override ++ public void setStacked(int stacked) { ++ getHandle().setStacked(stacked); ++ } ++ // Sakura end ++ + @Override + public FallingBlockEntity getHandle() { + return (FallingBlockEntity) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +index dc13deb1cea14f0650b292ddb6437fadefc0b8be..e9f8abb514654b87ec4f35b90fff04818a05780d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +@@ -12,6 +12,28 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { + super(server, entity); + } + ++ // Sakura start ++ @Override ++ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() { ++ return getHandle().getMergeLevel(); ++ } ++ ++ @Override ++ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) { ++ getHandle().setMergeLevel(level); ++ } ++ ++ @Override ++ public int getStacked() { ++ return getHandle().getStacked(); ++ } ++ ++ @Override ++ public void setStacked(int stacked) { ++ getHandle().setStacked(stacked); ++ } ++ // Sakura end ++ + @Override + public float getYield() { + return this.getHandle().yield; diff --git a/patches/server/0019-Optimised-Explosions.patch b/patches/server/0019-Optimised-Explosions.patch new file mode 100644 index 0000000..06cc5cb --- /dev/null +++ b/patches/server/0019-Optimised-Explosions.patch @@ -0,0 +1,979 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Thu, 12 Oct 2023 16:23:31 +0100 +Subject: [PATCH] Optimised Explosions + + +diff --git a/src/main/java/me/samsuik/sakura/explosion/DensityCache.java b/src/main/java/me/samsuik/sakura/explosion/DensityCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3f6f34cc617efaad420485a7f613cfcad88e3783 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/explosion/DensityCache.java +@@ -0,0 +1,130 @@ ++package me.samsuik.sakura.explosion; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++ ++import javax.annotation.Nullable; ++ ++public class DensityCache { ++ ++ private final Int2ObjectMap densityMap = new Int2ObjectOpenHashMap<>(); ++ ++ public @Nullable Density retrieveCache(int key) { ++ return densityMap.get(key); ++ } ++ ++ public void createCache(int key, Entity entity, Vec3 vec3d, float density) { ++ densityMap.put(key, new Density(entity.getBoundingBox(), vec3d, density)); ++ } ++ ++ public void clear() { ++ densityMap.clear(); ++ } ++ ++ public static int createKey(Entity entity, Vec3 vec3d) { ++ int hash = Mth.floor(vec3d.x); ++ hash = 31 * hash ^ Mth.floor(vec3d.y); ++ hash = 31 * hash ^ Mth.floor(vec3d.z); ++ hash = 31 * hash ^ Mth.floor(entity.getX()); ++ hash = 31 * hash ^ Mth.floor(entity.getY()); ++ hash = 31 * hash ^ Mth.floor(entity.getZ()); ++ return hash; ++ } ++ ++ public static final class Density { ++ private AABB source; ++ private AABB entity; ++ private AABB obstruction; ++ private final float density; ++ private final boolean expand; ++ ++ Density(AABB bb, Vec3 p, float d) { ++ entity = bb; ++ source = new AABB(p, p); ++ density = d; ++ expand = density == 0.0f || density == 1.0f; ++ } ++ ++ public boolean isExpandable() { ++ return expand; ++ } ++ ++ public float density() { ++ return density; ++ } ++ ++ public void obscure(Vec3 p) { ++ if (obstruction == null) { ++ obstruction = new AABB(p, p); ++ } else { ++ obstruction = expandBBWithVec3(p, obstruction); ++ } ++ } ++ ++ public boolean isObscured(Vec3 p) { ++ return obstruction != null ++ && obstruction.minX <= p.x && obstruction.maxX >= p.x ++ && obstruction.minY <= p.y && obstruction.maxY >= p.y ++ && obstruction.minZ <= p.z && obstruction.maxZ >= p.z; ++ } ++ ++ public void expand(AABB bb, Vec3 p) { ++ entity = entity.minmax(bb); ++ source = expandBBWithVec3(p, source); ++ } ++ ++ public boolean has(AABB bb, Vec3 p) { ++ return isBBWithinBounds(bb) && isPointWithinBounds(p); ++ } ++ ++ public boolean has(Vec3 p) { ++ return isPointWithinBounds(p); ++ } ++ ++ private boolean isBBWithinBounds(AABB bb) { ++ return entity.minX <= bb.minX && entity.maxX >= bb.maxX ++ && entity.minY <= bb.minY && entity.maxY >= bb.maxY ++ && entity.minZ <= bb.minZ && entity.maxZ >= bb.maxZ; ++ } ++ ++ private boolean isPointWithinBounds(Vec3 p) { ++ return source.minX <= p.x && source.maxX >= p.x ++ && source.minY <= p.y && source.maxY >= p.y ++ && source.minZ <= p.z && source.maxZ >= p.z; ++ } ++ ++ private AABB expandBBWithVec3(Vec3 point, AABB what) { ++ double minX = what.minX; ++ double minY = what.minY; ++ double minZ = what.minZ; ++ double maxX = what.maxX; ++ double maxY = what.maxY; ++ double maxZ = what.maxZ; ++ ++ if (point.x < minX) { ++ minX = point.x; ++ } else if (point.x > maxX) { ++ maxX = point.x; ++ } ++ ++ if (point.y < minY) { ++ minY = point.y; ++ } else if (point.y > maxY) { ++ maxY = point.y; ++ } ++ ++ if (point.z < minZ) { ++ minZ = point.z; ++ } else if (point.z > maxZ) { ++ maxZ = point.z; ++ } ++ ++ return new AABB(minX, minY, minZ, maxX, maxY, maxZ); ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/explosion/SakuraExplosion.java b/src/main/java/me/samsuik/sakura/explosion/SakuraExplosion.java +new file mode 100644 +index 0000000000000000000000000000000000000000..34514f41cf20284b449e8ed6b5f96c2d07c62053 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/explosion/SakuraExplosion.java +@@ -0,0 +1,383 @@ ++package me.samsuik.sakura.explosion; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.boss.EnderDragonPart; ++import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.entity.item.PrimedTnt; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.item.enchantment.ProtectionEnchantment; ++import net.minecraft.world.level.Explosion; ++import net.minecraft.world.level.ExplosionDamageCalculator; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/* ++ * Special explosion implementation to take advantage of TNT merging. ++ * ++ * This allows us to introduce more optimisations ++ * * if we have been unable to find any blocks nearby stop searching for blocks ++ * * avoid trying to affect entities that are completely obscured ++ * * reduce range checks for out of range entities ++ * * take better advantage of the block cache in paper ++ * * special case for explosions in the same position ++ * ++ * Unfortunately, this requires duplicating the impact entity section from Explosion. ++ * ++ * This does hurt performance in a "rogue tnt" scenario, tnt that has been spawned ++ * by an explosion destroying a tnt block often in massive blocks of tnt. It is not ++ * realistic to explode a big block of tnt in survival or factions. They only cause ++ * harm to a server and extremely wasteful for resources with minimal impact to terrain. ++ */ ++public class SakuraExplosion extends Explosion { ++ ++ private final Level level; ++ ++ public SakuraExplosion(Level world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, BlockInteraction destructionType) { ++ super(world, entity, damageSource, behavior, x, y, z, power, createFire, destructionType); ++ this.level = world; ++ } ++ ++ @Override ++ public void explode() { ++ PrimedTnt origin = (PrimedTnt) source; ++ List positions = new ArrayList<>(origin.getStacked()); ++ ++ // This is a temporary entity that will be used for movement. ++ PrimedTnt tnt = new PrimedTnt(level, 0, 0, 0, null); ++ AABB bounds = new AABB(x, y, z, x, y, z); ++ ++ origin.entityState().apply(tnt); ++ ++ Vec3 lastMovement = tnt.getDeltaMovement(); ++ ExplosionBlockCache[] blockCache = createBlockCache(); ++ int wrapped = 0; ++ ++ for (int i = 0; i < origin.getStacked() && !wasCanceled; ++i) { ++ if (i > 0) { ++ updatePosition(origin, tnt); ++ } ++ ++ // block at explosion position ++ int blockX = Mth.floor(x); ++ int blockY = Mth.floor(y); ++ int blockZ = Mth.floor(z); ++ Vec3 position = new Vec3(x, y, z); ++ ++ // search for blocks if necessary ++ if (wrapped < 7 + 12) { ++ getToBlow().clear(); ++ ++ long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ExplosionBlockCache center = getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ ++ if (interactsWithBlocks() && isDestructibleBlock(center.blockState) && !isProtectedRegion()) { ++ searchForBlocks(blockCache); ++ } ++ } ++ ++ // keep track of positions and bounds ++ positions.add(position); ++ bounds = bounds.minmax(new AABB(position, position)); ++ ++ Vec3 movement = tnt.getDeltaMovement(); ++ ++ if (wrapped < 7) { ++ // Check if the explosion has wrapped around with swinging on each axis ++ if (movement.x == lastMovement.x || movement.x * lastMovement.x < 0) wrapped |= 1; ++ if (movement.y == lastMovement.y || movement.y * lastMovement.y < 0) wrapped |= 1 << 1; ++ if (movement.z == lastMovement.z || movement.z * lastMovement.z < 0) wrapped |= 1 << 2; ++ } else if (getToBlow().isEmpty() && level.sakuraConfig().cannons.explosion.avoidRedundantBlockSearches) { ++ wrapped++; ++ } else { ++ wrapped = 7; ++ } ++ ++ lastMovement = tnt.getDeltaMovement(); ++ ++ if (i + 1 < origin.getStacked()) { ++ BlockPos.MutableBlockPos mbp = new BlockPos.MutableBlockPos(); ++ impactEntityIdle(tnt, new Entity[0], position, 1, radius * 2.0f, mbp, blockCache); ++ ++ // The purpose of this is to make sure papers blockCache doesn't become ++ // outdated by flushing the map and removing stale entries from the recent ++ // cache array. If there is any case where tnt can provide its self delta ++ // movement and then start moving without blocks this may break stuff to ++ // fix it see the note above or add a boolean to mark the cache as dirty ++ // outside this loop and then invalidate before the final impact entities. ++ if (!getToBlow().isEmpty() && tnt.getDeltaMovement().lengthSqr() <= 64.0) { ++ invalidateBlockCache(blockCache); ++ } ++ ++ // could it be viable to have a configuration option to only ++ // call finalize explosion when blocks are found ++ // may affect plugins that need exact explosion positions ++ super.finalizeExplosion(false); ++ ((ServerLevel) level).notifyPlayersOfExplosion(x, y, z, radius, this); ++ } else { ++ locateAndImpactEntities(positions, bounds, blockCache); ++ } ++ } ++ ++ clearBlockCache(); ++ } ++ ++ private void updatePosition(PrimedTnt origin, PrimedTnt tnt) { ++ boolean originMoved = !origin.position().equals(tnt.position()); ++ ++ origin.setFuse(100); ++ tnt.storeEntityState(); ++ tnt.entityState().apply(origin); ++ ++ // We have to check delta movement otherwise this optimisation can break reversing tnt. ++ // If origin was shot upwards to a block then falls in the explosion tick it will swing ++ // and origin and tnt will be in the same position every other tnt while swinging. ++ if (!getToBlow().isEmpty() || tnt.getDeltaMovement().lengthSqr() <= 64.0 || originMoved) { ++ origin.tick(); ++ } ++ ++ // update explosion position ++ x = origin.getX(); ++ y = origin.getY(0.0625); ++ z = origin.getZ(); ++ } ++ ++ private void locateAndImpactEntities(List positions, AABB bb, ExplosionBlockCache[] blockCache) { ++ double radius = this.radius * 2.0f; ++ ++ int minSection = io.papermc.paper.util.WorldUtil.getMinSection(level); ++ int maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(level); ++ ++ int minChunkX = Mth.floor(bb.minX - radius) >> 4; ++ int minChunkY = Mth.clamp(Mth.floor(bb.minY - radius) >> 4, minSection, maxSection); ++ int minChunkZ = Mth.floor(bb.minZ - radius) >> 4; ++ int maxChunkX = Mth.floor(bb.maxX + radius) >> 4; ++ int maxChunkY = Mth.clamp(Mth.floor(bb.maxY + radius) >> 4, minSection, maxSection); ++ int maxChunkZ = Mth.floor(bb.maxZ + radius) >> 4; ++ ++ Vec3 center = bb.getCenter(); ++ double change = Math.max(bb.maxX - bb.minX, Math.max(bb.maxY - bb.minY, bb.maxZ - bb.minZ)); ++ double maxDistanceSqr = Math.pow(radius + change, 2); ++ ++ boolean moved = change != 0.0; ++ ++ BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); // Paper - optimise explosions ++ ++ io.papermc.paper.chunk.system.entity.EntityLookup entityLookup = ((ServerLevel) level).getEntityLookup(); ++ ++ // impact entities already has a range check there is no reason to also ++ // do an intersection check when retrieving entities from the chunk. ++ ++ for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) { ++ for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) { ++ io.papermc.paper.world.ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ); ++ ++ if (chunk == null) { ++ continue; ++ } ++ ++ for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) { ++ if (moved) { ++ impactEntities(chunk.getSectionEntities(chunkY), positions, center, blockPos, blockCache, radius, maxDistanceSqr); ++ } else { ++ impactEntitiesIdle(chunk.getSectionEntities(chunkY), positions.get(0), positions.size(), blockPos, blockCache, radius); ++ } ++ } ++ } ++ } ++ } ++ ++ // swinging case: more than 1 position and actively changing positions. ++ private void impactEntities(Entity[] entities, List positions, Vec3 center, BlockPos.MutableBlockPos blockPos, ExplosionBlockCache[] blockCache, double radius, double maxDistanceSqr) { ++ for (int i = 0; i < entities.length; ++i) { ++ Entity entity = entities[i]; ++ if (entity == null) break; ++ ++ if (entity != source && !entity.ignoreExplosion() ++ && entity.distanceToSqr(center.x, center.y, center.z) <= maxDistanceSqr ++ ) { ++ int key = DensityCache.createKey(entity, center); ++ DensityCache.Density data = level.densityCache.retrieveCache(key); ++ Vec3 position = entity.position(); ++ ++ if (data != null && data.isObscured(position)) { ++ continue; ++ } else if (impactEntity(entity, entities, positions, radius, blockPos, blockCache) == 1 && data != null) { ++ data.obscure(position); ++ } ++ } ++ ++ // chunk entities can change while we're affecting entities ++ if (entities[i] != entity) { ++ i--; ++ } ++ } ++ } ++ ++ private int impactEntity(Entity entity, Entity[] section, List positions, double radius, BlockPos.MutableBlockPos blockPos, ExplosionBlockCache[] blockCache) { ++ int found = 0; ++ ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0; i < positions.size(); i++) { ++ Vec3 pos = positions.get(i); ++ ++ double distanceFromBottom = Math.sqrt(entity.distanceToSqr(pos)) / radius; ++ ++ if (distanceFromBottom > 1.0) continue; ++ ++ double x = entity.getX() - pos.x; ++ double y = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - pos.y; ++ double z = entity.getZ() - pos.z; ++ double distance = Math.sqrt(x * x + y * y + z * z); ++ ++ if (distance == 0.0D) continue; ++ ++ x /= distance; ++ y /= distance; ++ z /= distance; ++ double density = this.getBlockDensity(pos, entity, blockCache, blockPos); // Paper - Optimize explosions // Paper - optimise explosions ++ double exposure = (1.0D - distanceFromBottom) * density; ++ ++ int visible = density != 0.0 ? 1 : 0; ++ found |= (visible << 1) | 1; ++ ++ if (entity.isPrimedTNT || entity.isFallingBlock) { ++ entity.addDeltaMovement(x * exposure, y * exposure, z * exposure); ++ continue; ++ } ++ ++ impactNonLiving(entity, pos, section, x, y, z, exposure, blockPos, blockCache); ++ } ++ ++ return found; ++ } ++ ++ // stationary case: 1 position or stationary ++ private void impactEntitiesIdle(Entity[] entities, Vec3 position, int potential, BlockPos.MutableBlockPos blockPos, ExplosionBlockCache[] blockCache, double radius) { ++ for (int i = 0; i < entities.length; ++i) { ++ Entity entity = entities[i]; ++ if (entity == null) break; ++ ++ if (entity != source && !entity.ignoreExplosion()) { ++ impactEntityIdle(entity, entities, position, potential, radius, blockPos, blockCache); ++ } ++ ++ // chunk entities can change while we're affecting entities ++ if (entities[i] != entity) { ++ i--; ++ } ++ } ++ } ++ ++ private void impactEntityIdle(Entity entity, Entity[] section, Vec3 pos, int potential, double radius, BlockPos.MutableBlockPos blockPos, ExplosionBlockCache[] blockCache) { ++ double distanceFromBottom = Math.sqrt(entity.distanceToSqr(pos)) / radius; ++ ++ if (distanceFromBottom <= 1.0) { ++ double x = entity.getX() - pos.x; ++ double y = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - pos.y; ++ double z = entity.getZ() - pos.z; ++ double distance = Math.sqrt(x * x + y * y + z * z); ++ ++ if (distance != 0.0D) { ++ x /= distance; ++ y /= distance; ++ z /= distance; ++ double density = this.getBlockDensity(pos, entity, blockCache, blockPos); // Paper - Optimize explosions // Paper - optimise explosions ++ double exposure = (1.0D - distanceFromBottom) * density; ++ ++ if (entity.isPrimedTNT || entity.isFallingBlock) { ++ x *= exposure; ++ y *= exposure; ++ z *= exposure; ++ ++ if (exposure == 0.0) { ++ return; ++ } ++ ++ for (int i = 0; i < potential; ++i) { ++ entity.addDeltaMovement(x, y, z); ++ } ++ } else { ++ for (int i = 0; i < potential; ++i) { ++ impactNonLiving(entity, pos, section, x, y, z, exposure, blockPos, blockCache); ++ } ++ } ++ } ++ } ++ } ++ ++ private void impactNonLiving(Entity entity, Vec3 pos, Entity[] section, double d8, double d9, double d10, double d13, BlockPos.MutableBlockPos blockPos, ExplosionBlockCache[] blockCache) { ++ // CraftBukkit start ++ ++ // Special case ender dragon only give knockback if no damage is cancelled ++ // Thinks to note: ++ // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed) ++ // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon ++ // - Damaging EntityEnderDragon does nothing ++ // - EntityEnderDragon hitbock always covers the other parts and is therefore always present ++ if (entity instanceof EnderDragonPart) { ++ return; ++ } ++ ++ CraftEventFactory.entityDamage = this.source; ++ entity.lastDamageCancelled = false; ++ ++ if (entity instanceof EnderDragon) { ++ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) { ++ for (Entity ent : section) { ++ if (ent == null) break; ++ // Calculate damage separately for each EntityComplexPart ++ double d7part; ++ if (ent == entityComplexPart && (d7part = Math.sqrt(entityComplexPart.distanceToSqr(pos)) / radius) <= 1.0D) { ++ double d13part = (1.0D - d7part) * this.getSeenFraction(pos, entityComplexPart, null, blockCache, blockPos); // Sakura // Paper - optimise explosions ++ entityComplexPart.hurt(this.getDamageSource(), (float) ((int) ((d13part * d13part + d13part) / 2.0D * 7.0D * (double) radius + 1.0D))); ++ } ++ } ++ } ++ } else { ++ entity.hurt(this.getDamageSource(), (float) ((int) ((d13 * d13 + d13) / 2.0D * 7.0D * radius + 1.0D))); ++ } ++ ++ CraftEventFactory.entityDamage = null; ++ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled ++ return; ++ } ++ // CraftBukkit end ++ double d14; ++ ++ if (entity instanceof LivingEntity) { ++ LivingEntity entityliving = (LivingEntity) entity; ++ ++ d14 = entity instanceof Player && level.paperConfig().environment.disableExplosionKnockback ? 0 : ProtectionEnchantment.getExplosionKnockbackAfterDampener(entityliving, d13); // Paper - disable explosion knockback ++ } else { ++ d14 = d13; ++ } ++ ++ d8 *= d14; ++ d9 *= d14; ++ d10 *= d14; ++ ++ // Sakura start - reduce deltamovement allocations ++ entity.addDeltaMovement(d8, d9, d10); ++ if (entity instanceof Player) { ++ Vec3 vec3d1 = new Vec3(d8, d9, d10); ++ // Sakura end ++ Player entityhuman = (Player) entity; ++ ++ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Disable explosion knockback ++ this.getHitPlayers().put(entityhuman, vec3d1); ++ } ++ } ++ } ++ ++} +diff --git a/src/main/java/me/samsuik/sakura/utils/ExplosionUtil.java b/src/main/java/me/samsuik/sakura/utils/ExplosionUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e0387f16ff49031fdcbc8990613417da88d84e87 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/ExplosionUtil.java +@@ -0,0 +1,64 @@ ++package me.samsuik.sakura.utils; ++ ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class ExplosionUtil { ++ ++ private static final java.util.function.Function highestOf = (vector) -> { ++ double highest = 0; ++ ++ for (double v : vector) { ++ highest = Math.max(Math.abs(v), highest); ++ } ++ ++ return highest; ++ }; ++ ++ public static void reduceRays(DoubleArrayList rayCoords) { ++ // temporarily transform rayCoords into a double[] list ++ List vectors = new ArrayList<>(); ++ ++ for (int i = 0; i < rayCoords.size(); i += 3) { ++ vectors.add(new double[] { ++ rayCoords.getDouble(i), ++ rayCoords.getDouble(i + 1), ++ rayCoords.getDouble(i + 2) ++ }); ++ } ++ ++ vectors.sort((o1, o2) -> Double.compare(highestOf.apply(o2), highestOf.apply(o1))); ++ ++ List checked = new java.util.ArrayList<>(); ++ ++ for (double[] vector : vectors) { ++ boolean found = checked.stream().anyMatch((filtered) -> { ++ double x = (filtered[0] - vector[0]) / 0.3f; ++ double y = (filtered[1] - vector[1]) / 0.3f; ++ double z = (filtered[2] - vector[2]) / 0.3f; ++ double distanceSquared = x * x + y * y + z * z; ++ ++ return (distanceSquared > 0.009 && distanceSquared < 0.01) ++ || (distanceSquared > 0.0075 && distanceSquared < 0.008) ++ || (distanceSquared > 0.006 && distanceSquared < 0.00675); ++ }); ++ ++ if (!found) { ++ checked.add(vector); ++ } ++ } ++ ++ rayCoords.clear(); ++ ++ for (double[] vector : vectors) { ++ for (double coord : vector) { ++ rayCoords.add(coord); ++ } ++ } ++ ++ rayCoords.trim(); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4bc68b3145f42f5a432e1e897b3f41606735afd1..dc02ae364afe4b1226d224d54e563cef1d784393 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1580,6 +1580,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 2.5f && fallDistance > 2.5f); + } + ++ /* + @Override + protected void respawn() { + if (stacked <= 1) return; +@@ -115,6 +116,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + + tnt.discard(); + } ++ */ + // Sakura end + + @Override +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 1b335111bd9eb90bbda87225b740768705f26193..545b8dc248d3d57396f548e57898e009876cc150 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -56,12 +56,14 @@ public class Explosion { + private final Explosion.BlockInteraction blockInteraction; + private final RandomSource random; + private final Level level; +- private final double x; +- private final double y; +- private final double z; ++ // Sakura start - final private -> protected ++ protected double x; ++ protected double y; ++ protected double z; ++ // Sakura end + @Nullable + public final Entity source; +- private final float radius; ++ protected final float radius; // Sakura - private -> protected + private final DamageSource damageSource; + private final ExplosionDamageCalculator damageCalculator; + private final ObjectArrayList toBlow; +@@ -122,6 +124,12 @@ public class Explosion { + } + } + ++ // Sakura start ++ if (me.samsuik.sakura.configuration.GlobalConfiguration.get().cannons.explosion.reducedSearchRays) { ++ me.samsuik.sakura.utils.ExplosionUtil.reduceRays(rayCoords); ++ } ++ // Sakura end ++ + CACHED_RAYS = rayCoords.toDoubleArray(); + } + +@@ -129,14 +137,14 @@ public class Explosion { + private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1; + private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT; + +- private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3; +- private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1; ++ protected static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3; // Sakura - protected ++ protected static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1; // Sakura - protected + private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT; + + // resistance = (res + 0.3F) * 0.3F; + // so for resistance = 0, we need res = -0.3F + private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f); +- private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; ++ protected it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; // Sakura - protected + + public static final class ExplosionBlockCache { + +@@ -163,7 +171,7 @@ public class Explosion { + private long[] chunkPosCache = null; + private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null; + +- private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, ++ protected ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, // Sakura - private -> protected + final long key, final boolean calculateResistance) { + ExplosionBlockCache ret = this.blockCache.get(key); + if (ret != null) { +@@ -281,7 +289,7 @@ public class Explosion { + } + } + +- if (!collision.isEmpty() && collision.clipDirect(from, to, currPos)) { // Sakura ++ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) { // Sakura + return true; + } + } +@@ -313,7 +321,8 @@ public class Explosion { + } + } + +- private float getSeenFraction(final Vec3 source, final Entity target, ++ protected float getSeenFraction(final Vec3 source, final Entity target, // Sakura - protected ++ final @Nullable me.samsuik.sakura.explosion.DensityCache.Density data, // Sakura + final ExplosionBlockCache[] blockCache, + final BlockPos.MutableBlockPos blockPos) { + final AABB boundingBox = target.getBoundingBox(); +@@ -351,7 +360,11 @@ public class Explosion { + Math.fma(dz, diffZ, offZ) + ); + +- if (!this.clipsAnything(from, source, context, blockCache, blockPos)) { ++ // Sakura start ++ if (data != null && data.isExpandable() && data.has(from)) { ++ missedRays += (int) data.density(); ++ } else if (!this.clipsAnything(from, source, context, blockCache, blockPos)) { ++ // Sakura end + ++missedRays; + } + } +@@ -361,6 +374,66 @@ public class Explosion { + return (float)missedRays / (float)totalRays; + } + // Paper end - optimise collisions ++ // Sakura start ++ protected ExplosionBlockCache[] createBlockCache() { ++ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ ++ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); ++ ++ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ ++ return new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; ++ } ++ ++ protected void clearBlockCache() { ++ this.blockCache = null; // Paper - optimise explosions ++ this.chunkPosCache = null; // Paper - optimise explosions ++ this.chunkCache = null; // Paper - optimise explosions ++ } ++ ++ protected void invalidateBlockCache(ExplosionBlockCache[] blockCaches) { ++ for (BlockPos blow : getToBlow()) { ++ final int cacheKey = ++ (blow.getX() & BLOCK_EXPLOSION_CACHE_MASK) | ++ (blow.getY() & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | ++ (blow.getZ() & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); ++ ++ blockCaches[cacheKey] = null; ++ } ++ ++ blockCache.clear(); ++ } ++ ++ protected boolean isDestructibleBlock(BlockState state) { ++ if (state == null) { ++ return false; ++ } ++ ++ float power = radius * 1.3f; ++ float blockRes = state.getBlock().getExplosionResistance(); ++ float fluidRes = state.getFluidState().getExplosionResistance(); ++ ++ // This should be better than just checking if we're within a fluid block. ++ return Math.max(blockRes, fluidRes) <= power; ++ } ++ ++ protected boolean isProtectedRegion() { ++ // check if there is a plugin cancelling or clearing the block list. ++ // for our use case on factions and cannon servers this is a relatively ++ // sane optimisation, this may not be the case for other servers. ++ if (source != null && level.sakuraConfig().cannons.explosion.optimiseProtectedRegions) { ++ Location location = new Location(level.getWorld(), x, y, z); ++ java.util.ArrayList blocks = new java.util.ArrayList<>(1); ++ blocks.add(location.getBlock()); ++ EntityExplodeEvent event = new EntityExplodeEvent(source.getBukkitEntity(), location, blocks, 0.0f); ++ event.callEvent(); ++ return !event.isCancelled() && !event.blockList().isEmpty(); ++ } ++ ++ return false; ++ } ++ // Sakura end + + private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { + return (ExplosionDamageCalculator) (entity == null ? Explosion.EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity)); +@@ -407,7 +480,29 @@ public class Explosion { + return; + } + // CraftBukkit end ++ // Sakura start ++ final ExplosionBlockCache[] blockCache = createBlockCache(); ++ ++ // block at explosion position ++ int blockX = Mth.floor(x); ++ int blockY = Mth.floor(y); ++ int blockZ = Mth.floor(z); ++ long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ExplosionBlockCache center = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ ++ if (interactsWithBlocks() && isDestructibleBlock(center.blockState) && !isProtectedRegion()) { ++ searchForBlocks(blockCache); ++ } ++ ++ // Checking if this explosion occurred inside a block is not a viable optimisation. ++ // If an entity is 1.0e-7 away from the position no matter it will be affected no matter what. ++ locateAndImpactEntities(blockCache); ++ clearBlockCache(); ++ } ++ ++ protected void searchForBlocks(ExplosionBlockCache[] blockCache) { + this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); ++ // Sakura end + Set set = Sets.newHashSet(); + boolean flag = true; + +@@ -415,14 +510,7 @@ public class Explosion { + int j; + + // Paper start - optimise explosions +- this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); +- +- this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; +- java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); +- +- this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; +- +- final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; ++ // Sakura - move up + // use initial cache value that is most likely to be used: the source position + final ExplosionBlockCache initialCache; + { +@@ -521,10 +609,15 @@ public class Explosion { + } + + this.toBlow.addAll(set); ++ // Sakura start ++ } ++ ++ protected void locateAndImpactEntities(ExplosionBlockCache[] blockCache) { + float f2 = this.radius * 2.0F; + +- i = Mth.floor(this.x - (double) f2 - 1.0D); +- j = Mth.floor(this.x + (double) f2 + 1.0D); ++ int i = Mth.floor(this.x - (double) f2 - 1.0D); ++ int j = Mth.floor(this.x + (double) f2 + 1.0D); ++ // Sakura end + int l = Mth.floor(this.y - (double) f2 - 1.0D); + int i1 = Mth.floor(this.y + (double) f2 + 1.0D); + int j1 = Mth.floor(this.z - (double) f2 - 1.0D); +@@ -574,7 +667,7 @@ public class Explosion { + // Calculate damage separately for each EntityComplexPart + double d7part; + if (list.contains(entityComplexPart) && (d7part = Math.sqrt(entityComplexPart.distanceToSqr(vec3d)) / f2) <= 1.0D) { +- double d13part = (1.0D - d7part) * this.getSeenFraction(vec3d, entityComplexPart, blockCache, blockPos); // Paper - optimise explosions ++ double d13part = (1.0D - d7part) * this.getSeenFraction(vec3d, entityComplexPart, null, blockCache, blockPos); // Sakura // Paper - optimise explosions + entityComplexPart.hurt(this.getDamageSource(), (float) ((int) ((d13part * d13part + d13part) / 2.0D * 7.0D * (double) f2 + 1.0D))); + } + } +@@ -617,9 +710,7 @@ public class Explosion { + } + } + +- this.blockCache = null; // Paper - optimise explosions +- this.chunkPosCache = null; // Paper - optimise explosions +- this.chunkCache = null; // Paper - optimise explosions ++ // Sakura - move up + + } + +@@ -683,6 +774,12 @@ public class Explosion { + this.toBlow.add(coords); + } + ++ // Sakura start ++ if (!level.paperConfig().environment.optimizeExplosions) { ++ level.densityCache.clear(); ++ } ++ // Sakura end ++ + if (cancelled) { + this.wasCanceled = true; + return; +@@ -850,15 +947,22 @@ public class Explosion { + private BlockInteraction() {} + } + // Paper start - Optimize explosions +- private float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions +- if (!this.level.paperConfig().environment.optimizeExplosions) { +- return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions ++ // Sakura start ++ protected float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions ++ int key = me.samsuik.sakura.explosion.DensityCache.createKey(entity, vec3d); ++ me.samsuik.sakura.explosion.DensityCache.Density data = level.densityCache.retrieveCache(key); ++ ++ if (data != null && data.has(entity.getBoundingBox(), vec3d)) { ++ return data.density(); + } +- CacheKey key = new CacheKey(this, entity.getBoundingBox()); +- Float blockDensity = this.level.explosionDensityCache.get(key); +- if (blockDensity == null) { +- blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions; +- this.level.explosionDensityCache.put(key, blockDensity); ++ ++ float blockDensity = this.getSeenFraction(vec3d, entity, data, blockCache, blockPos); // Paper - optimise explosions; ++ ++ if (data == null || !data.isExpandable() && (blockDensity == 0.0f || blockDensity == 1.0f)) { ++ level.densityCache.createCache(key, entity, vec3d, blockDensity); ++ } else if (data.isExpandable() && data.density() == blockDensity) { ++ data.expand(entity.getBoundingBox(), vec3d); ++ // Sakura end + } + + return blockDensity; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 2f72a059b051bb3d35e0844c6b7ae3b6e2655e36..1ed0e2f30944575c252603b7c45649bb745efd2a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -228,6 +228,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Sakura end + public final me.samsuik.sakura.entity.merge.MergeHistory mergeHistory = new me.samsuik.sakura.entity.merge.MergeHistory(); // Sakura ++ public final me.samsuik.sakura.explosion.DensityCache densityCache = new me.samsuik.sakura.explosion.DensityCache(); + + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot +@@ -1406,7 +1407,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + Explosion.BlockInteraction explosion_effect1 = explosion_effect; +- Explosion explosion = new Explosion(this, entity, damageSource, behavior, x, y, z, power, createFire, explosion_effect1); ++ // Sakura start ++ Explosion explosion; ++ ++ if (explosionSourceType == ExplosionInteraction.TNT) { ++ explosion = new me.samsuik.sakura.explosion.SakuraExplosion(this, entity, damageSource, behavior, x, y, z, power, createFire, explosion_effect1); ++ } else { ++ explosion = new Explosion(this, entity, damageSource, behavior, x, y, z, power, createFire, explosion_effect1); ++ } ++ // Sakura end + + explosion.explode(); + explosion.finalizeExplosion(particles); diff --git a/patches/server/0020-Optimise-Fast-Movement.patch b/patches/server/0020-Optimise-Fast-Movement.patch new file mode 100644 index 0000000..9b10772 --- /dev/null +++ b/patches/server/0020-Optimise-Fast-Movement.patch @@ -0,0 +1,232 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Fri, 13 Oct 2023 14:36:19 +0100 +Subject: [PATCH] Optimise Fast Movement + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 311891f079d2136a18f62478bd289c91ff515772..01c5279084a8f84dcaf8d0d51a3305dc04476944 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1165,6 +1165,95 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + // Paper end - detailed watchdog information + ++ // Sakura start - stripped back movement method for basic entities ++ public void moveBasic(MoverType movementType, Vec3 movement) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); ++ ++ if (this.noPhysics) { ++ this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); ++ } else { ++ if (movementType == MoverType.PISTON) { // Paper ++ movement = this.limitPistonMovement(movement); ++ if (movement.equals(Vec3.ZERO)) { ++ return; ++ } ++ } ++ ++ this.level().getProfiler().push("move"); ++ if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { ++ movement = movement.multiply(this.stuckSpeedMultiplier); ++ this.stuckSpeedMultiplier = Vec3.ZERO; ++ this.setDeltaMovement(Vec3.ZERO); ++ } ++ ++ // collideScan for optimised large movements ++ Vec3 vec3d1 = this.collideScan(movement); ++ double d0 = vec3d1.lengthSqr(); ++ ++ if (d0 > 1.0E-7D) { ++ // We might be able to get away with the addition of the falling block special case here and the 2.5f limit. ++ // Falling blocks only use fall distance for damaging entities when they land such as anvils. ++ if (this.fallDistance != 0.0F && d0 >= 1.0D && !isFallingBlock) { ++ BlockHitResult movingobjectpositionblock = this.level().clip(new ClipContext(this.position(), this.position().add(vec3d1), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this)); ++ ++ if (movingobjectpositionblock.getType() != HitResult.Type.MISS) { ++ this.resetFallDistance(); ++ } ++ } ++ ++ this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z); ++ } ++ ++ this.level().getProfiler().pop(); ++ this.level().getProfiler().push("rest"); ++ boolean flag = !Mth.equal(movement.x, vec3d1.x); ++ boolean flag1 = !Mth.equal(movement.z, vec3d1.z); ++ ++ this.horizontalCollision = flag || flag1; ++ this.verticalCollision = movement.y != vec3d1.y; ++ this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0D; ++ if (this.horizontalCollision) { ++ this.minorHorizontalCollision = this.isHorizontalCollisionMinor(vec3d1); ++ } else { ++ this.minorHorizontalCollision = false; ++ } ++ ++ this.setOnGroundWithKnownMovement(this.verticalCollisionBelow, vec3d1); ++ BlockPos blockposition = this.getOnPosLegacy(); ++ BlockState iblockdata = this.level().getBlockState(blockposition); ++ ++ this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition); ++ if (this.isRemoved()) { ++ this.level().getProfiler().pop(); ++ } else { ++ if (this.horizontalCollision) { ++ Vec3 vec3d2 = this.getDeltaMovement(); ++ ++ this.setDeltaMovement(flag ? 0.0D : vec3d2.x, vec3d2.y, flag1 ? 0.0D : vec3d2.z); ++ } ++ ++ Block block = iblockdata.getBlock(); ++ ++ if (movement.y != vec3d1.y) { ++ block.updateEntityAfterFallOn(this.level(), this); ++ } ++ ++ if (this.onGround()) { ++ // used for slowing down entities on top of slime ++ block.stepOn(this.level(), blockposition, iblockdata, this); ++ } ++ ++ this.tryCheckInsideBlocks(); ++ ++ float f = this.getBlockSpeedFactor(); ++ ++ this.multiplyDeltaMovement((double) f, 1.0D, (double) f); // Sakura - reduce movement allocations ++ this.level().getProfiler().pop(); ++ } ++ } ++ } ++ // Sakura end ++ + public void move(MoverType movementType, Vec3 movement) { + // Paper start - detailed watchdog information + io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); +@@ -1542,6 +1631,99 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return offsetFactor; + } + ++ // Sakura start ++ private Vec3 collideScan(Vec3 movement) { ++ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) { ++ return movement; ++ } ++ ++ final boolean scan = movement.lengthSqr() >= 12.0; ++ final List potentialCollisionsBB = new java.util.ArrayList<>(8); ++ final List potentialCollisionsVoxel = new java.util.ArrayList<>(2); ++ final AABB currBoundingBox = getBoundingBox(); ++ ++ if (scan) { ++ return scanAndCollide(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ } else { ++ final AABB bb = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); ++ collectCollisions(bb, potentialCollisionsVoxel, potentialCollisionsBB); ++ return io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ } ++ } ++ ++ private Vec3 scanAndCollide(Vec3 movement, AABB currBoundingBox, List voxelList, List bbList) { ++ double x = movement.x; ++ double y = movement.y; ++ double z = movement.z; ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (y != 0.0) { ++ y = scanY(currBoundingBox, y, voxelList, bbList); ++ ++ if (y != 0.0) { ++ currBoundingBox = io.papermc.paper.util.CollisionUtil.offsetY(currBoundingBox, y); ++ } ++ } ++ ++ if (xSmaller && z != 0.0) { ++ z = scanZ(currBoundingBox, z, voxelList, bbList); ++ ++ if (z != 0.0) { ++ currBoundingBox = io.papermc.paper.util.CollisionUtil.offsetZ(currBoundingBox, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = scanX(currBoundingBox, x, voxelList, bbList); ++ ++ if (x != 0.0) { ++ currBoundingBox = io.papermc.paper.util.CollisionUtil.offsetX(currBoundingBox, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = scanZ(currBoundingBox, z, voxelList, bbList); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ private void collectCollisions(AABB collisionBox, List voxelList, List bbList) { ++ // Copied from the collide method below ++ io.papermc.paper.util.CollisionUtil.getCollisions( ++ level, this, collisionBox, voxelList, bbList, ++ (0) | (loadChunks ? io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS : 0), // Sakura ++ null, null ++ ); ++ ++ if (collidingWithWorldBorder = io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(level.getWorldBorder(), collisionBox)) { // Paper - this line *is* correct, ignore the IDE warning about assignments being used as a condition ++ voxelList.add(level.getWorldBorder().getCollisionShape()); ++ } ++ } ++ ++ private double scanX(AABB currBoundingBox, double x, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(x, 0.0, 0.0); ++ collectCollisions(scanBox, voxelList, bbList); ++ x = io.papermc.paper.util.CollisionUtil.performAABBCollisionsX(currBoundingBox, x, bbList); ++ return io.papermc.paper.util.CollisionUtil.performVoxelCollisionsX(currBoundingBox, x, voxelList); ++ } ++ ++ private double scanY(AABB currBoundingBox, double y, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(0.0, y, 0.0); ++ collectCollisions(scanBox, voxelList, bbList); ++ y = io.papermc.paper.util.CollisionUtil.performAABBCollisionsY(currBoundingBox, y, bbList); ++ return io.papermc.paper.util.CollisionUtil.performVoxelCollisionsY(currBoundingBox, y, voxelList); ++ } ++ ++ private double scanZ(AABB currBoundingBox, double z, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(0.0, 0.0, z); ++ collectCollisions(scanBox, voxelList, bbList); ++ z = io.papermc.paper.util.CollisionUtil.performAABBCollisionsZ(currBoundingBox, z, bbList); ++ return io.papermc.paper.util.CollisionUtil.performVoxelCollisionsZ(currBoundingBox, z, voxelList); ++ } ++ // Sakura end ++ + private Vec3 collide(Vec3 movement) { + // Paper start - optimise collisions + final boolean xZero = movement.x == 0.0; +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 2afcb3ebdfba545d7c1d73fd0aed486c1f8bf6ae..b592416daef8233a08537267c79290c6758250b4 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -201,7 +201,7 @@ public class FallingBlockEntity extends Entity { + this.addDeltaMovement(0.0D, -0.04D, 0.0D); // Sakura - reduce movement allocations + } + +- this.move(MoverType.SELF, this.getDeltaMovement()); ++ this.moveBasic(MoverType.SELF, this.getDeltaMovement()); // Sakura + + // Paper start - fix sand duping + if (this.isRemoved()) { +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 919680a42a8362859cd87fb3d87e8ee80e9cd960..f661c6225401dba8bb13edcc72fb919a2c76d675 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -126,7 +126,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + this.addDeltaMovement(0.0D, -0.04D, 0.0D); // Sakura - reduce movement allocations + } + +- this.move(MoverType.SELF, this.getDeltaMovement()); ++ this.moveBasic(MoverType.SELF, this.getDeltaMovement()); // Sakura + // Paper start - Configurable TNT entity height nerf + if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { + this.discard(); diff --git a/patches/server/0021-Limited-Get-Entities.patch b/patches/server/0021-Limited-Get-Entities.patch new file mode 100644 index 0000000..5e55af1 --- /dev/null +++ b/patches/server/0021-Limited-Get-Entities.patch @@ -0,0 +1,374 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 23 Sep 2021 15:19:31 +0100 +Subject: [PATCH] Limited Get Entities + + +diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +index c4d1dbbd39ba0cdc9176ffa6d350d2aa50380211..857d530ad68aacb23baf40128a3f8b843b472259 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java ++++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +@@ -514,6 +514,128 @@ public final class EntityLookup implements LevelEntityGetter { + return slices; + } + ++ // Sakura start - limited get entities ++ public void getLimitedEntities(final Entity except, final AABB box, final List into, ++ final Predicate predicate, final int limit, final int search) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getLimitedEntities(except, box, into, predicate, limit, search); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getLimitedEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate, final int limit, final int search) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getLimitedEntities(type, box, (List)into, (Predicate)predicate, limit, search); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getLimitedEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate, final int limit, final int search) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getLimitedEntities(clazz, except, box, into, predicate, limit, search); ++ } ++ } ++ } ++ } ++ } ++ // Sakura end ++ + public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { + final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; + final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index d917a19c838ed3d74322abd85e1f737e852b5d7b..1ba10713c85d6f19f075cc267602a04c7e048252 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -258,6 +258,30 @@ public final class ChunkEntitySlices { + } + // Sakura end + ++ // Sakura start - limited get entities ++ public void getLimitedEntities(final Entity except, final AABB box, final List into, final Predicate predicate, final int limit, final int search) { ++ this.allEntities.getLimitedEntities(except, box, into, predicate, limit, search); ++ } ++ ++ public void getLimitedEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate, final int limit, final int search) { ++ this.allEntities.getLimitedEntities(type, box, (List)into, (Predicate)predicate, limit, search); ++ } ++ ++ public void getLimitedEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate, final int limit, final int search) { ++ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); ++ if (collection != null) { ++ collection.getLimitedEntities(except, clazz, box, (List)into, (Predicate)predicate, limit, search); ++ } else { ++ synchronized (this) { ++ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); ++ } ++ collection.getLimitedEntities(except, clazz, box, (List)into, (Predicate)predicate, limit, search); ++ } ++ } ++ // Sakura end ++ + public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + this.hardCollidingEntities.getEntities(except, box, into, predicate); + } +@@ -448,6 +472,155 @@ public final class ChunkEntitySlices { + } + // Sakura end + ++ // Sakura start - limited get entities ++ public void getLimitedEntities(final Entity except, final Class clazz, final AABB box, final List into, ++ final Predicate predicate, final int limit, int search) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ int len = Math.min(storage.length, list.size()); ++ int index = manager.world.random.nextInt(len); // starting position ++ ++ for (int i = 0; i < len; ++i) { ++ index = (index + 1) % len; // Update index ++ final Entity entity = storage[index]; ++ ++ if (into.size() >= limit || search-- <= 0) { ++ return; ++ } ++ ++ if (entity == null || entity == except || !clazz.isInstance(entity) || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(entity)) { ++ continue; ++ } ++ ++ into.add(entity); ++ } ++ } ++ } ++ ++ public void getLimitedEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate, final int limit, int search) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ int len = Math.min(storage.length, list.size()); ++ int index = manager.world.random.nextInt(len); // starting position ++ ++ for (int i = 0; i < len; ++i) { ++ index = (index + 1) % len; // Update index ++ final Entity entity = storage[index]; ++ ++ if (into.size() >= limit || search-- <= 0) { ++ return; ++ } ++ ++ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test((T)entity)) { ++ continue; ++ } ++ ++ into.add((T)entity); ++ } ++ } ++ } ++ ++ public void getLimitedEntities(final Entity except, final AABB box, final List into, ++ final Predicate predicate, final int limit, int search) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ int len = Math.min(storage.length, list.size()); ++ int index = manager.world.random.nextInt(len); // starting position ++ ++ for (int i = 0; i < len; ++i) { ++ index = (index + 1) % len; // Update index ++ final Entity entity = storage[index]; ++ ++ if (into.size() >= limit || search-- <= 0) { ++ return; ++ } ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(entity)) { ++ continue; ++ } ++ ++ into.add(entity); ++ } ++ } ++ } ++ // Sakura end ++ + public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + if (this.count == 0) { + return; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 1ed0e2f30944575c252603b7c45649bb745efd2a..7382075d369a1c154d8ca62028c6a887d85adb07 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -230,6 +230,39 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final me.samsuik.sakura.entity.merge.MergeHistory mergeHistory = new me.samsuik.sakura.entity.merge.MergeHistory(); // Sakura + public final me.samsuik.sakura.explosion.DensityCache densityCache = new me.samsuik.sakura.explosion.DensityCache(); + ++ // Sakura start - limited get entities ++ public void getLimitedEntities(Entity except, AABB box, Predicate predicate, List into, int limit, int search) { ++ ((ServerLevel)this).getEntityLookup().getLimitedEntities(except, box, into, predicate, limit, search); ++ } ++ ++ public List getLimitedEntities(net.minecraft.world.entity.EntityType filter, AABB box, Predicate predicate, int limit, int search) { ++ List ret = new java.util.ArrayList<>(); ++ ((ServerLevel)this).getEntityLookup().getLimitedEntities(filter, box, ret, predicate, limit, search); ++ return ret; ++ } ++ ++ public void getLimitedEntitiesByClass(Class clazz, Entity except, final AABB box, List into, ++ Predicate predicate, int limit, int search) { ++ ((ServerLevel)this).getEntityLookup().getLimitedEntities((Class)clazz, except, box, (List)into, (Predicate)predicate, limit, search); ++ } ++ ++ public List getLimitedEntitiesOfClass(Class entityClass, AABB box, Predicate predicate, int limit, int search) { ++ List ret = new java.util.ArrayList<>(); ++ ((ServerLevel)this).getEntityLookup().getLimitedEntities(entityClass, null, box, ret, predicate, limit, search); ++ return ret; ++ } ++ ++ public List getLimitedEntities(@Nullable Entity except, AABB box, Predicate predicate, int limit, int search) { ++ List list = Lists.newArrayList(); ++ getLimitedEntities(except, box, predicate, list, limit, search); ++ return list; ++ } ++ ++ public List getLimitedEntities(@Nullable Entity except, AABB box, int limit, int search) { ++ return this.getLimitedEntities(except, box, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS, limit, search); ++ } ++ // Sakura end ++ + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper diff --git a/patches/server/0022-isPushedByFluid-API.patch b/patches/server/0022-isPushedByFluid-API.patch new file mode 100644 index 0000000..274d494 --- /dev/null +++ b/patches/server/0022-isPushedByFluid-API.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Fri, 13 Oct 2023 20:02:04 +0100 +Subject: [PATCH] isPushedByFluid API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 01c5279084a8f84dcaf8d0d51a3305dc04476944..f8faa10e12f5e67fc89ccac831895c70d6333536 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -658,6 +658,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + discard(); + } + // Sakura end ++ public boolean pushedByFluid; // Sakura + + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); +@@ -4154,7 +4155,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public boolean isPushedByFluid() { +- return true; ++ return pushedByFluid; // Sakura + } + + public static double getViewScale() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 315d8260e196709ed9084272aa640f11e327c0a8..f8dc36e73389035995b67ac04ce14886526888fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -548,6 +548,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.entity.isInWater(); + } + ++ // Sakura start ++ @Override ++ public boolean isPushedByFluid() { ++ return getHandle().isPushedByFluid(); ++ } ++ ++ @Override ++ public void setPushedByFluid(boolean push) { ++ getHandle().pushedByFluid = push; ++ } ++ // Sakura end ++ + @Override + public World getWorld() { + return this.entity.level().getWorld(); diff --git a/patches/server/0023-Cannon-Mechanics.patch b/patches/server/0023-Cannon-Mechanics.patch new file mode 100644 index 0000000..adb6670 --- /dev/null +++ b/patches/server/0023-Cannon-Mechanics.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Sun, 15 Oct 2023 22:48:35 +0100 +Subject: [PATCH] Cannon Mechanics + + +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 b592416daef8233a08537267c79290c6758250b4..0ebb06d3f8350dc4d4a931f339c872f9943b7631 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -67,6 +67,7 @@ public class FallingBlockEntity extends Entity { + public CompoundTag blockData; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); + public boolean autoExpire = true; // Paper - Auto expire setting ++ public boolean heightParity; // Sakura + + public FallingBlockEntity(EntityType type, Level world) { + super(type, world); +@@ -75,6 +76,7 @@ public class FallingBlockEntity extends Entity { + this.fallDamageMax = 40; + this.isFallingBlock = true; // Sakura + this.loadChunks = world.sakuraConfig().cannons.sand.loadsChunks; // Sakura - load chunks ++ this.heightParity = world.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura + } + + public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) { +@@ -183,6 +185,12 @@ public class FallingBlockEntity extends Entity { + return itemEntity; + } + // Sakura end ++ // Sakura start ++ @Override ++ public final double getEyeY() { ++ return heightParity ? getY() : super.getEyeY(); ++ } ++ // Sakura end + + @Override + public void tick() { +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 f661c6225401dba8bb13edcc72fb919a2c76d675..3818d07261ce4f276968691ad32a22b88ffe6826 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -46,6 +46,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { + this.yo = y; + this.zo = z; + this.owner = igniter; ++ // Sakura start ++ switch (world.sakuraConfig().cannons.mechanics.tntSpread) { ++ case NONE -> multiplyDeltaMovement(0, 0, 0); ++ case Y -> multiplyDeltaMovement(0, 1, 0); ++ } ++ // Sakura end + } + + @Override +@@ -239,7 +245,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + // Paper start - Optional prevent TNT from moving in water + @Override + public boolean isPushedByFluid() { +- return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); ++ return !level().paperConfig().fixes.preventTntFromMovingInWater && level().sakuraConfig().cannons.mechanics.tntFlowsInWater && super.isPushedByFluid(); // Sakura - convenience + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index 55bfb0afc0e4e9f1ce2dd15f729bee61822c5afc..69b3682783308d188929742f738ec71df973eb03 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -34,6 +34,16 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + public void setStacked(int stacked) { + getHandle().setStacked(stacked); + } ++ ++ @Override ++ public void setHeightParity(boolean parity) { ++ getHandle().heightParity = parity; ++ } ++ ++ @Override ++ public boolean getHeightParity() { ++ return getHandle().heightParity; ++ } + // Sakura end + + @Override diff --git a/patches/server/0024-Cache-MovingBlockEntity-collision-shape.patch b/patches/server/0024-Cache-MovingBlockEntity-collision-shape.patch new file mode 100644 index 0000000..f96dc74 --- /dev/null +++ b/patches/server/0024-Cache-MovingBlockEntity-collision-shape.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Mon, 16 Oct 2023 22:41:12 +0100 +Subject: [PATCH] Cache MovingBlockEntity collision shape + + +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 1ef87580574919796dbba707f44a413ee5c5781b..a971bb30ef8620f016a5968a9da40187ee31a3ef 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 +@@ -43,6 +43,11 @@ public class PistonMovingBlockEntity extends BlockEntity { + private float progressO; + private long lastTicked; + private int deathTicks; ++ // Sakura start ++ private VoxelShape collisionShape = Shapes.empty(); ++ private Direction shapeDirection = null; ++ private float shapeProgress = Float.MIN_VALUE; ++ // Sakura end + + public PistonMovingBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.PISTON, pos, state); +@@ -336,6 +341,18 @@ public class PistonMovingBlockEntity extends BlockEntity { + } + + public VoxelShape getCollisionShape(BlockGetter world, BlockPos pos) { ++ // Sakura start ++ var direction = NOCLIP.get(); ++ if (progress == shapeProgress && direction == shapeDirection) { ++ return collisionShape; ++ } else { ++ shapeProgress = progress; ++ shapeDirection = direction; ++ return collisionShape = createCollisionShape(world, pos); ++ } ++ } ++ private VoxelShape createCollisionShape(BlockGetter world, BlockPos pos) { ++ // Sakura end + VoxelShape voxelShape; + if (!this.extending && this.isSourcePiston && this.movedState.getBlock() instanceof PistonBaseBlock) { + voxelShape = this.movedState.setValue(PistonBaseBlock.EXTENDED, Boolean.valueOf(true)).getCollisionShape(world, pos); diff --git a/patches/server/0025-Optimise-TNT-fluid-state-and-pushing.patch b/patches/server/0025-Optimise-TNT-fluid-state-and-pushing.patch new file mode 100644 index 0000000..3ef874d --- /dev/null +++ b/patches/server/0025-Optimise-TNT-fluid-state-and-pushing.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Mon, 16 Oct 2023 22:57:55 +0100 +Subject: [PATCH] Optimise TNT fluid state and pushing + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f8faa10e12f5e67fc89ccac831895c70d6333536..91c05521b50f0c97841a6356b369f855ad804df1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2135,7 +2135,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return this.isInWater() || flag; + } + +- void updateInWaterStateAndDoWaterCurrentPushing() { ++ protected void updateInWaterStateAndDoWaterCurrentPushing() { // Sakura + Entity entity = this.getVehicle(); + + if (entity instanceof Boat) { +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 3818d07261ce4f276968691ad32a22b88ffe6826..923dd09399cd85cdb31e129a03affb9403feb181 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -124,6 +124,19 @@ public class PrimedTnt extends Entity implements TraceableEntity { + } + */ + // Sakura end ++ // Sakura start ++ protected boolean updateInWaterStateAndDoFluidPushing() { ++ if (isPushedByFluid()) { ++ return this.updateInWaterStateAndDoFluidPushing(); ++ } else { ++ // super method also handles lava fluid pushing ++ // we only need to search for water to negate fall distance ++ this.fluidHeight.clear(); ++ this.updateInWaterStateAndDoWaterCurrentPushing(); ++ return isInWater(); ++ } ++ } ++ // Sakura end + + @Override + public void tick() { diff --git a/patches/server/0026-Optimise-LivingEntity-pushEntities.patch b/patches/server/0026-Optimise-LivingEntity-pushEntities.patch new file mode 100644 index 0000000..92ecfdd --- /dev/null +++ b/patches/server/0026-Optimise-LivingEntity-pushEntities.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 23 Sep 2021 18:50:13 +0100 +Subject: [PATCH] Optimise LivingEntity#pushEntities + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 77a1c7dfbaccc2e74da5c78ce4dfcd1717a7ac65..68b5e7d5293557cd1d7c1c202c48ac8caa40e721 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3543,7 +3543,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule ++ // Sakura start - optimise entity pushing ++ int limit = Math.max(i, this.level().paperConfig().collisions.maxEntityCollisions); ++ int search = limit * limit; ++ List list = this.level().getLimitedEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, level().paperConfig().collisions.fixClimbingBypassingCrammingRule), limit, search); // Paper - fix climbing bypassing cramming rule ++ // Sakura end + + if (!list.isEmpty()) { + // Paper - moved up diff --git a/scripts/apatch.sh b/scripts/apatch.sh new file mode 100644 index 0000000..f56771d --- /dev/null +++ b/scripts/apatch.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +gitcmd="git -c commit.gpgsign=false" + +noapply=1 +isreject=0 +if [[ $1 == "--noapplied" ]]; then + noapply=1 + shift +fi + +if [ ! -z "$1" ]; then + file="$1" +elif [ -z "$1" ] && [ -f .git/rebase-apply/patch ]; then + file=".git/rebase-apply/patch" + noapply=1 + isreject=1 +else + echo "Please specify a file" + exit 1 +fi +applied=$(echo $file | sed 's/.patch$/-applied\.patch/g') +if [ "$1" == "--reset" ]; then + $gitcmd am --abort + $gitcmd reset --hard + $gitcmd clean -f + exit 0 +fi + + +(test "$isreject" != "1" && $gitcmd am -3 $file) || ( + echo "Failures - Wiggling" + $gitcmd reset --hard + $gitcmd clean -f + errors=$($gitcmd apply --rej $file 2>&1) + echo "$errors" >> ~/patch.log + export missingfiles="" + export summaryfail="" + export summarygood="" + for i in $(find . -name \*.rej); do + base=$(echo "$i" | sed 's/.rej//g') + if [ -f "$i" ]; then + sed -e 's/^diff a\/\(.*\) b\/\(.*\)[[:space:]].*rejected.*$/--- \1\n+++ \2/' -i $i && wiggle -v -l --replace "$base" "$i" + rm "$base.porig" "$i" + else + echo "No such file: $base" + missingfiles="$missingfiles\n$base" + fi + done + for i in $($gitcmd status --porcelain | awk '{print $2}'); do + filedata=$(cat "$i") + if [ -f "$file" ] && [[ "$filedata" == *"<<<<<"* ]]; then + export summaryfail="$summaryfail\nFAILED TO APPLY: $i" + else + $gitcmd add --force "$i" + export summarygood="$summarygood\nAPPLIED CLEAN: $i" + fi + done + echo -e "$summarygood" + echo -e "$summaryfail" + if [[ "$errors" == *"No such file"* ]]; then + echo "==========================="; + echo " " + echo " MISSING FILES" + echo $(echo "$errors" | grep "No such file") + echo -e "$missingfiles" + echo " " + echo "==========================="; + fi + $gitcmd status + $gitcmd diff +) +if [[ "$noapply" != "1" ]] && [[ "$file" != *-applied.patch ]]; then + mv "$file" "$applied" +fi diff --git a/scripts/upstream.sh b/scripts/upstream.sh new file mode 100644 index 0000000..9b2e530 --- /dev/null +++ b/scripts/upstream.sh @@ -0,0 +1,27 @@ +exit_on_error() { + echo "$1" + exit 1 +} + +git reset HEAD --hard + +oldHash=$(grep "paperRef=" gradle.properties | cut -d "=" -f2) +newHash=$(curl -s https://api.github.com/repos/PaperMC/paper/commits/master | jq -r .sha) + +if [ "$oldHash" = "$newHash" ]; then + echo "Upstream has not updated!" + exit 0 +fi + +echo "Updating paper: $oldHash -> $newHash" + +sed -i "s/$oldHash/$newHash/g" gradle.properties +git add gradle.properties + +./gradlew applyPatches || exit_on_error "An error occurred when merging patches!" +./gradlew rebuildPatches || exit_on_error "An error occurred when rebuilding patches!" +./gradlew createReobfPaperclipJar || exit_on_error "An error occurred when building!" + +scripts/upstreamCommit.sh $oldHash $newHash + +echo "Created new commit, please review before pushing." diff --git a/scripts/upstreamCommit.sh b/scripts/upstreamCommit.sh new file mode 100644 index 0000000..44cd386 --- /dev/null +++ b/scripts/upstreamCommit.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# requires curl & jq + +# upstreamCommit +# param: bashHash - the commit hash to use for comparing commits (baseHash...newHash) +# param: newHash - the commit hash to use for comparing commits + +( +set -e +PS1="$" + +paper=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/PaperMC/Paper/compare/$1...$2 | jq -r '.commits[] | "PaperMC/Paper@\(.sha[:7]) \(.commit.message | split("\r\n")[0] | split("\n")[0])"') + +updated="" +logsuffix="" +if [ ! -z "paper" ]; then + logsuffix="$logsuffix\n\nPaper Changes:\n$paper" + updated="Paper" +fi +disclaimer="Upstream has released updates that appear to apply and compile correctly" + +log="${UP_LOG_PREFIX}Updated Upstream ($updated)\n\n${disclaimer}${logsuffix}" + +echo -e "$log" | git commit -F - + +) || exit 1 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..7d3618d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://papermc.io/repo/repository/maven-public/") + } +} + +rootProject.name = "sakura" + +include("sakura-api", "sakura-server")