commit e048e9394037258edbdf68bf99ed37a5bc28f64e
Author: M2ke4U <79621885+MrHua269@users.noreply.github.com>
Date: Sun Nov 26 20:04:33 2023 +0800
Initial Commit
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..2fc4a4d
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,63 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
+
+name: LuminolCI - Ver/1.20.2
+
+on:
+ push:
+ branches: [ "ver/1.20.2" ]
+ pull_request:
+ branches: [ "ver/1.20.2" ]
+
+permissions: write-all
+
+jobs:
+ build:
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up git
+ run: git config --global user.email "noreply@github.com" && git config --global user.name "ci"
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ - name: Make gradlew executable
+ run: chmod 777 ./gradlew
+ - name: Setup project
+ uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
+ with:
+ arguments: applyPatches
+ - name: Build project to paperclip jar
+ uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
+ with:
+ arguments: createReobfPaperclipJar
+ - name: Capture build artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Artifacts
+ path: build/libs/
+ - name: Publish API
+ run: |
+ echo "GITHUB_USERNAME=LuminolCI" >> $GITHUB_ENV
+ export GITHUB_USERNAME=LuminolCI
+ echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV
+ export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
+ ./gradlew publish
+ - name: Rename jar file
+ run: mv build/libs/Luminol-paperclip-1.20.2-R0.1-SNAPSHOT-reobf.jar build/libs/lunminol-1.20.2-paperclip.jar
+ - name: Release Artifacts
+ uses: svenstaro/upload-release-action@v2
+ with:
+ release_name: "Luminol MC1.20.2 - ${{ github.event.repository.updated_at}}"
+ tag: "1.20.2-${{ github.run_id }}"
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ file: "build/libs/lunminol-1.20.2-paperclip.jar"
+ file_glob: true
+ prerelease: false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1755aef
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,58 @@
+# JVM crash related
+core.*
+hs_err_pid*
+
+# Intellij
+.idea/
+*.iml
+*.ipr
+*.iws
+out/
+
+# Eclipse
+.classpath
+.project
+.settings/
+
+# netbeans
+nbproject/
+nbactions.xml
+
+# Gradle
+!gradle-wrapper.jar
+.gradle/
+build/
+*/build/
+
+# we use maven!
+build.xml
+
+# Maven
+log/
+target/
+dependency-reduced-pom.xml
+
+# various other potential build files
+build/
+bin/
+dist/
+manifest.mf
+
+# Mac
+.DS_Store/
+.DS_Store
+
+# vim
+.*.sw[a-p]
+
+# Linux temp files
+*~
+
+# other stuff
+run/
+
+build-data/
+Hearse-API
+Hearse-Server
+*.jar
+/patches/todo/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0f389f9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Era4FunMC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5e4508d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# Luminol
+[](LICENSE)
+[](https://github.com/LuminolMC/Luminol/issues)
+
+
+
+
+
Luminol is a folia fork that with many useful optimizations, configurable vanilla features, more API supports, and it was designed for survival and anarchy servers
+
+## Features
+ - Configurable vanilla features
+ - Tpsbar support
+ - Linear region file format(from kaiiju)
+ - Useful optimizations to improve the performance of single threaded region
+ - More API supports for plugin developing(W.I.P)
+
+## Download
+Any versions are available in [release](https://github.com/LuminolMC/Luminol/releases),also you can build it by yourself through the following steps.
+
+## Build
+To build a paperclip jar,you need run the following command.And you can find the jar in build/libs(Note: JDK17 in needed)
+ ```shell
+ ./gradlew applyPatches && ./gradlew createReobfPaperclipJar
+```
+## Using API
+For gradle:
+```kotlin
+dependencies {
+ compileOnly("me.earthme.luminol:luminol-api:1.20.2-R0.1-SNAPSHOT")
+}
+ ```
+For maven
+```xml
+
+ me.earthme.luminol
+ luminol-api
+ 1.20.2-R0.1-SNAPSHOT
+
+```
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..eb0b91e
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,95 @@
+import io.papermc.paperweight.util.constants.*
+
+plugins {
+ java
+ `maven-publish`
+ id("com.github.johnrengelman.shadow") version "8.1.1" apply false
+ id("io.papermc.paperweight.patcher") version "1.5.9"
+}
+
+val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
+
+repositories {
+ mavenCentral()
+ maven(paperMavenPublicUrl) {
+ content { onlyForConfigurations(configurations.paperclip.name) }
+ }
+}
+
+dependencies {
+ remapper("net.fabricmc:tiny-remapper:0.8.6:fat")
+ decompiler("org.quiltmc:quiltflower:1.9.0")
+ paperclip("io.papermc:paperclip:3.0.3-SNAPSHOT")
+}
+
+subprojects {
+ apply(plugin = "java")
+ apply(plugin = "maven-publish")
+
+ java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+ }
+
+ tasks.withType {
+ options.encoding = Charsets.UTF_8.name()
+ options.release.set(17)
+ }
+
+ tasks.withType {
+ options.encoding = Charsets.UTF_8.name()
+ }
+
+ tasks.withType {
+ filteringCharset = Charsets.UTF_8.name()
+ }
+
+ repositories {
+ mavenCentral()
+ maven(paperMavenPublicUrl)
+ maven("https://oss.sonatype.org/content/groups/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")
+ maven("https://repo.codemc.io/repository/maven-public/")
+ }
+
+}
+
+tasks.generateDevelopmentBundle {
+ apiCoordinates.set("me.earthme.luminol:luminol-api")
+ mojangApiCoordinates.set("io.papermc.paper:paper-mojangapi")
+ libraryRepositories.set(
+ listOf(
+ "https://repo.maven.apache.org/maven2/",
+ paperMavenPublicUrl,
+ )
+ )
+}
+
+paperweight {
+ serverProject.set(project(":luminol-server"))
+
+ remapRepo.set("https://maven.fabricmc.net/")
+ decompileRepo.set("https://maven.quiltmc.org/")
+
+ useStandardUpstream("folia") {
+ url.set(github("PaperMC", "Folia"))
+ ref.set(providers.gradleProperty("foliaCommit"))
+
+ withStandardPatcher {
+ apiSourceDirPath.set("Folia-API")
+ serverSourceDirPath.set("Folia-Server")
+
+
+ apiPatchDir.set(layout.projectDirectory.dir("patches/api"))
+ apiOutputDir.set(layout.projectDirectory.dir("Luminol-API"))
+
+ serverPatchDir.set(layout.projectDirectory.dir("patches/server"))
+ serverOutputDir.set(layout.projectDirectory.dir("Luminol-Server"))
+ }
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..02bad58
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,9 @@
+group = me.earthme.luminol
+version = 1.20.2-R0.1-SNAPSHOT
+
+foliaCommit = 1e5e2154c9f88d788cecf0b9fcc77ece7d1f8663
+
+org.gradle.caching = true
+org.gradle.parallel = true
+org.gradle.vfs.watch = false
+org.gradle.jvmargs = -Xmx3G
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..ccebba7
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..309b4e1
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..65dcd68
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,244 @@
+#!/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/HEAD/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
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# 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*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ 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 \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/patches/api/0001-Added-maven-publish.patch b/patches/api/0001-Added-maven-publish.patch
new file mode 100644
index 0000000..79ebf67
--- /dev/null
+++ b/patches/api/0001-Added-maven-publish.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 18:04:47 +0800
+Subject: [PATCH] Added maven publish
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index 639651972fddce4dff63a0f0a7e566a15b9e2dd6..890820cb36e4cadeadb4c2d7cd961a73b065a217 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -168,3 +168,23 @@ tasks.check {
+ dependsOn(scanJar)
+ }
+ // Paper end
++
++// Luminol start
++publishing {
++ repositories {
++ maven {
++ name = "githubPackage"
++ url = uri("https://maven.pkg.github.com/LuminolMC/Luminol")
++
++ credentials.username = System.getenv("GITHUB_USERNAME")
++ credentials.password = System.getenv("GITHUB_TOKEN")
++ }
++
++ publications {
++ register("gpr") {
++ from(components["java"])
++ }
++ }
++ }
++}
++// Luminol end
+\ No newline at end of file
diff --git a/patches/server/0001-Fix-build.patch b/patches/server/0001-Fix-build.patch
new file mode 100644
index 0000000..43a5020
--- /dev/null
+++ b/patches/server/0001-Fix-build.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 08:20:14 +0800
+Subject: [PATCH] Fix build
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index 0cd12a854e544e867abfd94c18a9f138ba57e587..d295ee01481b088a376691de7c0927e95d7a68a8 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -14,7 +14,7 @@ val alsoShade: Configuration by configurations.creating
+
+ dependencies {
+ // Folia start
+- implementation(project(":folia-api"))
++ implementation(project(":luminol-api")) //Luminol
+ implementation("io.papermc.paper:paper-mojangapi:${project.version}") {
+ exclude("io.papermc.paper", "paper-api")
+ }
+@@ -74,7 +74,7 @@ tasks.jar {
+ attributes(
+ "Main-Class" to "org.bukkit.craftbukkit.Main",
+ "Implementation-Title" to "CraftBukkit",
+- "Implementation-Version" to "git-Folia-$implementationVersion", // Folia
++ "Implementation-Version" to "git-Luminol-$implementationVersion", // Folia //Luminol
+ "Implementation-Vendor" to date, // Paper
+ "Specification-Title" to "Bukkit",
+ "Specification-Version" to project.version,
diff --git a/patches/server/0002-Rebrand-to-Luminol.patch b/patches/server/0002-Rebrand-to-Luminol.patch
new file mode 100644
index 0000000..50a5d2d
--- /dev/null
+++ b/patches/server/0002-Rebrand-to-Luminol.patch
@@ -0,0 +1,91 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 08:20:54 +0800
+Subject: [PATCH] Rebrand to Luminol
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
+index cb60d58d4a7556dd896f31d0cd249f860bb3ef84..b52a805150e95d7d27403d3c18088b335a355011 100644
+--- a/src/main/java/com/destroystokyo/paper/Metrics.java
++++ b/src/main/java/com/destroystokyo/paper/Metrics.java
+@@ -593,7 +593,7 @@ public class Metrics {
+ boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
+ // Only start Metrics, if it's enabled in the config
+ if (config.getBoolean("enabled", true)) {
+- Metrics metrics = new Metrics("Folia", serverUUID, logFailedRequests, Bukkit.getLogger()); // Folia - we have our own bstats page
++ Metrics metrics = new Metrics("Luminol", serverUUID, logFailedRequests, Bukkit.getLogger()); // Folia - we have our own bstats page //Luminol
+
+ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
+ String minecraftVersion = Bukkit.getVersion();
+@@ -611,7 +611,7 @@ public class Metrics {
+ } else {
+ paperVersion = "unknown";
+ }
+- metrics.addCustomChart(new Metrics.SimplePie("folia_version", () -> paperVersion)); // Folia - we have our own bstats page
++ metrics.addCustomChart(new Metrics.SimplePie("luminol_version", () -> paperVersion)); // Folia - we have our own bstats page //Luminol
+
+ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
+ Map> map = new HashMap<>();
+diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+index e2f704c115fd6e00960bb56bb0779f1100c89c17..72b9343979de5aa8bb399cbe7cb8a795df9830ab 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+@@ -20,7 +20,7 @@ import java.util.stream.StreamSupport;
+ public class PaperVersionFetcher implements VersionFetcher {
+ private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end
+ private static final String GITHUB_BRANCH_NAME = "master";
+- private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper";
++ private static final String DOWNLOAD_PAGE = "https://github.com/Era4FunMC/Luminol"; //Luminol
+ private static @Nullable String mcVer;
+
+ @Override
+@@ -31,8 +31,8 @@ public class PaperVersionFetcher implements VersionFetcher {
+ @Nonnull
+ @Override
+ public Component getVersionMessage(@Nonnull String serverVersion) {
+- String[] parts = serverVersion.substring("git-Folia-".length()).split("[-\\s]"); // Folia
+- final Component updateMessage = getUpdateStatusMessage("PaperMC/Folia", GITHUB_BRANCH_NAME, parts[0]); // Folia
++ String[] parts = serverVersion.substring("git-Luminol-".length()).split("[-\\s]"); // Folia //Luminol
++ final Component updateMessage = getUpdateStatusMessage("Era4FunMC/Luminol", GITHUB_BRANCH_NAME, parts[0]); // Folia //Luminol
+ final Component history = getHistory();
+
+ return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage;
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 4b189b54c8f326939a7b9ffdfb35edfb7d8cee63..781612b6fc3d020e832164ebce231961dd68e24e 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1833,7 +1833,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
++ return "Luminol"; //Luminol - Luminol > // Folia - Folia > // 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 bc778637c7d4371cafa9bcda67f8965f57cc66d6..5fe36cbb14f0f0e9692cfa40381cebdb4e142bbe 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -269,7 +269,7 @@ import javax.annotation.Nullable; // Paper
+ import javax.annotation.Nonnull; // Paper
+
+ public final class CraftServer implements Server {
+- private final String serverName = "Folia"; // Folia // Paper
++ private final String serverName = "Luminol"; // Folia // Paper //Luminol
+ 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 e9b6ca3aa25e140467ae866d572483050ea3fa0e..9699d7dcca5cf67f50ad05c0e875de424a4e00c5 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/dev.folia/folia-api/pom.properties"); // Folia
++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/me.earthme.luminol/luminol-api/pom.properties"); // Folia //Luminol
+ Properties properties = new Properties();
+
+ if (stream != null) {
diff --git a/patches/server/0003-Added-empty-luminol-config.patch b/patches/server/0003-Added-empty-luminol-config.patch
new file mode 100644
index 0000000..6edb74e
--- /dev/null
+++ b/patches/server/0003-Added-empty-luminol-config.patch
@@ -0,0 +1,179 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 10:40:56 +0800
+Subject: [PATCH] Added empty luminol config
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index d295ee01481b088a376691de7c0927e95d7a68a8..54d761d7e4733c12fbb4957acd509d278ae11316 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -19,6 +19,7 @@ dependencies {
+ exclude("io.papermc.paper", "paper-api")
+ }
+ // Folia end
++ implementation("com.electronwill.night-config:toml:3.6.0") //Luminol - Night config
+ // Paper start
+ implementation("org.jline:jline-terminal-jansi:3.21.0")
+ implementation("net.minecrell:terminalconsoleappender:1.3.0")
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1f9ff0fc33fa36c90fc4cbbd21b7b790de581632
+--- /dev/null
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -0,0 +1,99 @@
++package me.earthme.luminol;
++
++import com.electronwill.nightconfig.core.file.CommentedFileConfig;
++import net.minecraft.server.level.ServerLevel;
++
++import java.io.File;
++import java.io.IOException;
++
++public class LuminolConfig {
++ private static final File PARENT_FOLDER = new File("luminol_config");
++ private static final File MAIN_CONFIG_FILE = new File(PARENT_FOLDER,"luminol_global.toml");
++ private static CommentedFileConfig MAIN_CONFIG;
++
++ public static String serverModName = "Luminol";
++ public static boolean fakeVanillaModeEnabled = false;
++
++ public static void init() throws IOException {
++ PARENT_FOLDER.mkdir();
++
++ if (!MAIN_CONFIG_FILE.exists()){
++ MAIN_CONFIG_FILE.createNewFile();
++ }
++
++ MAIN_CONFIG = CommentedFileConfig.ofConcurrent(MAIN_CONFIG_FILE);
++
++ MAIN_CONFIG.load();
++ initValues();
++ MAIN_CONFIG.save();
++ }
++
++ public static void initValues(){
++ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
++ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
++ }
++
++ public static T get(String key,T def){
++ if (MAIN_CONFIG.contains(key)){
++ return MAIN_CONFIG.get(key);
++ }
++
++ MAIN_CONFIG.set(key,def);
++ return def;
++ }
++
++ public static T get(String key,T def,String comment){
++ MAIN_CONFIG.setComment(key,comment);
++
++ if (MAIN_CONFIG.contains(key)){
++ return MAIN_CONFIG.get(key);
++ }
++
++ MAIN_CONFIG.set(key,def);
++ return def;
++ }
++
++ public static class LumionalWorldConfig{
++ private final File configFile;
++ private CommentedFileConfig commentedFileConfig;
++
++ public LumionalWorldConfig(ServerLevel level) {
++ this.configFile = new File(PARENT_FOLDER,"luminol_world_"+level.getWorld().getName()+".toml");
++ }
++
++ public void init() throws IOException {
++ if (!this.configFile.exists()){
++ this.configFile.createNewFile();
++ }
++
++ this.commentedFileConfig = CommentedFileConfig.ofConcurrent(this.configFile);
++ this.commentedFileConfig.load();
++ this.initValues();
++ this.commentedFileConfig.save();
++ }
++
++ public void initValues(){
++
++ }
++
++ public T get(String key,T def,String comment){
++ this.commentedFileConfig.setComment(key,comment);
++
++ if (this.commentedFileConfig.contains(key)){
++ return this.commentedFileConfig.get(key);
++ }
++
++ this.commentedFileConfig.set(key,def);
++ return def;
++ }
++
++ public T get(String key,T def){
++ if (this.commentedFileConfig.contains(key)){
++ return this.commentedFileConfig.get(key);
++ }
++
++ this.commentedFileConfig.set(key,def);
++ return def;
++ }
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 05d8cabd2294456e3c8df60265f8b035990dd896..f0bf57a7acd77eeffbeeb6743ba58166823022fd 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -19,6 +19,8 @@ import java.util.Locale;
+ import java.util.Optional;
+ import java.util.function.BooleanSupplier;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.DefaultUncaughtExceptionHandler;
+ import net.minecraft.DefaultUncaughtExceptionHandlerWithName;
+ import net.minecraft.SharedConstants;
+@@ -206,6 +208,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ // Spigot end
+ // Paper start
+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
++ LuminolConfig.init(); //Luminol
+ paperConfigurations.initializeGlobalConfiguration();
+ paperConfigurations.initializeWorldDefaultsConfiguration();
+ // Paper start - moved up to right after PlayerList creation but before file load/save
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 724aa1d8147ea2fb5e46d291adacfb7e1f5b5f62..e274c501e2455845f6f9a4614802336205362b69 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -38,6 +38,8 @@ import java.util.stream.Collectors;
+ import java.util.stream.Stream;
+ import javax.annotation.Nonnull;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.CrashReport;
+ import net.minecraft.Util;
+ import net.minecraft.core.BlockPos;
+@@ -766,6 +768,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+ // Folia end - region threading
+
++ public final LuminolConfig.LumionalWorldConfig lumionalWorldConfig; //Luminol
++
+ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
+ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
+ // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
+@@ -850,6 +854,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system
+ this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system
+ this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked
++ this.lumionalWorldConfig = new LuminolConfig.LumionalWorldConfig(this);
++ try {
++ this.lumionalWorldConfig.init();
++ } catch (IOException e) {
++ throw new RuntimeException("Failed to create luminol config for level "+ this.getWorld().getName()+"!",e);
++ }
+ }
+
+ // Folia start - region threading
diff --git a/patches/server/0004-Add-config-for-server-brand-name.patch b/patches/server/0004-Add-config-for-server-brand-name.patch
new file mode 100644
index 0000000..6f0869e
--- /dev/null
+++ b/patches/server/0004-Add-config-for-server-brand-name.patch
@@ -0,0 +1,49 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 11:17:37 +0800
+Subject: [PATCH] Add config for server brand name
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java
+index 5d7110a33957a597592cacb864c947eb053e8563..25cda48a6ab9a703d64c391007f5c32c368fdbe2 100644
+--- a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java
++++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java
+@@ -1,6 +1,7 @@
+ package com.destroystokyo.paper.network;
+
+ import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.server.MinecraftServer;
+ import net.minecraft.server.level.ServerPlayer;
+ import org.bukkit.entity.Player;
+@@ -14,7 +15,7 @@ class PaperServerListPingEventImpl extends PaperServerListPingEvent {
+
+ PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) {
+ super(client, server.server.motd(), server.getPlayerCount(), server.getMaxPlayers(),
+- server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon);
++ LuminolConfig.fakeVanillaModeEnabled ? server.getServerVersion() : server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon);//Luminol - Fake vanilla mode
+ this.server = server;
+ }
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 781612b6fc3d020e832164ebce231961dd68e24e..03165a7791aeac54e44391bbb0f432b613369bfa 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -53,6 +53,8 @@ import java.util.stream.Collectors;
+ import java.util.stream.Stream;
+ import javax.annotation.Nullable;
+ import javax.imageio.ImageIO;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.CrashReport;
+ import net.minecraft.ReportedException;
+ import net.minecraft.SharedConstants;
+@@ -1833,7 +1835,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Folia - Folia > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
++ return LuminolConfig.fakeVanillaModeEnabled ? "vanilla" : LuminolConfig.serverModName; //Luminol //Luminol - Luminol > // Folia - Folia > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+ }
+
+ public SystemReport fillSystemReport(SystemReport details) {
diff --git a/patches/server/0005-Add-config-for-unsafe-teleportation.patch b/patches/server/0005-Add-config-for-unsafe-teleportation.patch
new file mode 100644
index 0000000..bfd8ee9
--- /dev/null
+++ b/patches/server/0005-Add-config-for-unsafe-teleportation.patch
@@ -0,0 +1,82 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 11:18:04 +0800
+Subject: [PATCH] Add config for unsafe teleportation
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 1f9ff0fc33fa36c90fc4cbbd21b7b790de581632..36ca0b94d29d81e5f1f2aff4a38ead0b363dd1c7 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -14,6 +14,8 @@ public class LuminolConfig {
+ public static String serverModName = "Luminol";
+ public static boolean fakeVanillaModeEnabled = false;
+
++ public static boolean safeTeleportation = true;
++
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+
+@@ -31,6 +33,8 @@ public class LuminolConfig {
+ public static void initValues(){
+ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
++
++ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 385d5bc08928dd990b690b926e174af86f178c64..e357ed9607889536ecd0e6ea8b59c97d3dab631f 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -24,6 +24,8 @@ import java.util.function.BiConsumer;
+ import java.util.function.Predicate;
+ import java.util.stream.Stream;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.BlockUtil;
+ import net.minecraft.CrashReport;
+ import net.minecraft.CrashReportCategory;
+@@ -4001,6 +4003,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+
+ protected boolean tryEndPortal() {
+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot portal entity async");
++ if (!LuminolConfig.safeTeleportation && !(this instanceof Player)) return false; //Luminol - Unsafe teleportation
+ BlockPos pos = this.portalBlock;
+ ServerLevel world = this.portalWorld;
+ this.portalBlock = null;
+diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
+index 41d7cff39fc37955877668337689b4b26cd8c7cf..ca5799c618bec3d0abc4566d82a29bcc767c6f1f 100644
+--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
+@@ -1,5 +1,6 @@
+ package net.minecraft.world.level.block;
+
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.particles.ParticleTypes;
+ import net.minecraft.resources.ResourceKey;
+@@ -7,6 +8,7 @@ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.BlockGetter;
+ import net.minecraft.world.level.Level;
+@@ -53,6 +55,13 @@ public class EndPortalBlock extends BaseEntityBlock {
+ // return; // CraftBukkit - always fire event in case plugins wish to change it
+ }
+
++ //Luminol start - Unsafe teleportation
++ if (!LuminolConfig.safeTeleportation && !(entity instanceof Player)){
++ entity.endPortalLogicAsync();
++ return;
++ }
++ //Luminol end - Unsafe teleportation
++
+ // Paper start - move all of this logic into portal tick
+ entity.portalWorld = ((ServerLevel)world);
+ entity.portalBlock = pos.immutable();
diff --git a/patches/server/0006-Add-config-for-sand-duping.patch b/patches/server/0006-Add-config-for-sand-duping.patch
new file mode 100644
index 0000000..e661044
--- /dev/null
+++ b/patches/server/0006-Add-config-for-sand-duping.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 18:17:52 +0800
+Subject: [PATCH] Add config for sand duping
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 36ca0b94d29d81e5f1f2aff4a38ead0b363dd1c7..9db3bdd7a7d0b2a110e927ee4781eee489d0da9b 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -15,6 +15,7 @@ public class LuminolConfig {
+ public static boolean fakeVanillaModeEnabled = false;
+
+ public static boolean safeTeleportation = true;
++ public static boolean enableSandDuping = false;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -35,6 +36,7 @@ public class LuminolConfig {
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
+
+ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
++ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
+ }
+
+ public static T get(String key,T def){
+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 8e348099d6b3eb4510405d76453d70e7cadeebf6..cf72aa13fce00bf21c036c14a605ea7c6090d5f0 100644
+--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+@@ -4,6 +4,8 @@ import com.mojang.logging.LogUtils;
+ import java.util.Iterator;
+ import java.util.function.Predicate;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.CrashReportCategory;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+@@ -133,7 +135,7 @@ public class FallingBlockEntity extends Entity {
+ @Override
+ public void tick() {
+ // Paper start - fix sand duping
+- if (this.isRemoved()) {
++ if (!LuminolConfig.enableSandDuping && this.isRemoved()) { //Luminol - Add config for sand duping
+ return;
+ }
+ // Paper end - fix sand duping
+@@ -150,7 +152,7 @@ public class FallingBlockEntity extends Entity {
+ this.move(MoverType.SELF, this.getDeltaMovement());
+
+ // Paper start - fix sand duping
+- if (this.isRemoved()) {
++ if (!LuminolConfig.enableSandDuping && this.isRemoved()) { //Luminol - Add config for sand duping
+ return;
+ }
+ // Paper end - fix sand duping
diff --git a/patches/server/0007-Add-config-for-void-trading.patch b/patches/server/0007-Add-config-for-void-trading.patch
new file mode 100644
index 0000000..02128c2
--- /dev/null
+++ b/patches/server/0007-Add-config-for-void-trading.patch
@@ -0,0 +1,39 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 18:22:09 +0800
+Subject: [PATCH] Add config for void trading
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 9db3bdd7a7d0b2a110e927ee4781eee489d0da9b..dffa9db569fcef2feec75072fe86c9a6ded80aa4 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -16,6 +16,7 @@ public class LuminolConfig {
+
+ public static boolean safeTeleportation = true;
+ public static boolean enableSandDuping = false;
++ public static boolean enableVoidTrading = false;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -37,6 +38,7 @@ public class LuminolConfig {
+
+ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
+ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
++ enableVoidTrading = get("fixes.enable_void_trading",enableVoidTrading);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index e274c501e2455845f6f9a4614802336205362b69..722a5ec7f8e4995ac7025ca0785145d46bd66fcd 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -3026,7 +3026,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ // Spigot Start
+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
+ // Paper start
+- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
++ if (!LuminolConfig.enableVoidTrading && (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null)) { //Luminol - Add config for void trading
+ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
+ }
+ // Paper end
diff --git a/patches/server/0008-Add-config-for-incorrect-tripwire-updating-fixing.patch b/patches/server/0008-Add-config-for-incorrect-tripwire-updating-fixing.patch
new file mode 100644
index 0000000..f379fe1
--- /dev/null
+++ b/patches/server/0008-Add-config-for-incorrect-tripwire-updating-fixing.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 18:25:37 +0800
+Subject: [PATCH] Add config for incorrect tripwire updating fixing
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index dffa9db569fcef2feec75072fe86c9a6ded80aa4..a49da370c91211d4e2274f72d62c911a1912296a 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -17,6 +17,7 @@ public class LuminolConfig {
+ public static boolean safeTeleportation = true;
+ public static boolean enableSandDuping = false;
+ public static boolean enableVoidTrading = false;
++ public static boolean allowIncorrectTripwireUpdating = false;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -39,6 +40,7 @@ public class LuminolConfig {
+ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
+ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
+ enableVoidTrading = get("fixes.enable_void_trading",enableVoidTrading);
++ allowIncorrectTripwireUpdating = get("fixes.allow_incorrect_trip_wire_updaing",allowIncorrectTripwireUpdating);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
+index 004dce26ff073f1de52a84cd425c4f60fdab5e50..ef150d50ff076c80ee62c3c413745fa312c9d289 100644
+--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
+@@ -2,6 +2,8 @@ package net.minecraft.world.level.block;
+
+ import com.google.common.base.MoreObjects;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.server.level.ServerLevel;
+@@ -147,7 +149,7 @@ public class TripWireHookBlock extends Block {
+ boolean flag7 = (Boolean) iblockdata2.getValue(TripWireBlock.POWERED);
+
+ flag5 |= flag6 && flag7;
+- if (k != i || !tripWireBeingRemoved || !flag6) // Paper - don't update the tripwire again if being removed and not disarmed
++ if (k != i || !tripWireBeingRemoved || !flag6 || LuminolConfig.allowIncorrectTripwireUpdating) // Paper - don't update the tripwire again if being removed and not disarmed //Luminol - Add config for incorrect tripwire updating fixing
+ aiblockdata[k] = iblockdata2;
+ if (k == i) {
+ world.scheduleTick(pos, (Block) this, 10);
diff --git a/patches/server/0009-Add-config-for-chat-sign.patch b/patches/server/0009-Add-config-for-chat-sign.patch
new file mode 100644
index 0000000..c295a36
--- /dev/null
+++ b/patches/server/0009-Add-config-for-chat-sign.patch
@@ -0,0 +1,118 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 18:31:24 +0800
+Subject: [PATCH] Add config for chat sign
+
+
+diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
+index 772e3a864e0e70288a1c010d8bbb809d34d16a41..68bd66aad880c81d1f8eaf88525597c878350961 100644
+--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
+@@ -15,6 +15,8 @@ import java.util.Objects;
+ import java.util.Set;
+ import java.util.concurrent.ExecutionException;
+ import java.util.function.Function;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.kyori.adventure.audience.Audience;
+ import net.kyori.adventure.audience.ForwardingAudience;
+ import net.kyori.adventure.key.Key;
+@@ -355,7 +357,7 @@ public final class ChatProcessor {
+
+ private void sendToServer(final ChatType.Bound chatType, final @Nullable Function msgFunction) {
+ final PlayerChatMessage toConsoleMessage = msgFunction == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(msgFunction.apply(ChatProcessor.this.server.console));
+- ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure");
++ ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) || LuminolConfig.disableChatSign ? null : "Not Secure"); //Luminol - Add config for chat sign
+ }
+ }
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index a49da370c91211d4e2274f72d62c911a1912296a..8d1ce8eae252fcf05b6a62d2dc467d6d503d3df4 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -13,6 +13,7 @@ public class LuminolConfig {
+
+ public static String serverModName = "Luminol";
+ public static boolean fakeVanillaModeEnabled = false;
++ public static boolean disableChatSign = false;
+
+ public static boolean safeTeleportation = true;
+ public static boolean enableSandDuping = false;
+@@ -36,6 +37,7 @@ public class LuminolConfig {
+ public static void initValues(){
+ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
++ disableChatSign = get("misc.disable_chat_sign",disableChatSign,"Set this to true to disable mojang's chat sign");
+
+ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
+ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
+diff --git a/src/main/java/net/minecraft/commands/arguments/ArgumentSignatures.java b/src/main/java/net/minecraft/commands/arguments/ArgumentSignatures.java
+index 72a8aa676836fcb3b4578689d16af65e18f55bbe..04653d58f2493d796e61bc97f0481cb628539c37 100644
+--- a/src/main/java/net/minecraft/commands/arguments/ArgumentSignatures.java
++++ b/src/main/java/net/minecraft/commands/arguments/ArgumentSignatures.java
+@@ -4,6 +4,8 @@ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Objects;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.network.FriendlyByteBuf;
+ import net.minecraft.network.chat.MessageSignature;
+ import net.minecraft.network.chat.SignableCommand;
+@@ -14,8 +16,16 @@ public record ArgumentSignatures(List entries) {
+ private static final int MAX_ARGUMENT_NAME_LENGTH = 16;
+
+ public ArgumentSignatures(FriendlyByteBuf buf) {
+- this(buf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 8), ArgumentSignatures.Entry::new));
++ this(readSign(buf));
++ }
++
++ //Luminol start - Add config for chat sign
++ private static List readSign(FriendlyByteBuf buf) {
++ var entries = buf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 8), Entry::new);
++ return LuminolConfig.disableChatSign ? List.of() : entries;
+ }
++ //Luminol end
++
+
+ @Nullable
+ public MessageSignature get(String argumentName) {
+diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
+index 2e395962b555bef0ce1a98e1d768e7738f011535..11a9bc78c34b9f6bfff1ebf979be55b9bbbbed55 100644
+--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java
++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
+@@ -48,6 +48,8 @@ import java.util.function.Function;
+ import java.util.function.IntFunction;
+ import java.util.function.ToIntFunction;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.Util;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+@@ -65,6 +67,7 @@ import net.minecraft.nbt.NbtIo;
+ import net.minecraft.nbt.Tag;
+ import net.minecraft.network.chat.Component;
+ import net.minecraft.network.chat.MutableComponent;
++import net.minecraft.network.protocol.status.ServerStatus;
+ import net.minecraft.resources.ResourceKey;
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.util.Crypt;
+@@ -137,6 +140,17 @@ public class FriendlyByteBuf extends ByteBuf {
+ public void writeJsonWithCodec(Codec codec, T value) {
+ DataResult dataresult = codec.encodeStart(JsonOps.INSTANCE, value);
+
++
++ //Luminol start - Add config for chat sign
++ if (codec == ServerStatus.CODEC) {
++ JsonElement element = Util.getOrThrow(dataresult, string -> new EncoderException("Failed to encode: " + string + " " + value));
++ element.getAsJsonObject().addProperty("preventsChatReports", LuminolConfig.disableChatSign);
++
++ this.writeUtf(GSON.toJson(element));
++ return;
++ }
++ //Luminol end
++
+ this.writeUtf(FriendlyByteBuf.GSON.toJson((JsonElement) Util.getOrThrow(dataresult, (s) -> {
+ return new EncoderException("Failed to encode: " + s + " " + value);
+ })));
diff --git a/patches/server/0010-Add-config-for-vanilla-random.patch b/patches/server/0010-Add-config-for-vanilla-random.patch
new file mode 100644
index 0000000..c1a6603
--- /dev/null
+++ b/patches/server/0010-Add-config-for-vanilla-random.patch
@@ -0,0 +1,39 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 19:03:34 +0800
+Subject: [PATCH] Add config for vanilla random
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 8d1ce8eae252fcf05b6a62d2dc467d6d503d3df4..89d8ffc947d265e0e81943ad851e868b622de168 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -19,6 +19,7 @@ public class LuminolConfig {
+ public static boolean enableSandDuping = false;
+ public static boolean enableVoidTrading = false;
+ public static boolean allowIncorrectTripwireUpdating = false;
++ public static boolean useVanillaRandomSource = false;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -43,6 +44,7 @@ public class LuminolConfig {
+ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
+ enableVoidTrading = get("fixes.enable_void_trading",enableVoidTrading);
+ allowIncorrectTripwireUpdating = get("fixes.allow_incorrect_trip_wire_updaing",allowIncorrectTripwireUpdating);
++ useVanillaRandomSource = get("fixes.use_vanilla_random_source",useVanillaRandomSource,"RNG feature related");
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index e357ed9607889536ecd0e6ea8b59c97d3dab631f..cb32a3851a315f1f1b4fb6d26fdffbcb471bfc06 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -569,7 +569,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ this.bb = Entity.INITIAL_AABB;
+ this.stuckSpeedMultiplier = Vec3.ZERO;
+ this.nextStep = 1.0F;
+- this.random = SHARED_RANDOM; // Paper
++ this.random = LuminolConfig.useVanillaRandomSource ? RandomSource.create() : SHARED_RANDOM;//Luminol - Add config for vanilla random SHARED_RANDOM; // Paper
+ this.remainingFireTicks = -this.getFireImmuneTicks();
+ this.fluidHeight = new Object2DoubleArrayMap(2);
+ this.fluidOnEyes = new HashSet();
diff --git a/patches/server/0011-Kaiiju-Don-t-pathfind-outside-region.patch b/patches/server/0011-Kaiiju-Don-t-pathfind-outside-region.patch
new file mode 100644
index 0000000..bd63c99
--- /dev/null
+++ b/patches/server/0011-Kaiiju-Don-t-pathfind-outside-region.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 19:03:34 +0800
+Subject: [PATCH] Kaiiju Don't pathfind outside region
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+index 1ab77f3518d1df30f66ae44d7d4fa69e5b32d93a..98bf17441da3169d49de55fe89d79ebe250a2b7e 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+@@ -107,7 +107,9 @@ public class MoveToTargetSink extends Behavior {
+
+ private boolean tryComputePath(Mob entity, WalkTarget walkTarget, long time) {
+ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition();
++ if (io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel) entity.level(), blockPos)) // Kaiiju - Don't pathfind outside region
+ this.path = entity.getNavigation().createPath(blockPos, 0);
++ else this.path = null; // Kaiiju - Don't pathfind outside region
+ this.speedModifier = walkTarget.getSpeedModifier();
+ Brain> brain = entity.getBrain();
+ if (this.reachedTarget(entity, walkTarget)) {
diff --git a/patches/server/0012-Add-logger-field-to-LuminolConfig.patch b/patches/server/0012-Add-logger-field-to-LuminolConfig.patch
new file mode 100644
index 0000000..9a8a4d0
--- /dev/null
+++ b/patches/server/0012-Add-logger-field-to-LuminolConfig.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 19:26:01 +0800
+Subject: [PATCH] Add logger field to LuminolConfig
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 89d8ffc947d265e0e81943ad851e868b622de168..74573c50c903cfbe5f9617be5b75c21647f05a91 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -2,11 +2,14 @@ package me.earthme.luminol;
+
+ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+ import net.minecraft.server.level.ServerLevel;
++import org.apache.logging.log4j.LogManager;
++import org.apache.logging.log4j.Logger;
+
+ import java.io.File;
+ import java.io.IOException;
+
+ public class LuminolConfig {
++ private static final Logger logger = LogManager.getLogger();
+ private static final File PARENT_FOLDER = new File("luminol_config");
+ private static final File MAIN_CONFIG_FILE = new File(PARENT_FOLDER,"luminol_global.toml");
+ private static CommentedFileConfig MAIN_CONFIG;
diff --git a/patches/server/0013-Kaiiju-region-format-settings.patch b/patches/server/0013-Kaiiju-region-format-settings.patch
new file mode 100644
index 0000000..1cf6a6a
--- /dev/null
+++ b/patches/server/0013-Kaiiju-region-format-settings.patch
@@ -0,0 +1,98 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sat, 25 Nov 2023 21:28:45 +0800
+Subject: [PATCH] Kaiiju region format settings
+
+
+diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/RegionFileFormat.java b/src/main/java/dev/kaiijumc/kaiiju/region/RegionFileFormat.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..7164d9cd03186f0657783f83de3d6435cda2b17e
+--- /dev/null
++++ b/src/main/java/dev/kaiijumc/kaiiju/region/RegionFileFormat.java
+@@ -0,0 +1,16 @@
++package dev.kaiijumc.kaiiju.region;
++
++public enum RegionFileFormat {
++ ANVIL,
++ LINEAR,
++ INVALID;
++
++ public static RegionFileFormat fromString(String format) {
++ for (RegionFileFormat rff : values()) {
++ if (rff.name().equalsIgnoreCase(format)) {
++ return rff;
++ }
++ }
++ return RegionFileFormat.INVALID;
++ }
++}
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 74573c50c903cfbe5f9617be5b75c21647f05a91..3d526d7cfb313e419de89be1b275651982be42c7 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -1,10 +1,13 @@
+ package me.earthme.luminol;
+
++import dev.kaiijumc.kaiiju.region.RegionFileFormat;
+ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+ import net.minecraft.server.level.ServerLevel;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
++import java.util.Arrays;
++import java.util.logging.Level;
+ import java.io.File;
+ import java.io.IOException;
+
+@@ -24,6 +27,9 @@ public class LuminolConfig {
+ public static boolean allowIncorrectTripwireUpdating = false;
+ public static boolean useVanillaRandomSource = false;
+
++ public static RegionFileFormat regionFormatName = RegionFileFormat.ANVIL;
++ public static int regionFormatLinearCompressionLevel = 1;
++
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+
+@@ -48,6 +54,19 @@ public class LuminolConfig {
+ enableVoidTrading = get("fixes.enable_void_trading",enableVoidTrading);
+ allowIncorrectTripwireUpdating = get("fixes.allow_incorrect_trip_wire_updaing",allowIncorrectTripwireUpdating);
+ useVanillaRandomSource = get("fixes.use_vanilla_random_source",useVanillaRandomSource,"RNG feature related");
++
++ regionFormatName = RegionFileFormat.fromString(get("save.region-format.format", regionFormatName.name()));
++ if (regionFormatName.equals(RegionFileFormat.INVALID)) {
++ logger.error("Unknown region format in luminol global config: " + regionFormatName);
++ logger.error("Falling back to ANVIL region file format.");
++ regionFormatName = RegionFileFormat.ANVIL;
++ }
++ regionFormatLinearCompressionLevel = get("save.region-format.linear.compression-level", regionFormatLinearCompressionLevel);
++ if (regionFormatLinearCompressionLevel > 23 || regionFormatLinearCompressionLevel < 1) {
++ logger.error("Linear region compression level should be between 1 and 22 in luminol global config: " + regionFormatLinearCompressionLevel);
++ logger.error("Falling back to compression level 1.");
++ regionFormatLinearCompressionLevel = 1;
++ }
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 03165a7791aeac54e44391bbb0f432b613369bfa..675bbf1f69011f7f95fabc050c9877d6e70070b2 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -882,7 +882,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop
+Date: Sun, 26 Nov 2023 11:30:57 +0800
+Subject: [PATCH] Kaiiju Add linear region format
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index 54d761d7e4733c12fbb4957acd509d278ae11316..8a926993088d33983f75071b3320dd67c3e857c3 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -19,6 +19,10 @@ dependencies {
+ exclude("io.papermc.paper", "paper-api")
+ }
+ // Folia end
++ // Kaiiju start - Linear format
++ implementation("com.github.luben:zstd-jni:1.5.4-1")
++ implementation("org.lz4:lz4-java:1.8.0")
++ // Kaiiju end
+ implementation("com.electronwill.night-config:toml:3.6.0") //Luminol - Night config
+ // Paper start
+ implementation("org.jline:jline-terminal-jansi:3.21.0")
+diff --git a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java
+index f2c27e0ac65be4b75c1d86ef6fd45fdb538d96ac..00724993d0448454d14a47652b039b88052b8a4f 100644
+--- a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java
++++ b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java
+@@ -314,8 +314,8 @@ public final class PaperFileIOThread extends QueueExecutorThread {
+ public abstract void writeData(final int x, final int z, final CompoundTag compound) throws IOException;
+ public abstract CompoundTag readData(final int x, final int z) throws IOException;
+
+- public abstract T computeForRegionFile(final int chunkX, final int chunkZ, final Function function);
+- public abstract T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function);
++ public abstract T computeForRegionFile(final int chunkX, final int chunkZ, final Function function); // Kaiiju
++ public abstract T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function); // Kaiiju
+
+ public static final class InProgressWrite {
+ public long writeCounter;
+diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..249303116d3cfadd078ebf0ae6e44bf99eed6a47
+--- /dev/null
++++ b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java
+@@ -0,0 +1,31 @@
++package dev.kaiijumc.kaiiju.region;
++
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.chunk.ChunkStatus;
++
++import java.io.DataInputStream;
++import java.io.DataOutputStream;
++import java.io.IOException;
++import java.nio.file.Path;
++import java.util.concurrent.locks.ReentrantLock;
++
++public interface AbstractRegionFile {
++ void flush() throws IOException;
++ void clear(ChunkPos pos) throws IOException;
++ void close() throws IOException;
++ void setStatus(int x, int z, ChunkStatus status);
++ void setOversized(int x, int z, boolean b) throws IOException;
++
++ boolean hasChunk(ChunkPos pos);
++ boolean doesChunkExist(ChunkPos pos) throws Exception;
++ boolean isOversized(int x, int z);
++ boolean recalculateHeader() throws IOException;
++
++ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException;
++ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException;
++ CompoundTag getOversizedData(int x, int z) throws IOException;
++ ChunkStatus getStatusIfCached(int x, int z);
++ ReentrantLock getFileLock();
++ Path getRegionFile();
++}
+diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..dcfbabf54b19a4c29d5c95830242c5c26bf2f4aa
+--- /dev/null
++++ b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java
+@@ -0,0 +1,29 @@
++package dev.kaiijumc.kaiiju.region;
++
++import net.minecraft.world.level.chunk.storage.RegionFile;
++import net.minecraft.world.level.chunk.storage.RegionFileVersion;
++
++import java.io.IOException;
++import java.nio.file.Path;
++
++public class AbstractRegionFileFactory {
++ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, Path file, Path directory, boolean dsync) throws IOException {
++ return getAbstractRegionFile(linearCompression, file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
++ }
++
++ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
++ return getAbstractRegionFile(linearCompression, file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
++ }
++
++ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
++ return getAbstractRegionFile(linearCompression, file, directory, outputChunkStreamVersion, dsync, false);
++ }
++
++ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
++ if (file.toString().endsWith(".linear")) {
++ return new LinearRegionFile(file, linearCompression);
++ } else {
++ return new RegionFile(file, directory, outputChunkStreamVersion, dsync, canRecalcHeader);
++ }
++ }
++}
+diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e40989889f3821bb7484fc0bae5d94b033013904
+--- /dev/null
++++ b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java
+@@ -0,0 +1,316 @@
++package dev.kaiijumc.kaiiju.region;
++
++import com.github.luben.zstd.ZstdInputStream;
++import com.github.luben.zstd.ZstdOutputStream;
++import com.mojang.logging.LogUtils;
++import net.jpountz.lz4.LZ4Compressor;
++import net.jpountz.lz4.LZ4Factory;
++import net.jpountz.lz4.LZ4FastDecompressor;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.chunk.ChunkStatus;
++import org.slf4j.Logger;
++
++import javax.annotation.Nullable;
++import java.io.*;
++import java.nio.ByteBuffer;
++import java.nio.file.Files;
++import java.nio.file.Path;
++import java.nio.file.StandardCopyOption;
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.List;
++import java.util.concurrent.atomic.AtomicBoolean;
++import java.util.concurrent.locks.ReentrantLock;
++
++public class LinearRegionFile implements AbstractRegionFile, AutoCloseable {
++ private static final long SUPERBLOCK = -4323716122432332390L;
++ private static final byte VERSION = 2;
++ private static final int HEADER_SIZE = 32;
++ private static final int FOOTER_SIZE = 8;
++ private static final Logger LOGGER = LogUtils.getLogger();
++ private static final List SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2);
++ private static final LinearRegionFileFlusher linearRegionFileFlusher = new LinearRegionFileFlusher();
++
++ private final byte[][] buffer = new byte[1024][];
++ private final int[] bufferUncompressedSize = new int[1024];
++
++ private final int[] chunkTimestamps = new int[1024];
++ private final ChunkStatus[] statuses = new ChunkStatus[1024];
++
++ private final LZ4Compressor compressor;
++ private final LZ4FastDecompressor decompressor;
++
++ public final ReentrantLock fileLock = new ReentrantLock(true);
++ private final int compressionLevel;
++
++ private AtomicBoolean markedToSave = new AtomicBoolean(false);
++ public boolean closed = false;
++ public Path path;
++
++
++ public LinearRegionFile(Path file, int compression) throws IOException {
++ this.path = file;
++ this.compressionLevel = compression;
++ this.compressor = LZ4Factory.fastestInstance().fastCompressor();
++ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor();
++
++ File regionFile = new File(this.path.toString());
++
++ Arrays.fill(this.bufferUncompressedSize, 0);
++
++ if (!regionFile.canRead()) return;
++
++ try (FileInputStream fileStream = new FileInputStream(regionFile);
++ DataInputStream rawDataStream = new DataInputStream(fileStream)) {
++
++ long superBlock = rawDataStream.readLong();
++ if (superBlock != SUPERBLOCK)
++ throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file);
++
++ byte version = rawDataStream.readByte();
++ if (!SUPPORTED_VERSIONS.contains(version))
++ throw new RuntimeException("Invalid version: " + version + " in " + file);
++
++ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
++ rawDataStream.skipBytes(11);
++
++ int dataCount = rawDataStream.readInt();
++ long fileLength = file.toFile().length();
++ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
++ throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
++
++ rawDataStream.skipBytes(8); // Skip data hash (Long): Unused.
++
++ byte[] rawCompressed = new byte[dataCount];
++ rawDataStream.readFully(rawCompressed, 0, dataCount);
++
++ superBlock = rawDataStream.readLong();
++ if (superBlock != SUPERBLOCK)
++ throw new IOException("Footer superblock invalid " + this.path);
++
++ try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
++
++ int[] starts = new int[1024];
++ for (int i = 0; i < 1024; i++) {
++ starts[i] = dataStream.readInt();
++ dataStream.skipBytes(4); // Skip timestamps (Int): Unused.
++ }
++
++ for (int i = 0; i < 1024; i++) {
++ if (starts[i] > 0) {
++ int size = starts[i];
++ byte[] b = new byte[size];
++ dataStream.readFully(b, 0, size);
++
++ int maxCompressedLength = this.compressor.maxCompressedLength(size);
++ byte[] compressed = new byte[maxCompressedLength];
++ int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength);
++ b = new byte[compressedLength];
++ System.arraycopy(compressed, 0, b, 0, compressedLength);
++
++ this.buffer[i] = b;
++ this.bufferUncompressedSize[i] = size;
++ }
++ }
++ }
++ }
++ }
++
++ public Path getRegionFile() {
++ return this.path;
++ }
++
++ public ReentrantLock getFileLock() {
++ return this.fileLock;
++ }
++
++ public void flush() throws IOException {
++ if (isMarkedToSave()) flushWrapper(); // sync
++ }
++
++ private void markToSave() {
++ linearRegionFileFlusher.scheduleSave(this);
++ markedToSave.set(true);
++ }
++
++ public boolean isMarkedToSave() {
++ return markedToSave.getAndSet(false);
++ }
++
++ public void flushWrapper() {
++ try {
++ save();
++ } catch (IOException e) {
++ LOGGER.error("Failed to flush region file " + path.toAbsolutePath(), e);
++ }
++ }
++
++ public boolean doesChunkExist(ChunkPos pos) throws Exception {
++ throw new Exception("doesChunkExist is a stub");
++ }
++
++ private synchronized void save() throws IOException {
++ long timestamp = getTimestamp();
++ short chunkCount = 0;
++
++ File tempFile = new File(path.toString() + ".tmp");
++
++ try (FileOutputStream fileStream = new FileOutputStream(tempFile);
++ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream();
++ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel);
++ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream);
++ DataOutputStream dataStream = new DataOutputStream(fileStream)) {
++
++ dataStream.writeLong(SUPERBLOCK);
++ dataStream.writeByte(VERSION);
++ dataStream.writeLong(timestamp);
++ dataStream.writeByte(this.compressionLevel);
++
++ ArrayList byteBuffers = new ArrayList<>();
++ for (int i = 0; i < 1024; i++) {
++ if (this.bufferUncompressedSize[i] != 0) {
++ chunkCount += 1;
++ byte[] content = new byte[bufferUncompressedSize[i]];
++ this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]);
++
++ byteBuffers.add(content);
++ } else byteBuffers.add(null);
++ }
++ for (int i = 0; i < 1024; i++) {
++ zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size
++ zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp
++ }
++ for (int i = 0; i < 1024; i++) {
++ if (byteBuffers.get(i) != null)
++ zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length);
++ }
++ zstdDataStream.close();
++
++ dataStream.writeShort(chunkCount);
++
++ byte[] compressed = zstdByteArray.toByteArray();
++
++ dataStream.writeInt(compressed.length);
++ dataStream.writeLong(0);
++
++ dataStream.write(compressed, 0, compressed.length);
++ dataStream.writeLong(SUPERBLOCK);
++
++ dataStream.flush();
++ fileStream.getFD().sync();
++ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs
++ }
++ Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING);
++ }
++
++
++ public void setStatus(int x, int z, ChunkStatus status) {
++ this.statuses[getChunkIndex(x, z)] = status;
++ }
++
++ public synchronized void write(ChunkPos pos, ByteBuffer buffer) {
++ try {
++ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array()));
++ int uncompressedSize = b.length;
++
++ int maxCompressedLength = this.compressor.maxCompressedLength(b.length);
++ byte[] compressed = new byte[maxCompressedLength];
++ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength);
++ b = new byte[compressedLength];
++ System.arraycopy(compressed, 0, b, 0, compressedLength);
++
++ int index = getChunkIndex(pos.x, pos.z);
++ this.buffer[index] = b;
++ this.chunkTimestamps[index] = getTimestamp();
++ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize;
++ } catch (IOException e) {
++ LOGGER.error("Chunk write IOException " + e + " " + this.path);
++ }
++ markToSave();
++ }
++
++ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) {
++ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos)));
++ }
++
++ private class ChunkBuffer extends ByteArrayOutputStream {
++ private final ChunkPos pos;
++
++ public ChunkBuffer(ChunkPos chunkcoordintpair) {
++ super();
++ this.pos = chunkcoordintpair;
++ }
++
++ public void close() throws IOException {
++ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count);
++ LinearRegionFile.this.write(this.pos, bytebuffer);
++ }
++ }
++
++ private byte[] toByteArray(InputStream in) throws IOException {
++ ByteArrayOutputStream out = new ByteArrayOutputStream();
++ byte[] tempBuffer = new byte[4096];
++
++ int length;
++ while ((length = in.read(tempBuffer)) >= 0) {
++ out.write(tempBuffer, 0, length);
++ }
++
++ return out.toByteArray();
++ }
++
++ @Nullable
++ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) {
++ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) {
++ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]];
++ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]);
++ return new DataInputStream(new ByteArrayInputStream(content));
++ }
++ return null;
++ }
++
++ public ChunkStatus getStatusIfCached(int x, int z) {
++ return this.statuses[getChunkIndex(x, z)];
++ }
++
++ public void clear(ChunkPos pos) {
++ int i = getChunkIndex(pos.x, pos.z);
++ this.buffer[i] = null;
++ this.bufferUncompressedSize[i] = 0;
++ this.chunkTimestamps[i] = getTimestamp();
++ markToSave();
++ }
++
++ public boolean hasChunk(ChunkPos pos) {
++ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0;
++ }
++
++ public void close() throws IOException {
++ if (closed) return;
++ closed = true;
++ flush(); // sync
++ }
++
++ private static int getChunkIndex(int x, int z) {
++ return (x & 31) + ((z & 31) << 5);
++ }
++
++ private static int getTimestamp() {
++ return (int) (System.currentTimeMillis() / 1000L);
++ }
++
++ public boolean recalculateHeader() {
++ return false;
++ }
++
++ public void setOversized(int x, int z, boolean something) {}
++
++ public CompoundTag getOversizedData(int x, int z) throws IOException {
++ throw new IOException("getOversizedData is a stub " + this.path);
++ }
++
++ public boolean isOversized(int x, int z) {
++ return false;
++ }
++}
+diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e800c6103396530efe5acd0b4081a3cd05b62b68
+--- /dev/null
++++ b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java
+@@ -0,0 +1,45 @@
++package dev.kaiijumc.kaiiju.region;
++
++import com.google.common.util.concurrent.ThreadFactoryBuilder;
++import java.util.Queue;
++import java.util.concurrent.*;
++import me.earthme.luminol.LuminolConfig;
++import org.bukkit.Bukkit;
++
++public class LinearRegionFileFlusher {
++ private final Queue savingQueue = new LinkedBlockingQueue<>();
++ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
++ new ThreadFactoryBuilder()
++ .setNameFormat("linear-flush-scheduler")
++ .build()
++ );
++ private final ExecutorService executor = Executors.newFixedThreadPool(
++ LuminolConfig.linearFlushThreads,
++ new ThreadFactoryBuilder()
++ .setNameFormat("linear-flusher-%d")
++ .build()
++ );
++
++ public LinearRegionFileFlusher() {
++ Bukkit.getLogger().info("Using " + LuminolConfig.linearFlushThreads + " threads for linear region flushing.");
++ scheduler.scheduleAtFixedRate(this::pollAndFlush, 0L, LuminolConfig.linearFlushFrequency, TimeUnit.SECONDS);
++ }
++
++ public void scheduleSave(LinearRegionFile regionFile) {
++ if (savingQueue.contains(regionFile)) return;
++ savingQueue.add(regionFile);
++ }
++
++ private void pollAndFlush() {
++ while (!savingQueue.isEmpty()) {
++ LinearRegionFile regionFile = savingQueue.poll();
++ if (!regionFile.closed && regionFile.isMarkedToSave())
++ executor.execute(regionFile::flushWrapper);
++ }
++ }
++
++ public void shutdown() {
++ executor.shutdown();
++ scheduler.shutdown();
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
+index 8a11e10b01fa012b2f98b1c193c53251e848f909..61001e76b38f8647b33e73b5cc15b4b6785cf49a 100644
+--- a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
++++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
+@@ -811,7 +811,7 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
+ final ChunkDataController taskController) {
+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+ if (intendingToBlock) {
+- return taskController.computeForRegionFile(chunkX, chunkZ, true, (final RegionFile file) -> {
++ return taskController.computeForRegionFile(chunkX, chunkZ, true, (final dev.kaiijumc.kaiiju.region.AbstractRegionFile file) -> { // Kaiiju
+ if (file == null) { // null if no regionfile exists
+ return Boolean.FALSE;
+ }
+@@ -824,7 +824,7 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
+ return Boolean.FALSE;
+ } // else: it either exists or is not known, fall back to checking the loaded region file
+
+- return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final RegionFile file) -> {
++ return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final dev.kaiijumc.kaiiju.region.AbstractRegionFile file) -> { // Kaiiju
+ if (file == null) { // null if not loaded
+ // not sure at this point, let the I/O thread figure it out
+ return Boolean.TRUE;
+@@ -1126,9 +1126,9 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
+ return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ));
+ }
+
+- public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) {
++ public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { // Kaiiju
+ final RegionFileStorage cache = this.getCache();
+- final RegionFile regionFile;
++ final dev.kaiijumc.kaiiju.region.AbstractRegionFile regionFile; // Kaiiju
+ synchronized (cache) {
+ try {
+ regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly, true);
+@@ -1141,19 +1141,19 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
+ return function.apply(regionFile);
+ } finally {
+ if (regionFile != null) {
+- regionFile.fileLock.unlock();
++ regionFile.getFileLock().unlock(); // Kaiiju
+ }
+ }
+ }
+
+- public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) {
++ public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { // Kaiiju
+ final RegionFileStorage cache = this.getCache();
+- final RegionFile regionFile;
++ final dev.kaiijumc.kaiiju.region.AbstractRegionFile regionFile; // Kaiiju
+
+ synchronized (cache) {
+ regionFile = cache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ));
+ if (regionFile != null) {
+- regionFile.fileLock.lock();
++ regionFile.getFileLock().lock(); // Kaiiju
+ }
+ }
+
+@@ -1161,7 +1161,7 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
+ return function.apply(regionFile);
+ } finally {
+ if (regionFile != null) {
+- regionFile.fileLock.unlock();
++ regionFile.getFileLock().unlock(); // Kaiiju
+ }
+ }
+ }
+diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
+index 513833c2ea23df5b079d157bc5cb89d5c9754c0b..bd377033987bf7ded03016d1d8f05fd039bffbb5 100644
+--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
++++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
+@@ -84,8 +84,13 @@ public class ThreadedWorldUpgrader {
+ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert");
+ LOGGER.info("Starting conversion now for world " + this.worldName);
+
++ // Kaiiju start
++ dev.kaiijumc.kaiiju.region.RegionFileFormat formatName = me.earthme.luminol.LuminolConfig.regionFormatName;
++ int linearCompression = me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel;
++ LOGGER.info("Using format " + formatName + " (" + linearCompression + ")");
++ // Kaiiju end
+ final WorldInfo info = new WorldInfo(() -> worldPersistentData,
+- new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey);
++ new ChunkStorage(formatName, linearCompression, regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); // Kaiiju
+
+ long expectedChunks = (long)regionFiles.length * (32L * 32L);
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 3d526d7cfb313e419de89be1b275651982be42c7..90a6cfd011aaefe66fda79f887380ab2d62a07b1 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -29,6 +29,8 @@ public class LuminolConfig {
+
+ public static RegionFileFormat regionFormatName = RegionFileFormat.ANVIL;
+ public static int regionFormatLinearCompressionLevel = 1;
++ public static int linearFlushFrequency = 10;
++ public static int linearFlushThreads = 1;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -67,6 +69,12 @@ public class LuminolConfig {
+ logger.error("Falling back to compression level 1.");
+ regionFormatLinearCompressionLevel = 1;
+ }
++ linearFlushFrequency = get("save.region-format.linear.flush-frequency", linearFlushFrequency);
++ linearFlushThreads = get("save.region-format.linear.flush-max-threads", linearFlushThreads);
++ if (linearFlushThreads < 0)
++ linearFlushThreads = Math.max(Runtime.getRuntime().availableProcessors() + linearFlushThreads, 1);
++ else
++ linearFlushThreads = Math.max(linearFlushThreads, 1);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 50597a8b45bbd7dcc40b361da78358d9d01f5484..4709782f3e858edfa6ce25696462eb45909885ee 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -212,7 +212,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ // Paper end - optimise chunk tick iteration
+
+ public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) {
+- super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
++ super(me.earthme.luminol.LuminolConfig.regionFormatName, me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel, session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); // Kaiiju
+ // Paper - rewrite chunk system
+ this.tickingGenerated = new AtomicInteger();
+ //this.playerMap = new PlayerMap(); // Folia - region threading
+@@ -257,7 +257,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system
+ this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
+ this.overworldDataStorage = persistentStateManagerFactory;
+- this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, iregistrycustom, world);
++ this.poiManager = new PoiManager(me.earthme.luminol.LuminolConfig.regionFormatName, me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel, path.resolve("poi"), dataFixer, dsync, iregistrycustom, world); // Kaiiju
+ this.setServerViewDistance(viewDistance);
+ // Paper start
+ this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
+@@ -823,13 +823,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+
+ // Paper start - chunk status cache "api"
+ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) {
+- net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos); // Kaiiju
+
+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+ }
+
+ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
+- net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true); // Kaiiju
+
+ if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) {
+ return null;
+@@ -847,7 +847,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }
+
+ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
+- net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false); // Kaiiju
+
+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
+ }
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 722a5ec7f8e4995ac7025ca0785145d46bd66fcd..561681deaf647277ecde64eed4cfbd9f38b5fed1 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -453,8 +453,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
+
+ private static final class EntityRegionFileStorage extends net.minecraft.world.level.chunk.storage.RegionFileStorage {
+
+- public EntityRegionFileStorage(Path directory, boolean dsync) {
+- super(directory, dsync);
++ public EntityRegionFileStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path directory, boolean dsync) { // Kaiiju
++ super(format, linearCompression, directory, dsync); // Kaiiju
+ }
+
+ protected void write(ChunkPos pos, net.minecraft.nbt.CompoundTag nbt) throws IOException {
+@@ -812,7 +812,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ // CraftBukkit end
+ boolean flag2 = minecraftserver.forceSynchronousWrites();
+ DataFixer datafixer = minecraftserver.getFixerUpper();
+- this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
++ this.entityStorage = new EntityRegionFileStorage(me.earthme.luminol.LuminolConfig.regionFormatName, me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); // Kaiiju
+
+ // this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper // Paper - rewrite chunk system
+ StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
+diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+index e16ef1b7c0bfe6d6194c09f6787a50fd9b28f55e..cc70b729663bececec45a03ec21a94792e5f2052 100644
+--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
++++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+@@ -61,7 +61,7 @@ public class WorldUpgrader {
+ private volatile int skipped;
+ private final Object2FloatMap> progressMap = Object2FloatMaps.synchronize(new Object2FloatOpenCustomHashMap(Util.identityStrategy()));
+ private volatile Component status = Component.translatable("optimizeWorld.stage.counting");
+- public static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
++ public static Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.(linear | mca)$"); // Kaiiju
+ private final DimensionDataStorage overworldDataStorage;
+
+ public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, Registry dimensionOptionsRegistry, boolean eraseCache) {
+@@ -116,7 +116,12 @@ public class WorldUpgrader {
+ ResourceKey resourcekey1 = (ResourceKey) iterator1.next();
+ Path path = this.levelStorage.getDimensionPath(resourcekey1);
+
+- builder1.put(resourcekey1, new ChunkStorage(path.resolve("region"), this.dataFixer, true));
++ // Kaiiju start
++ String worldName = this.levelStorage.getLevelId();
++ dev.kaiijumc.kaiiju.region.RegionFileFormat formatName = me.earthme.luminol.LuminolConfig.regionFormatName;
++ int linearCompression = me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel;
++ builder1.put(resourcekey1, new ChunkStorage(formatName, linearCompression, path.resolve("region"), this.dataFixer, true));
++ // Kaiiju end
+ }
+
+ ImmutableMap, ChunkStorage> immutablemap1 = builder1.build();
+@@ -235,7 +240,7 @@ public class WorldUpgrader {
+ File file = this.levelStorage.getDimensionPath(world).toFile();
+ File file1 = new File(file, "region");
+ File[] afile = file1.listFiles((file2, s) -> {
+- return s.endsWith(".mca");
++ return s.endsWith(".mca") || s.endsWith(".linear"); // Kaiiju
+ });
+
+ if (afile == null) {
+@@ -254,7 +259,11 @@ public class WorldUpgrader {
+ int l = Integer.parseInt(matcher.group(2)) << 5;
+
+ try {
+- RegionFile regionfile = new RegionFile(file2.toPath(), file1.toPath(), true);
++ // Kaiiju start
++ String worldName = this.levelStorage.getLevelId();
++ int linearCompression = me.earthme.luminol.LuminolConfig.regionFormatLinearCompressionLevel;
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(linearCompression, file2.toPath(), file1.toPath(), true);
++ // Kaiiju end
+
+ try {
+ for (int i1 = 0; i1 < 32; ++i1) {
+diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+index 5150d447c9dc2f539446749c8bee102050bab4ed..187ff795192c7eb56dffafa1ff6fa3068ac341c3 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+@@ -59,8 +59,8 @@ public class PoiManager extends SectionStorage {
+ // Paper end - rewrite chunk system
+
+
+- public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) {
+- super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world);
++ public PoiManager(dev.kaiijumc.kaiiju.region.RegionFileFormat formatName, int linearCompression, Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { // Kaiiju
++ super(formatName, linearCompression, path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); // Kaiiju
+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+index 8ebecb588058da174b0e0e19e54fcddfeeca1422..1d880f27dd147da683fc30ed6f1bfa43ecdb7d93 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+@@ -37,11 +37,11 @@ public class ChunkStorage implements AutoCloseable {
+ public final RegionFileStorage regionFileCache;
+ // Paper end - async chunk loading
+
+- public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) {
++ public ChunkStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path directory, DataFixer dataFixer, boolean dsync) { // Kaiiju
+ this.fixerUpper = dataFixer;
+ // Paper start - async chunk io
+ // remove IO worker
+- this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper
++ this.regionFileCache = new RegionFileStorage(format, linearCompression, directory, dsync, true); // Paper - nuke IOWorker // Paper
+ // Paper end - async chunk io
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+index 9248769e6d357f6eec68945fd7700e79b2942c41..024870a31469c40cd680be0399ce0e0e73839a0f 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -26,7 +26,7 @@ import net.minecraft.nbt.NbtIo; // Paper
+ import net.minecraft.world.level.ChunkPos;
+ import org.slf4j.Logger;
+
+-public class RegionFile implements AutoCloseable {
++public class RegionFile implements AutoCloseable, dev.kaiijumc.kaiiju.region.AbstractRegionFile { // Kaiiju
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final int SECTOR_BYTES = 4096;
+@@ -50,6 +50,16 @@ public class RegionFile implements AutoCloseable {
+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper
+ public final Path regionFile; // Paper
+
++ // Kaiiju start - Abstract getters
++ public Path getRegionFile() {
++ return this.regionFile;
++ }
++
++ public java.util.concurrent.locks.ReentrantLock getFileLock() {
++ return this.fileLock;
++ }
++ // Kaiiju end
++
+ // Paper start - try to recover from RegionFile header corruption
+ private static long roundToSectors(long bytes) {
+ long sectors = bytes >>> 12; // 4096 = 2^12
+@@ -128,7 +138,7 @@ public class RegionFile implements AutoCloseable {
+ }
+
+ // note: only call for CHUNK regionfiles
+- boolean recalculateHeader() throws IOException {
++ public boolean recalculateHeader() throws IOException { // Kaiiju
+ if (!this.canRecalcHeader) {
+ return false;
+ }
+@@ -954,10 +964,10 @@ public class RegionFile implements AutoCloseable {
+ private static int getChunkIndex(int x, int z) {
+ return (x & 31) + (z & 31) * 32;
+ }
+- synchronized boolean isOversized(int x, int z) {
++ public synchronized boolean isOversized(int x, int z) { // Kaiiju
+ return this.oversized[getChunkIndex(x, z)] == 1;
+ }
+- synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
++ public synchronized void setOversized(int x, int z, boolean oversized) throws IOException { // Kaiiju
+ final int offset = getChunkIndex(x, z);
+ boolean previous = this.oversized[offset] == 1;
+ this.oversized[offset] = (byte) (oversized ? 1 : 0);
+@@ -996,7 +1006,7 @@ public class RegionFile implements AutoCloseable {
+ return this.regionFile.getParent().resolve(this.regionFile.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
+ }
+
+- synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
++ public synchronized CompoundTag getOversizedData(int x, int z) throws IOException { // Kaiiju
+ Path file = getOversizedFile(x, z);
+ try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) {
+ return NbtIo.read((java.io.DataInput) out);
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+index db571f658f636cdda1dcdbaffa0c4da67fae11ad..21537b14c6c6789da3db04577f1c93998397678d 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -21,9 +21,13 @@ public class RegionFileStorage implements AutoCloseable {
+
+ public static final String ANVIL_EXTENSION = ".mca";
+ private static final int MAX_CACHE_SIZE = 256;
+- public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap();
++ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); // Kaiiju
+ private final Path folder;
+ private final boolean sync;
++ // Kaiiju start - Per world chunk format
++ public final dev.kaiijumc.kaiiju.region.RegionFileFormat format;
++ public final int linearCompression;
++ // Kaiiju end
+ private final boolean isChunkData; // Paper
+
+ // Paper start - cache regionfile does not exist state
+@@ -55,11 +59,15 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+ // Paper end - cache regionfile does not exist state
+
+- protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor
++ protected RegionFileStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path directory, boolean dsync) { // Paper - protected constructor
+ // Paper start - add isChunkData param
+- this(directory, dsync, false);
++ this(format, linearCompression, directory, dsync, false);
+ }
+- RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) {
++ RegionFileStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path directory, boolean dsync, boolean isChunkData) { // Kaiiju
++ // Kaiiju start
++ this.format = format;
++ this.linearCompression = linearCompression;
++ // Kaiiju end
+ this.isChunkData = isChunkData;
+ // Paper end - add isChunkData param
+ this.folder = directory;
+@@ -70,7 +78,7 @@ public class RegionFileStorage implements AutoCloseable {
+ @Nullable
+ public static ChunkPos getRegionFileCoordinates(Path file) {
+ String fileName = file.getFileName().toString();
+- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca") || !fileName.endsWith(".linear")) { // Kaiiju
+ return null;
+ }
+
+@@ -90,29 +98,29 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+ }
+
+- public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) {
++ public synchronized dev.kaiijumc.kaiiju.region.AbstractRegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { // Kaiiju
+ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()));
+ }
+
+ public synchronized boolean chunkExists(ChunkPos pos) throws IOException {
+- RegionFile regionfile = getRegionFile(pos, true);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = getRegionFile(pos, true); // Kaiiju
+
+ return regionfile != null ? regionfile.hasChunk(pos) : false;
+ }
+
+- public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
++ public synchronized dev.kaiijumc.kaiiju.region.AbstractRegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Kaiiju
+ return this.getRegionFile(chunkcoordintpair, existingOnly, false);
+ }
+- public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException {
++ public synchronized dev.kaiijumc.kaiiju.region.AbstractRegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { // Kaiiju
+ // Paper end
+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER
+- RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = this.regionCache.getAndMoveToFirst(i); // Kaiiju
+
+ if (regionfile != null) {
+ // Paper start
+ if (lock) {
+ // must be in this synchronized block
+- regionfile.fileLock.lock();
++ regionfile.getFileLock().lock(); // Kaiiju
+ }
+ // Paper end
+ return regionfile;
+@@ -123,28 +131,45 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+ // Paper end - cache regionfile does not exist state
+ if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - configurable
+- ((RegionFile) this.regionCache.removeLast()).close();
++ this.regionCache.removeLast().close(); // Kaiiju
+ }
+
+ // Paper - only create directory if not existing only - moved down
+ Path path = this.folder;
+ int j = chunkcoordintpair.getRegionX();
+- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
+- if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state
+- this.markNonExisting(regionPos);
+- return null; // CraftBukkit
++ // Kaiiju start - Polyglot
++ //Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
++ Path path1;
++ if (existingOnly) {
++ Path anvil = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
++ Path linear = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".linear");
++ if (java.nio.file.Files.exists(anvil)) path1 = anvil;
++ else if (java.nio.file.Files.exists(linear)) path1 = linear;
++ else {
++ this.markNonExisting(regionPos);
++ return null;
++ }
++ // Kaiiju end
+ } else {
++ // Kaiiju start - Polyglot
++ String extension = switch (this.format) {
++ case LINEAR -> "linear";
++ default -> "mca";
++ };
++ path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + "." + extension);
++ // Kaiiju end
+ this.createRegionFile(regionPos);
+ }
++
+ // Paper end - cache regionfile does not exist state
+ FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above
+- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile1 = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(this.linearCompression, path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header // Kaiiju
+
+ this.regionCache.putAndMoveToFirst(i, regionfile1);
+ // Paper start
+ if (lock) {
+ // must be in this synchronized block
+- regionfile1.fileLock.lock();
++ regionfile1.getFileLock().lock(); // Kaiiju
+ }
+ // Paper end
+ return regionfile1;
+@@ -172,7 +197,7 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+
+
+- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
++ private static CompoundTag readOversizedChunk(dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // Kaiiju
+ synchronized (regionfile) {
+ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
+ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
+@@ -219,14 +244,14 @@ public class RegionFileStorage implements AutoCloseable {
+ @Nullable
+ public CompoundTag read(ChunkPos pos) throws IOException {
+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
+- RegionFile regionfile = this.getRegionFile(pos, true, true); // Paper
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = this.getRegionFile(pos, true, true); // Paper // Kaiiju
+ if (regionfile == null) {
+ return null;
+ }
+ // Paper start - Add regionfile parameter
+ return this.read(pos, regionfile);
+ }
+- public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException {
++ public CompoundTag read(ChunkPos pos, dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile) throws IOException { // Kaiiju
+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
+ // if we decide to re-read
+ // Paper end
+@@ -236,7 +261,7 @@ public class RegionFileStorage implements AutoCloseable {
+
+ // Paper start
+ if (regionfile.isOversized(pos.x, pos.z)) {
+- printOversizedLog("Loading Oversized Chunk!", regionfile.regionFile, pos.x, pos.z);
++ printOversizedLog("Loading Oversized Chunk!", regionfile.getRegionFile(), pos.x, pos.z); // Kaiiju
+ return readOversizedChunk(regionfile, pos);
+ }
+ // Paper end
+@@ -250,12 +275,12 @@ public class RegionFileStorage implements AutoCloseable {
+ if (this.isChunkData) {
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
+ if (!chunkPos.equals(pos)) {
+- net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath());
++ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.getRegionFile().toAbsolutePath()); // Kaiiju
+ if (regionfile.recalculateHeader()) {
+- regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
++ regionfile.getFileLock().lock(); // otherwise we will unlock twice and only lock once. // Kaiiju
+ return this.read(pos, regionfile);
+ }
+- net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath());
++ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.getRegionFile().toAbsolutePath()); // Kaiiju
+ return null;
+ }
+ }
+@@ -289,13 +314,13 @@ public class RegionFileStorage implements AutoCloseable {
+
+ return nbttagcompound;
+ } finally { // Paper start
+- regionfile.fileLock.unlock();
++ regionfile.getFileLock().unlock(); // Kaiiju
+ } // Paper end
+ }
+
+ public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException {
+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
+- RegionFile regionfile = this.getRegionFile(chunkPos, true);
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = this.getRegionFile(chunkPos, true); // Kaiiju
+ if (regionfile == null) {
+ return;
+ }
+@@ -325,7 +350,7 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+
+ protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
+- RegionFile regionfile = this.getRegionFile(pos, nbt == null, true); // CraftBukkit // Paper // Paper start - rewrite chunk system
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = this.getRegionFile(pos, nbt == null, true); // CraftBukkit // Paper // Paper start - rewrite chunk system // Kaiiju
+ if (nbt == null && regionfile == null) {
+ return;
+ }
+@@ -375,7 +400,7 @@ public class RegionFileStorage implements AutoCloseable {
+ }
+ // Paper end
+ } finally { // Paper start
+- regionfile.fileLock.unlock();
++ regionfile.getFileLock().unlock(); // Kaiiju
+ } // Paper end
+ }
+
+@@ -384,7 +409,7 @@ public class RegionFileStorage implements AutoCloseable {
+ ObjectIterator objectiterator = this.regionCache.values().iterator();
+
+ while (objectiterator.hasNext()) {
+- RegionFile regionfile = (RegionFile) objectiterator.next();
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = (dev.kaiijumc.kaiiju.region.AbstractRegionFile) objectiterator.next(); // Kaiiju
+
+ try {
+ regionfile.close();
+@@ -400,7 +425,7 @@ public class RegionFileStorage implements AutoCloseable {
+ ObjectIterator objectiterator = this.regionCache.values().iterator();
+
+ while (objectiterator.hasNext()) {
+- RegionFile regionfile = (RegionFile) objectiterator.next();
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = (dev.kaiijumc.kaiiju.region.AbstractRegionFile) objectiterator.next(); // Kaiiju
+
+ regionfile.flush();
+ }
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+index 4aac1979cf57300825a999c876fcf24d3170e68e..3b96582f15d0985b670b5b5a1548800dee2154f4 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+@@ -47,8 +47,8 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl
+ public final RegistryAccess registryAccess; // Paper - rewrite chunk system
+ protected final LevelHeightAccessor levelHeightAccessor;
+
+- public SectionStorage(Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) {
+- super(path, dsync); // Paper - remove mojang I/O thread
++ public SectionStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) { // Kaiiju
++ super(format, linearCompression, path, dsync); // Paper - remove mojang I/O thread // Kaiiju
+ this.codec = codecFactory;
+ this.factory = factory;
+ this.fixerUpper = dataFixer;
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index bcbf7ba7c687ecaa530d4de3af45ebeb80dd15c5..f19db38e64854d051d4d8dbbb3ff4236b5d4537b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -561,7 +561,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ return true;
+ }
+
+- net.minecraft.world.level.chunk.storage.RegionFile file;
++ dev.kaiijumc.kaiiju.region.AbstractRegionFile file; // Kaiiju
+ try {
+ file = world.getChunkSource().chunkMap.regionFileCache.getRegionFile(chunkPos, false);
+ } catch (java.io.IOException ex) {
diff --git a/patches/server/0015-Add-a-simple-tpsbar.patch b/patches/server/0015-Add-a-simple-tpsbar.patch
new file mode 100644
index 0000000..7c14df3
--- /dev/null
+++ b/patches/server/0015-Add-a-simple-tpsbar.patch
@@ -0,0 +1,356 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 15:19:16 +0800
+Subject: [PATCH] Add a simple tpsbar
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 90a6cfd011aaefe66fda79f887380ab2d62a07b1..0657fce8aabb956a400b3cead53c28ef52e67fe9 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -2,11 +2,15 @@ package me.earthme.luminol;
+
+ import dev.kaiijumc.kaiiju.region.RegionFileFormat;
+ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
++import me.earthme.luminol.commands.TpsBarCommand;
++import me.earthme.luminol.functions.GlobalServerTpsBar;
+ import net.minecraft.server.level.ServerLevel;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
++import org.bukkit.Bukkit;
+
+ import java.util.Arrays;
++import java.util.List;
+ import java.util.logging.Level;
+ import java.io.File;
+ import java.io.IOException;
+@@ -20,6 +24,10 @@ public class LuminolConfig {
+ public static String serverModName = "Luminol";
+ public static boolean fakeVanillaModeEnabled = false;
+ public static boolean disableChatSign = false;
++ public static boolean tpsbarEnabled = false;
++ public static String tpsBarFormat = "TPS: MSPT: Ping: ms";
++ public static String[] tpsColors = new String[]{"GREEN","YELLOW","RED","PURPLE"};
++ public static String[] pingColors = new String[]{"GREEN","YELLOW","RED","PURPLE"};
+
+ public static boolean safeTeleportation = true;
+ public static boolean enableSandDuping = false;
+@@ -46,10 +54,25 @@ public class LuminolConfig {
+ MAIN_CONFIG.save();
+ }
+
++ public static void initTpsbar(){
++ if (tpsbarEnabled){
++ GlobalServerTpsBar.init();
++ Bukkit.getCommandMap().register("tpsbar","luminol",new TpsBarCommand("tpsbar"));
++ }
++ }
++
+ public static void initValues(){
+ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
+ disableChatSign = get("misc.disable_chat_sign",disableChatSign,"Set this to true to disable mojang's chat sign");
++ tpsbarEnabled = get("misc.enable_tpsbar",tpsbarEnabled,"When this enabled,You or your players can see the tps,mspt and ping through a simple bossbar");
++ tpsBarFormat = get("misc.tpsbar_title_format",tpsBarFormat,"The format of tpsbar.");
++ tpsColors = get("misc.tpsbar_range_colors", List.of(tpsColors),"The bar and text color of each tps ranges.The last is the color of initial bar's color").toArray(String[]::new);
++ pingColors = get("misc.tpsbar_ping_range_colors",List.of(pingColors),"As same as the tpsColors").toArray(String[]::new);
++
++ if (tpsbarEnabled){
++ initTpsbar();
++ }
+
+ safeTeleportation = get("fixes.enable_safe_teleportation",safeTeleportation,"If this enabled,the end portals will not teleport removed entities.");
+ enableSandDuping = get("fixes.enable_sand_duping",enableSandDuping,"If this enabled,The value of safe teleportation will always be false and sand duping will be enabled");
+diff --git a/src/main/java/me/earthme/luminol/commands/TpsBarCommand.java b/src/main/java/me/earthme/luminol/commands/TpsBarCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3f74f748501d2f915869e9077dd2f2206075346b
+--- /dev/null
++++ b/src/main/java/me/earthme/luminol/commands/TpsBarCommand.java
+@@ -0,0 +1,40 @@
++package me.earthme.luminol.commands;
++
++import me.earthme.luminol.functions.GlobalServerTpsBar;
++import org.bukkit.ChatColor;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++import org.bukkit.entity.Player;
++import org.jetbrains.annotations.NotNull;
++
++public class TpsBarCommand extends Command {
++ public TpsBarCommand(@NotNull String name) {
++ super(name);
++ this.setPermission("molia.commands.tpsbar");
++ this.setDescription("Show the tps and mspt through a bossbar");
++ this.setUsage("/tpsbar");
++ }
++
++ @Override
++ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
++ if (!testPermission(sender)){
++ return true;
++ }
++
++ if (!(sender instanceof Player player)){
++ sender.sendMessage(ChatColor.RED+"Only player can use this command!");
++ return true;
++ }
++
++ if (GlobalServerTpsBar.isPlayerVisible(player)) {
++ player.sendMessage(ChatColor.BLUE + "Disabled tps bar");
++ GlobalServerTpsBar.setVisibilityForPlayer(player,false);
++ return true;
++ }
++
++ player.sendMessage(ChatColor.GREEN + "Enabled tps bar");
++ GlobalServerTpsBar.setVisibilityForPlayer(player,true);
++
++ return true;
++ }
++}
+diff --git a/src/main/java/me/earthme/luminol/functions/GlobalServerTpsBar.java b/src/main/java/me/earthme/luminol/functions/GlobalServerTpsBar.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1b97730810895a1a049e8942dc47c20cd750d14c
+--- /dev/null
++++ b/src/main/java/me/earthme/luminol/functions/GlobalServerTpsBar.java
+@@ -0,0 +1,206 @@
++package me.earthme.luminol.functions;
++
++import com.google.common.collect.Lists;
++import io.papermc.paper.threadedregions.ThreadedRegionizer;
++import io.papermc.paper.threadedregions.TickData;
++import io.papermc.paper.threadedregions.TickRegions;
++import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
++import me.earthme.luminol.LuminolConfig;
++import net.kyori.adventure.bossbar.BossBar;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.minimessage.MiniMessage;
++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
++import org.bukkit.entity.Player;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.*;
++import java.util.concurrent.TimeUnit;
++
++public class GlobalServerTpsBar {
++ protected static final MinecraftInternalPlugin NULL_PLUGIN = new MinecraftInternalPlugin();
++ protected static final Map uuid2Bossbars = new HashMap<>();
++ protected static final List visibleExclude = Lists.newCopyOnWriteArrayList();
++ protected static volatile ScheduledTask tpsbarTask = null;
++
++ public static void init(){
++ Bukkit.getAsyncScheduler().runAtFixedRate(NULL_PLUGIN,c -> {
++ tpsbarTask = c;
++ try {
++ update();
++ }catch (Exception e){
++ e.printStackTrace();
++ }
++ },1,1, TimeUnit.SECONDS);
++ }
++
++ public static void cancelBarUpdateTask(){
++ if (tpsbarTask == null){
++ return;
++ }
++
++ tpsbarTask.cancel();
++ }
++
++ public static boolean isPlayerVisible(Player player){
++ return !visibleExclude.contains(player.getUniqueId());
++ }
++
++ public static void setVisibilityForPlayer(Player target,boolean canSee){
++ if (!canSee){
++ visibleExclude.add(target.getUniqueId());
++ return;
++ }
++
++ visibleExclude.remove(target.getUniqueId());
++ }
++
++ private static void update(){
++ updateBarValues();
++ cleanUpPlayers();
++ }
++
++ private static void cleanUpPlayers(){
++ final List toRemove = new ArrayList<>();
++
++ for (Map.Entry bossBarEntry : uuid2Bossbars.entrySet()){
++ final UUID uuid = bossBarEntry.getKey();
++ boolean shouldRemove = true;
++
++ for (Player player : Bukkit.getOnlinePlayers()){
++ if (player.getUniqueId() == uuid){
++ shouldRemove = false;
++ break;
++ }
++ }
++
++ if (shouldRemove){
++ toRemove.add(uuid);
++ }
++ }
++
++ for (UUID uuid : toRemove){
++ uuid2Bossbars.remove(uuid);
++ }
++ }
++
++ private static void updateBarValues(){
++ for (Player apiPlayer : Bukkit.getOnlinePlayers()){
++ final ServerPlayer nmsPlayer = ((CraftPlayer) apiPlayer).getHandle();
++ final ThreadedRegionizer.ThreadedRegion region = ((ServerLevel) nmsPlayer.level()).regioniser.getRegionAtUnsynchronised(nmsPlayer.sectionX,nmsPlayer.sectionZ);
++
++ if (region == null){
++ continue;
++ }
++
++ final TickData.TickReportData reportData = region.getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime());
++
++ BossBar targetBossbar = uuid2Bossbars.get(nmsPlayer.getUUID());
++
++ if (targetBossbar == null && !visibleExclude.contains(nmsPlayer.getUUID())){
++ targetBossbar = BossBar.bossBar(Component.text(""),0.0F, BossBar.Color.valueOf(LuminolConfig.tpsColors[3]), BossBar.Overlay.NOTCHED_20);
++ uuid2Bossbars.put(nmsPlayer.getUUID(),targetBossbar);
++ apiPlayer.showBossBar(targetBossbar);
++ }
++
++ if (reportData != null && targetBossbar != null){
++ final TickData.SegmentData tpsData = reportData.tpsData().segmentAll();
++ final double mspt = reportData.timePerTickData().segmentAll().average() / 1.0E6;
++ updateTpsBar(tpsData.average(),mspt,targetBossbar,apiPlayer);
++ }
++ }
++ }
++
++ private static void updateTpsBar(double tps, double mspt, @NotNull BossBar bar, @NotNull Player player){
++ bar.name(MiniMessage.miniMessage().deserialize(
++ LuminolConfig.tpsBarFormat,
++ Placeholder.component("tps",getTpsComponent(tps)),
++ Placeholder.component("mspt",getMsptComponent(mspt)),
++ Placeholder.component("ping",getPingComponent(player.getPing()))
++ ));
++ bar.color(barColorFromTps(tps));
++ bar.progress((float) Math.min(mspt / 50,1.0));
++ }
++
++ private static @NotNull Component getPingComponent(int ping){
++ final BossBar.Color colorBukkit = barColorFromPing(ping);
++ final String colorString = colorBukkit.name();
++
++ final String content = "<%s>%s>";
++ final String replaced = String.format(content,colorString,colorString);
++
++ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.valueOf(ping)));
++ }
++
++ private static BossBar.Color barColorFromPing(int ping){
++ if (ping == -1){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[3]);
++ }
++
++ if (ping <= 80){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[0]);
++ }
++
++ if (ping <= 160){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[1]);
++ }
++
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[2]);
++ }
++
++ private static @NotNull Component getMsptComponent(double mspt){
++ final BossBar.Color colorBukkit = barColorFromMspt(mspt);
++ final String colorString = colorBukkit.name();
++
++ final String content = "<%s>%s>";
++ final String replaced = String.format(content,colorString,colorString);
++
++ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", mspt)));
++ }
++
++ private static BossBar.Color barColorFromMspt(double mspt){
++ if (mspt == -1){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[3]);
++ }
++
++ if (mspt <= 25){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[0]);
++ }
++
++ if (mspt <= 50){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[1]);
++ }
++
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[2]);
++ }
++
++ private static @NotNull Component getTpsComponent(double tps){
++ final BossBar.Color colorBukkit = barColorFromTps(tps);
++ final String colorString = colorBukkit.name();
++
++ final String content = "<%s>%s>";
++ final String replaced = String.format(content,colorString,colorString);
++
++ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", tps)));
++ }
++
++ private static BossBar.Color barColorFromTps(double tps){
++ if (tps == -1){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[3]);
++ }
++
++ if (tps >= 18){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[0]);
++ }
++
++ if (tps >= 15){
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[1]);
++ }
++
++ return BossBar.Color.valueOf(LuminolConfig.tpsColors[2]);
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index f0bf57a7acd77eeffbeeb6743ba58166823022fd..4a1e068c27853a38db0641806626e7ac740bd8de 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -1,20 +1,16 @@
+ package net.minecraft.server.dedicated;
+
+-import com.google.common.collect.Lists;
++import me.earthme.luminol.functions.GlobalServerTpsBar;
+ import com.mojang.authlib.GameProfile;
+ import com.mojang.datafixers.DataFixer;
+ import com.mojang.logging.LogUtils;
+-import java.io.BufferedReader;
++
+ import java.io.BufferedWriter;
+ import java.io.IOException;
+-import java.io.InputStreamReader;
+ import java.net.InetAddress;
+ import java.net.Proxy;
+-import java.nio.charset.StandardCharsets;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+-import java.util.Collections;
+-import java.util.List;
+ import java.util.Locale;
+ import java.util.Optional;
+ import java.util.function.BooleanSupplier;
+@@ -829,6 +825,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+
+ @Override
+ public void stopServer() {
++ GlobalServerTpsBar.cancelBarUpdateTask(); //Luminol - Tpsbar
+ super.stopServer();
+ //Util.shutdownExecutors(); // Paper - moved into super
+ SkullBlockEntity.clear();
diff --git a/patches/server/0016-Petal-Reduce-sensor-work.patch b/patches/server/0016-Petal-Reduce-sensor-work.patch
new file mode 100644
index 0000000..4032f6f
--- /dev/null
+++ b/patches/server/0016-Petal-Reduce-sensor-work.patch
@@ -0,0 +1,55 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 15:36:59 +0800
+Subject: [PATCH] Petal Reduce sensor work
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 0657fce8aabb956a400b3cead53c28ef52e67fe9..c3a481cb7bea0619b1af0e3203e9d88514e84c62 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -40,6 +40,8 @@ public class LuminolConfig {
+ public static int linearFlushFrequency = 10;
+ public static int linearFlushThreads = 1;
+
++ public static boolean reduceSensorWork = true;
++
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+
+@@ -98,6 +100,8 @@ public class LuminolConfig {
+ linearFlushThreads = Math.max(Runtime.getRuntime().availableProcessors() + linearFlushThreads, 1);
+ else
+ linearFlushThreads = Math.max(linearFlushThreads, 1);
++
++ reduceSensorWork = get("optimizations.reduce_sensor_work",reduceSensorWork,"This optimization is from petal.You can find out more about it on petal's repository");
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
+index 4e9d510646abbc2d2b6f2d935f7416b6872eb234..d3f8aa29b05a3813c0ec6e2ea5a253868abd6b07 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -9,6 +9,8 @@ import java.util.Optional;
+ import java.util.UUID;
+ import java.util.function.Predicate;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.NonNullList;
+ import net.minecraft.core.Vec3i;
+@@ -924,10 +926,11 @@ public abstract class Mob extends LivingEntity implements Targeting {
+ return;
+ }
+ // Paper end
++ int i = this.tickCount + this.getId(); // Folia - region threading //Luminol - Petal - Move up
++
+ this.level().getProfiler().push("sensing");
+- this.sensing.tick();
++ if (i % 10 == 0 || !LuminolConfig.reduceSensorWork)this.sensing.tick(); //Luminol - Petal - Reduce sensor work
+ this.level().getProfiler().pop();
+- int i = this.tickCount + this.getId(); // Folia - region threading
+
+ if (i % 2 != 0 && this.tickCount > 1) {
+ this.level().getProfiler().push("targetSelector");
diff --git a/patches/server/0017-Add-config-for-username-check.patch b/patches/server/0017-Add-config-for-username-check.patch
new file mode 100644
index 0000000..9a602e7
--- /dev/null
+++ b/patches/server/0017-Add-config-for-username-check.patch
@@ -0,0 +1,52 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 15:56:26 +0800
+Subject: [PATCH] Add config for username check
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index c3a481cb7bea0619b1af0e3203e9d88514e84c62..0929a5a167691bde7dedaa1e2812b34ad69913d6 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -28,6 +28,7 @@ public class LuminolConfig {
+ public static String tpsBarFormat = "TPS: MSPT: Ping: ms";
+ public static String[] tpsColors = new String[]{"GREEN","YELLOW","RED","PURPLE"};
+ public static String[] pingColors = new String[]{"GREEN","YELLOW","RED","PURPLE"};
++ public static boolean disableUsernameCheck = false;
+
+ public static boolean safeTeleportation = true;
+ public static boolean enableSandDuping = false;
+@@ -71,6 +72,7 @@ public class LuminolConfig {
+ tpsBarFormat = get("misc.tpsbar_title_format",tpsBarFormat,"The format of tpsbar.");
+ tpsColors = get("misc.tpsbar_range_colors", List.of(tpsColors),"The bar and text color of each tps ranges.The last is the color of initial bar's color").toArray(String[]::new);
+ pingColors = get("misc.tpsbar_ping_range_colors",List.of(pingColors),"As same as the tpsColors").toArray(String[]::new);
++ disableUsernameCheck = get("misc.disable_username_check",disableUsernameCheck,"Disable username check that can accept usernames with other characters(such as Chinese).Not recommended to use");
+
+ if (tpsbarEnabled){
+ initTpsbar();
+diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+index f7c2d6d82ee1b5975cd114934b7beaec3d5d490d..f26c44bb8c4e3d8556c8c5ac7389e02381239594 100644
+--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -16,6 +16,8 @@ import java.util.concurrent.atomic.AtomicInteger;
+ import javax.annotation.Nullable;
+ import javax.crypto.Cipher;
+ import javax.crypto.SecretKey;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.DefaultUncaughtExceptionHandler;
+ import net.minecraft.core.UUIDUtil;
+ import net.minecraft.network.Connection;
+@@ -162,10 +164,10 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ @Override
+ public void handleHello(ServerboundHelloPacket packet) {
+ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]);
+- Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]);
++ if (!LuminolConfig.disableUsernameCheck) Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]); //Luminol - Add config for usename check
+ // Paper start - validate usernames
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation) {
+- if (!this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation && !validateUsername(packet.name())) {
++ if (!this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation && !validateUsername(packet.name()) && !LuminolConfig.disableUsernameCheck) { //Luminol - Add config for username check
+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
+ return;
+ }
diff --git a/patches/server/0018-Pufferfish-Optimize-entity-coordinate-key.patch b/patches/server/0018-Pufferfish-Optimize-entity-coordinate-key.patch
new file mode 100644
index 0000000..cc4e956
--- /dev/null
+++ b/patches/server/0018-Pufferfish-Optimize-entity-coordinate-key.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 15:59:49 +0800
+Subject: [PATCH] Pufferfish Optimize entity coordinate key
+
+
+diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
+index 8f91b7f44baaf62b829a81afc0633311e6c13f19..a149d98be7b4744f18d4ed4940881e13a76e4a7c 100644
+--- a/src/main/java/io/papermc/paper/util/MCUtil.java
++++ b/src/main/java/io/papermc/paper/util/MCUtil.java
+@@ -213,7 +213,7 @@ public final class MCUtil {
+ }
+
+ public static long getCoordinateKey(final Entity entity) {
+- return ((long)(MCUtil.fastFloor(entity.getZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
++ return ((long)(entity.blockPosition.getZ() >> 4) << 32) | ((entity.blockPosition.getX() >> 4) & 0xFFFFFFFFL); // Pufferfish - eliminate double->long cast in hotpath
+ }
+
+ public static long getCoordinateKey(final ChunkPos pair) {
diff --git a/patches/server/0019-Pufferfish-Cache-climbing-check-for-activation.patch b/patches/server/0019-Pufferfish-Cache-climbing-check-for-activation.patch
new file mode 100644
index 0000000..3323031
--- /dev/null
+++ b/patches/server/0019-Pufferfish-Cache-climbing-check-for-activation.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:00:17 +0800
+Subject: [PATCH] Pufferfish Cache climbing check for activation
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index cb32a3851a315f1f1b4fb6d26fdffbcb471bfc06..3c9ec0f5fef49b0abc42382551de11ab942b3b20 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -308,7 +308,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ public double yo;
+ public double zo;
+ private Vec3 position;
+- private BlockPos blockPosition;
++ public BlockPos blockPosition; // Pufferfish - private->public
+ private ChunkPos chunkPosition;
+ private Vec3 deltaMovement;
+ private float yRot;
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 82996d0c2891871bd6ef9ff81aef123add08ebda..51dee7adc0dc534fac66bf3e8709865157dd763b 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -142,7 +142,6 @@ import org.bukkit.event.entity.EntityTeleportEvent;
+ import org.bukkit.event.player.PlayerItemConsumeEvent;
+ // CraftBukkit end
+
+-import co.aikar.timings.MinecraftTimings; // Paper
+
+ public abstract class LivingEntity extends Entity implements Attackable {
+
+@@ -2006,6 +2005,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ return this.lastClimbablePos;
+ }
+
++
++ // Pufferfish start
++ private boolean cachedOnClimable = false;
++ private BlockPos lastClimbingPosition = null;
++
++ public boolean onClimableCached() {
++ if (!this.blockPosition().equals(this.lastClimbingPosition)) {
++ this.cachedOnClimable = this.onClimbable();
++ this.lastClimbingPosition = this.blockPosition();
++ }
++ return this.cachedOnClimable;
++ }
++ // Pufferfish end
++
+ public boolean onClimbable() {
+ if (this.isSpectator()) {
+ return false;
+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
+index d88988200016c1a3cc76c017dfb7deabf6fc17af..22daed525b023998a05884db603e2c7385ce0873 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -305,7 +305,7 @@ public class ActivationRange
+ if ( entity instanceof LivingEntity )
+ {
+ LivingEntity living = (LivingEntity) entity;
+- if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper
++ if ( living.onClimableCached() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper // Pufferfish - use cached
+ {
+ return 1; // Paper
+ }
diff --git a/patches/server/0020-Pufferfish-Improve-fluid-direction-caching.patch b/patches/server/0020-Pufferfish-Improve-fluid-direction-caching.patch
new file mode 100644
index 0000000..80a7eac
--- /dev/null
+++ b/patches/server/0020-Pufferfish-Improve-fluid-direction-caching.patch
@@ -0,0 +1,237 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:00:59 +0800
+Subject: [PATCH] Pufferfish Improve fluid direction caching
+
+
+diff --git a/src/main/java/gg/airplane/structs/FluidDirectionCache.java b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..aa8467b9dda1f7707e41f50ac7b3e9d7343723ec
+--- /dev/null
++++ b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
+@@ -0,0 +1,136 @@
++package gg.airplane.structs;
++
++import it.unimi.dsi.fastutil.HashCommon;
++
++/**
++ * This is a replacement for the cache used in FluidTypeFlowing.
++ * The requirements for the previous cache were:
++ * - Store 200 entries
++ * - Look for the flag in the cache
++ * - If it exists, move to front of cache
++ * - If it doesn't exist, remove last entry in cache and insert in front
++ *
++ * This class accomplishes something similar, however has a few different
++ * requirements put into place to make this more optimize:
++ *
++ * - maxDistance is the most amount of entries to be checked, instead
++ * of having to check the entire list.
++ * - In combination with that, entries are all tracked by age and how
++ * frequently they're used. This enables us to remove old entries,
++ * without constantly shifting any around.
++ *
++ * Usage of the previous map would have to reset the head every single usage,
++ * shifting the entire map. Here, nothing happens except an increment when
++ * the cache is hit, and when it needs to replace an old element only a single
++ * element is modified.
++ */
++public class FluidDirectionCache {
++
++ private static class FluidDirectionEntry {
++ private final T data;
++ private final boolean flag;
++ private int uses = 0;
++ private int age = 0;
++
++ private FluidDirectionEntry(T data, boolean flag) {
++ this.data = data;
++ this.flag = flag;
++ }
++
++ public int getValue() {
++ return this.uses - (this.age >> 1); // age isn't as important as uses
++ }
++
++ public void incrementUses() {
++ this.uses = this.uses + 1 & Integer.MAX_VALUE;
++ }
++
++ public void incrementAge() {
++ this.age = this.age + 1 & Integer.MAX_VALUE;
++ }
++ }
++
++ private final FluidDirectionEntry[] entries;
++ private final int mask;
++ private final int maxDistance; // the most amount of entries to check for a value
++
++ public FluidDirectionCache(int size) {
++ int arraySize = HashCommon.nextPowerOfTwo(size);
++ this.entries = new FluidDirectionEntry[arraySize];
++ this.mask = arraySize - 1;
++ this.maxDistance = Math.min(arraySize, 4);
++ }
++
++ public Boolean getValue(T data) {
++ FluidDirectionEntry curr;
++ int pos;
++
++ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
++ return null;
++ } else if (data.equals(curr.data)) {
++ curr.incrementUses();
++ return curr.flag;
++ }
++
++ int checked = 1; // start at 1 because we already checked the first spot above
++
++ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
++ if (data.equals(curr.data)) {
++ curr.incrementUses();
++ return curr.flag;
++ } else if (++checked >= this.maxDistance) {
++ break;
++ }
++ }
++
++ return null;
++ }
++
++ public void putValue(T data, boolean flag) {
++ FluidDirectionEntry curr;
++ int pos;
++
++ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
++ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
++ return;
++ } else if (data.equals(curr.data)) {
++ curr.incrementUses();
++ return;
++ }
++
++ int checked = 1; // start at 1 because we already checked the first spot above
++
++ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
++ if (data.equals(curr.data)) {
++ curr.incrementUses();
++ return;
++ } else if (++checked >= this.maxDistance) {
++ this.forceAdd(data, flag);
++ return;
++ }
++ }
++
++ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
++ }
++
++ private void forceAdd(T data, boolean flag) {
++ int expectedPos = HashCommon.mix(data.hashCode()) & this.mask;
++
++ int toRemovePos = expectedPos;
++ FluidDirectionEntry entryToRemove = this.entries[toRemovePos];
++
++ for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) {
++ int pos = i & this.mask;
++ FluidDirectionEntry entry = this.entries[pos];
++ if (entry.getValue() < entryToRemove.getValue()) {
++ toRemovePos = pos;
++ entryToRemove = entry;
++ }
++
++ entry.incrementAge(); // use this as a mechanism to age the other entries
++ }
++
++ // remove the least used/oldest entry
++ this.entries[toRemovePos] = new FluidDirectionEntry(data, flag);
++ }
++}
+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..c55f51e6db55f9fa66f53eef0e7a56af5f81d742 100644
+--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+@@ -45,6 +45,8 @@ public abstract class FlowingFluid extends Fluid {
+ public static final BooleanProperty FALLING = BlockStateProperties.FALLING;
+ public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_FLOWING;
+ private static final int CACHE_SIZE = 200;
++ // Pufferfish start - use our own cache
++ /*
+ private static final ThreadLocal> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> {
+ Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap(200) {
+ protected void rehash(int i) {}
+@@ -53,6 +55,14 @@ public abstract class FlowingFluid extends Fluid {
+ object2bytelinkedopenhashmap.defaultReturnValue((byte) 127);
+ return object2bytelinkedopenhashmap;
+ });
++ */
++
++ private static final ThreadLocal> localFluidDirectionCache = ThreadLocal.withInitial(() -> {
++ // Pufferfish todo - mess with this number for performance
++ // with 2048 it seems very infrequent on a small world that it has to remove old entries
++ return new gg.airplane.structs.FluidDirectionCache<>(2048);
++ });
++ // Pufferfish end
+ private final Map shapes = Maps.newIdentityHashMap();
+
+ public FlowingFluid() {}
+@@ -252,6 +262,8 @@ public abstract class FlowingFluid extends Fluid {
+ return false;
+ }
+ // Paper end - optimise collisions
++ // Pufferfish start - modify to use our cache
++ /*
+ Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
+
+ if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
+@@ -259,9 +271,16 @@ public abstract class FlowingFluid extends Fluid {
+ } else {
+ object2bytelinkedopenhashmap = null;
+ }
++ */
++ gg.airplane.structs.FluidDirectionCache cache = null;
++
++ if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
++ cache = localFluidDirectionCache.get();
++ }
+
+ Block.BlockStatePairKey block_a;
+
++ /*
+ if (object2bytelinkedopenhashmap != null) {
+ block_a = new Block.BlockStatePairKey(state, fromState, face);
+ byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a);
+@@ -272,11 +291,22 @@ public abstract class FlowingFluid extends Fluid {
+ } else {
+ block_a = null;
+ }
++ */
++ if (cache != null) {
++ block_a = new Block.BlockStatePairKey(state, fromState, face);
++ Boolean flag = cache.getValue(block_a);
++ if (flag != null) {
++ return flag;
++ }
++ } else {
++ block_a = null;
++ }
+
+ VoxelShape voxelshape = state.getCollisionShape(world, pos);
+ VoxelShape voxelshape1 = fromState.getCollisionShape(world, fromPos);
+ boolean flag = !Shapes.mergedFaceOccludes(voxelshape, voxelshape1, face);
+
++ /*
+ if (object2bytelinkedopenhashmap != null) {
+ if (object2bytelinkedopenhashmap.size() == 200) {
+ object2bytelinkedopenhashmap.removeLastByte();
+@@ -284,6 +314,11 @@ public abstract class FlowingFluid extends Fluid {
+
+ object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0));
+ }
++ */
++ if (cache != null) {
++ cache.putValue(block_a, flag);
++ }
++ // Pufferfish end
+
+ return flag;
+ }
diff --git a/patches/server/0021-Pufferfish-Optimize-suffocation.patch b/patches/server/0021-Pufferfish-Optimize-suffocation.patch
new file mode 100644
index 0000000..5930c45
--- /dev/null
+++ b/patches/server/0021-Pufferfish-Optimize-suffocation.patch
@@ -0,0 +1,86 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:05:32 +0800
+Subject: [PATCH] Pufferfish Optimize suffocation
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 0929a5a167691bde7dedaa1e2812b34ad69913d6..de0855656ad3882b182aa5674fd0117288268e71 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -42,6 +42,7 @@ public class LuminolConfig {
+ public static int linearFlushThreads = 1;
+
+ public static boolean reduceSensorWork = true;
++ public static boolean enableSuffocationOptimization = true;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -104,6 +105,7 @@ public class LuminolConfig {
+ linearFlushThreads = Math.max(linearFlushThreads, 1);
+
+ reduceSensorWork = get("optimizations.reduce_sensor_work",reduceSensorWork,"This optimization is from petal.You can find out more about it on petal's repository");
++ enableSuffocationOptimization = get("optimizations.optimize_suffocation_check",enableSuffocationOptimization);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 51dee7adc0dc534fac66bf3e8709865157dd763b..f13d26b280f095d006ffccb36af66bb7487cb8da 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -19,6 +19,8 @@ import java.util.Optional;
+ import java.util.UUID;
+ import java.util.function.Predicate;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.BlockUtil;
+ import net.minecraft.advancements.CriteriaTriggers;
+ import net.minecraft.commands.arguments.EntityAnchorArgument;
+@@ -420,7 +422,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ boolean flag = this instanceof net.minecraft.world.entity.player.Player;
+
+ if (!this.level().isClientSide) {
+- if (this.isInWall()) {
++ if (shouldCheckForSuffocation() && this.isInWall()) { // Pufferfish - optimize suffocation
+ this.hurt(this.damageSources().inWall(), 1.0F);
+ } else if (flag && !this.level().getWorldBorder().isWithinBounds(this.getBoundingBox())) {
+ double d0 = this.level().getWorldBorder().getDistanceToBorder(this) + this.level().getWorldBorder().getDamageSafeZone();
+@@ -1410,6 +1412,19 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ return this.getHealth() <= 0.0F;
+ }
+
++ // Pufferfish start - optimize suffocation
++ public boolean couldPossiblyBeHurt(float amount) {
++ if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && amount <= this.lastHurt) {
++ return false;
++ }
++ return true;
++ }
++
++ public boolean shouldCheckForSuffocation() {
++ return !LuminolConfig.enableSuffocationOptimization || (tickCount % 10 == 0 && couldPossiblyBeHurt(1.0F));
++ }
++ // Pufferfish end
++
+ @Override
+ public boolean hurt(DamageSource source, float amount) {
+ if (this.isInvulnerableTo(source)) {
+diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+index 1e07febcf7a3dfb281728cc5e3e4f15dd776d7e0..c65ab566c6241dd6a44bd11a449ef0c4b2f6dc65 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -150,6 +150,13 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
+ this.bossEvent.setName(this.getDisplayName());
+ }
+
++ // Pufferfish start - optimize suffocation
++ @Override
++ public boolean shouldCheckForSuffocation() {
++ return true;
++ }
++ // Pufferfish end
++
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return SoundEvents.WITHER_AMBIENT;
diff --git a/patches/server/0022-Pufferfish-Early-return-optimization-for-target-find.patch b/patches/server/0022-Pufferfish-Early-return-optimization-for-target-find.patch
new file mode 100644
index 0000000..6f4787a
--- /dev/null
+++ b/patches/server/0022-Pufferfish-Early-return-optimization-for-target-find.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:07:30 +0800
+Subject: [PATCH] Pufferfish Early return optimization for target finding
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+index c157309ac78e7af084d3acb6e8b2bcd469a39d5e..ac5e5676b194a2a99e5cf53eb89c1152cac963b8 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
++++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+@@ -75,9 +75,18 @@ public class TargetingConditions {
+ }
+
+ if (this.range > 0.0D) {
+- double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
+- double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper
++ // Pufferfish start - check range before getting visibility
++ // d = invisibility percent, e = follow range adjusted for invisibility, f = distance
+ double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ());
++ double followRangeRaw = this.useFollowRange ? this.getFollowRange(baseEntity) : this.range;
++
++ if (f > followRangeRaw * followRangeRaw) { // the actual follow range will always be this value or smaller, so if the distance is larger then it never will return true after getting invis
++ return false;
++ }
++
++ double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
++ double e = Math.max((followRangeRaw) * d, 2.0D); // Paper
++ // Pufferfish end
+ if (f > e * e) {
+ return false;
+ }
diff --git a/patches/server/0023-Pufferfish-Reduce-chunk-loading-lookups.patch b/patches/server/0023-Pufferfish-Reduce-chunk-loading-lookups.patch
new file mode 100644
index 0000000..47a59c0
--- /dev/null
+++ b/patches/server/0023-Pufferfish-Reduce-chunk-loading-lookups.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:07:56 +0800
+Subject: [PATCH] Pufferfish Reduce chunk loading & lookups
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+index bd5996eef2d946e9d7765b6b315bc5951158810e..0d51f435f18f3f9d59a3241a0b7fa1c4af841b72 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+@@ -333,11 +333,17 @@ public class EnderMan extends Monster implements NeutralMob {
+ private boolean teleport(double x, double y, double z) {
+ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(x, y, z);
+
+- while (blockposition_mutableblockposition.getY() > this.level().getMinBuildHeight() && !this.level().getBlockState(blockposition_mutableblockposition).blocksMotion()) {
++ // Pufferfish start - single chunk lookup
++ net.minecraft.world.level.chunk.LevelChunk chunk = this.level().getChunkIfLoaded(blockposition_mutableblockposition);
++ if (chunk == null) {
++ return false;
++ }
++ // Pufferfish end
++ while (blockposition_mutableblockposition.getY() > this.level().getMinBuildHeight() && !chunk.getBlockState(blockposition_mutableblockposition).blocksMotion()) { // Pufferfish
+ blockposition_mutableblockposition.move(Direction.DOWN);
+ }
+
+- BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition);
++ BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Pufferfish
+ boolean flag = iblockdata.blocksMotion();
+ boolean flag1 = iblockdata.getFluidState().is(FluidTags.WATER);
+
diff --git a/patches/server/0024-Pufferfish-Improve-container-checking-with-a-bitset.patch b/patches/server/0024-Pufferfish-Improve-container-checking-with-a-bitset.patch
new file mode 100644
index 0000000..4b33cf5
--- /dev/null
+++ b/patches/server/0024-Pufferfish-Improve-container-checking-with-a-bitset.patch
@@ -0,0 +1,478 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:08:46 +0800
+Subject: [PATCH] Pufferfish Improve container checking with a bitset
+
+
+diff --git a/src/main/java/gg/airplane/structs/ItemListWithBitset.java b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1b7a4ee47f4445d7f2ac91d3a73ae113edbdddb2
+--- /dev/null
++++ b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
+@@ -0,0 +1,114 @@
++package gg.airplane.structs;
++
++import net.minecraft.core.NonNullList;
++import net.minecraft.world.item.ItemStack;
++import org.apache.commons.lang.Validate;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++import java.util.AbstractList;
++import java.util.Arrays;
++import java.util.List;
++
++public class ItemListWithBitset extends AbstractList {
++ public static ItemListWithBitset fromList(List list) {
++ if (list instanceof ItemListWithBitset ours) {
++ return ours;
++ }
++ return new ItemListWithBitset(list);
++ }
++
++ private static ItemStack[] createArray(int size) {
++ ItemStack[] array = new ItemStack[size];
++ Arrays.fill(array, ItemStack.EMPTY);
++ return array;
++ }
++
++ private final ItemStack[] items;
++
++ private long bitSet = 0;
++ private final long allBits;
++
++ private static class OurNonNullList extends NonNullList {
++ protected OurNonNullList(List delegate) {
++ super(delegate, ItemStack.EMPTY);
++ }
++ }
++
++ public final NonNullList nonNullList = new OurNonNullList(this);
++
++ private ItemListWithBitset(List list) {
++ this(list.size());
++
++ for (int i = 0; i < list.size(); i++) {
++ this.set(i, list.get(i));
++ }
++ }
++
++ public ItemListWithBitset(int size) {
++ Validate.isTrue(size < Long.BYTES * 8, "size is too large");
++
++ this.items = createArray(size);
++ this.allBits = ((1L << size) - 1);
++ }
++
++ public boolean isCompletelyEmpty() {
++ return this.bitSet == 0;
++ }
++
++ public boolean hasFullStacks() {
++ return (this.bitSet & this.allBits) == allBits;
++ }
++
++ @Override
++ public ItemStack set(int index, @NotNull ItemStack itemStack) {
++ ItemStack existing = this.items[index];
++
++ this.items[index] = itemStack;
++
++ if (itemStack == ItemStack.EMPTY) {
++ this.bitSet &= ~(1L << index);
++ } else {
++ this.bitSet |= 1L << index;
++ }
++
++ return existing;
++ }
++
++ @NotNull
++ @Override
++ public ItemStack get(int var0) {
++ return this.items[var0];
++ }
++
++ @Override
++ public int size() {
++ return this.items.length;
++ }
++
++ @Override
++ public void clear() {
++ Arrays.fill(this.items, ItemStack.EMPTY);
++ }
++
++ // these are unsupported for block inventories which have a static size
++ @Override
++ public void add(int var0, ItemStack var1) {
++ throw new UnsupportedOperationException();
++ }
++
++ @Override
++ public ItemStack remove(int var0) {
++ throw new UnsupportedOperationException();
++ }
++
++ @Override
++ public String toString() {
++ return "ItemListWithBitset{" +
++ "items=" + Arrays.toString(items) +
++ ", bitSet=" + Long.toString(bitSet, 2) +
++ ", allBits=" + Long.toString(allBits, 2) +
++ ", size=" + this.items.length +
++ '}';
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/CompoundContainer.java b/src/main/java/net/minecraft/world/CompoundContainer.java
+index 241fec02e6869c638d3a160819b32173a081467b..6a8f9e8f5bf108674c47018def28906e2d0a729c 100644
+--- a/src/main/java/net/minecraft/world/CompoundContainer.java
++++ b/src/main/java/net/minecraft/world/CompoundContainer.java
+@@ -1,5 +1,6 @@
+ package net.minecraft.world;
+
++import net.minecraft.core.Direction; // Pufferfish
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+
+@@ -64,6 +65,23 @@ public class CompoundContainer implements Container {
+ this.container2 = second;
+ }
+
++ // Pufferfish start
++ @Override
++ public boolean hasEmptySlot(Direction enumdirection) {
++ return this.container1.hasEmptySlot(null) || this.container2.hasEmptySlot(null);
++ }
++
++ @Override
++ public boolean isCompletelyFull(Direction enumdirection) {
++ return this.container1.isCompletelyFull(null) && this.container2.isCompletelyFull(null);
++ }
++
++ @Override
++ public boolean isCompletelyEmpty(Direction enumdirection) {
++ return this.container1.isCompletelyEmpty(null) && this.container2.isCompletelyEmpty(null);
++ }
++ // Pufferfish end
++
+ @Override
+ public int getContainerSize() {
+ return this.container1.getContainerSize() + this.container2.getContainerSize();
+diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java
+index d6cbe98e67fdbf8db46338a88ab1356dd63b50a3..20dd3a63b2f955b05a75eb240e33ae4cf6aef28f 100644
+--- a/src/main/java/net/minecraft/world/Container.java
++++ b/src/main/java/net/minecraft/world/Container.java
+@@ -3,6 +3,8 @@ package net.minecraft.world;
+ import java.util.Set;
+ import java.util.function.Predicate;
+ import net.minecraft.core.BlockPos;
++
++import net.minecraft.core.Direction; // Pufferfish
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
+@@ -14,6 +16,63 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+ // CraftBukkit end
+
+ public interface Container extends Clearable {
++ // Pufferfish start - allow the inventory to override and optimize these frequent calls
++ default boolean hasEmptySlot(@org.jetbrains.annotations.Nullable Direction enumdirection) { // there is a slot with 0 items in it
++ if (this instanceof WorldlyContainer worldlyContainer) {
++ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
++ if (this.getItem(i).isEmpty()) {
++ return true;
++ }
++ }
++ } else {
++ int size = this.getContainerSize();
++ for (int i = 0; i < size; i++) {
++ if (this.getItem(i).isEmpty()) {
++ return true;
++ }
++ }
++ }
++ return false;
++ }
++
++ default boolean isCompletelyFull(@org.jetbrains.annotations.Nullable Direction enumdirection) { // every stack is maxed
++ if (this instanceof WorldlyContainer worldlyContainer) {
++ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
++ ItemStack itemStack = this.getItem(i);
++ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
++ return false;
++ }
++ }
++ } else {
++ int size = this.getContainerSize();
++ for (int i = 0; i < size; i++) {
++ ItemStack itemStack = this.getItem(i);
++ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
++ return false;
++ }
++ }
++ }
++ return true;
++ }
++
++ default boolean isCompletelyEmpty(@org.jetbrains.annotations.Nullable Direction enumdirection) {
++ if (this instanceof WorldlyContainer worldlyContainer) {
++ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
++ if (!this.getItem(i).isEmpty()) {
++ return false;
++ }
++ }
++ } else {
++ int size = this.getContainerSize();
++ for (int i = 0; i < size; i++) {
++ if (!this.getItem(i).isEmpty()) {
++ return false;
++ }
++ }
++ }
++ return true;
++ }
++ // Pufferfish end
+
+ int LARGE_MAX_STACK_SIZE = 64;
+ int DEFAULT_DISTANCE_LIMIT = 8;
+diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+index 00187fbbeddfc17e1b6887f8bf0f50da23938470..f64edfdb03f99624daf1e05b5dc86d845c3018b6 100644
+--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+@@ -27,7 +27,10 @@ import org.bukkit.inventory.InventoryHolder;
+
+ public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
+
++ // Pufferfish start
+ private NonNullList itemStacks;
++ private gg.airplane.structs.ItemListWithBitset itemStacksOptimized;
++ // Pufferfish end
+ @Nullable
+ public ResourceLocation lootTable;
+ public long lootTableSeed;
+@@ -89,12 +92,18 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
+
+ protected AbstractMinecartContainer(EntityType> type, Level world) {
+ super(type, world);
+- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
++ // Pufferfish start
++ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
++ this.itemStacks = this.itemStacksOptimized.nonNullList;
++ // Pufferfish end
+ }
+
+ protected AbstractMinecartContainer(EntityType> type, double x, double y, double z, Level world) {
+ super(type, world, x, y, z);
+- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
++ // Pufferfish start
++ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
++ this.itemStacks = this.itemStacksOptimized.nonNullList;
++ // Pufferfish end
+ }
+
+ @Override
+@@ -156,6 +165,10 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
+ protected void readAdditionalSaveData(CompoundTag nbt) {
+ super.readAdditionalSaveData(nbt);
+ this.lootableData.loadNbt(nbt); // Paper
++ // Pufferfish start
++ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
++ this.itemStacks = this.itemStacksOptimized.nonNullList;
++ // Pufferfish end
+ this.readChestVehicleSaveData(nbt);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+index a71414397bd45ee7bcacfeef0041d80dfa25f114..d66806565770cb03a21794f99e5c4b0f3040b26a 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+@@ -31,7 +31,10 @@ import org.bukkit.entity.HumanEntity;
+ public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity {
+
+ private static final int EVENT_SET_OPEN_COUNT = 1;
++ // Pufferfish start
+ private NonNullList items;
++ private gg.airplane.structs.ItemListWithBitset optimizedItems;
++ // Pufferfish end
+ public final ContainerOpenersCounter openersCounter;
+ private final ChestLidController chestLidController;
+
+@@ -65,9 +68,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
+ }
+ // CraftBukkit end
+
++ private final boolean isNative = getClass().equals(ChestBlockEntity.class); // Pufferfish
+ protected ChestBlockEntity(BlockEntityType> type, BlockPos pos, BlockState state) {
+ super(type, pos, state);
+- this.items = NonNullList.withSize(27, ItemStack.EMPTY);
++ // Pufferfish start
++ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(27);
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ this.openersCounter = new ContainerOpenersCounter() {
+ @Override
+ protected void onOpen(Level world, BlockPos pos, BlockState state) {
+@@ -98,6 +105,23 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
+ this.chestLidController = new ChestLidController();
+ }
+
++ // Pufferfish start
++ @Override
++ public boolean hasEmptySlot(Direction enumdirection) {
++ return isNative ? !this.optimizedItems.hasFullStacks() : super.hasEmptySlot(enumdirection);
++ }
++
++ @Override
++ public boolean isCompletelyFull(Direction enumdirection) {
++ return isNative ? this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection) : super.isCompletelyFull(enumdirection);
++ }
++
++ @Override
++ public boolean isCompletelyEmpty(Direction enumdirection) {
++ return isNative && this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
++ }
++ // Pufferfish end
++
+ public ChestBlockEntity(BlockPos pos, BlockState state) {
+ this(BlockEntityType.CHEST, pos, state);
+ }
+@@ -115,7 +139,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
+ @Override
+ public void load(CompoundTag nbt) {
+ super.load(nbt);
+- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
++ // Pufferfish start
++ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ if (!this.tryLoadLootTable(nbt)) {
+ ContainerHelper.loadAllItems(nbt, this.items);
+ }
+@@ -187,7 +214,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
+
+ @Override
+ protected void setItems(NonNullList list) {
+- this.items = list;
++ // Pufferfish start
++ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+index 72779080969fe9f058c19533ffc9dad5f9c47086..c90d578643490709936545ee9cbd41c8671eeb7a 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -47,7 +47,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+
+ public static final int MOVE_ITEM_SPEED = 8;
+ public static final int HOPPER_CONTAINER_SIZE = 5;
++ // Pufferfish start
+ private NonNullList items;
++ private gg.airplane.structs.ItemListWithBitset optimizedItems; // Pufferfish
++ // Pufferfish end
+ private int cooldownTime;
+ private long tickedGameTime;
+
+@@ -83,14 +86,37 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+
+ public HopperBlockEntity(BlockPos pos, BlockState state) {
+ super(BlockEntityType.HOPPER, pos, state);
+- this.items = NonNullList.withSize(5, ItemStack.EMPTY);
++ // Pufferfish start
++ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(5);
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ this.cooldownTime = -1;
+ }
+
++ // Pufferfish start
++ @Override
++ public boolean hasEmptySlot(Direction enumdirection) {
++ return !this.optimizedItems.hasFullStacks();
++ }
++
++ @Override
++ public boolean isCompletelyFull(Direction enumdirection) {
++ return this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection);
++ }
++
++ @Override
++ public boolean isCompletelyEmpty(Direction enumdirection) {
++ return this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
++ }
++ // Pufferfish end
++
+ @Override
+ public void load(CompoundTag nbt) {
+ super.load(nbt);
+- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
++ // Pufferfish start
++ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ if (!this.tryLoadLootTable(nbt)) {
+ ContainerHelper.loadAllItems(nbt, this.items);
+ }
+@@ -493,6 +519,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+ }
+
+ private static boolean isFullContainer(Container inventory, Direction direction) {
++ if (true) return inventory.isCompletelyFull(direction); // Pufferfish - use bitsets
+ // Paper start - optimize hoppers
+ if (inventory instanceof WorldlyContainer worldlyContainer) {
+ for (final int slot : worldlyContainer.getSlotsForFace(direction)) {
+@@ -515,7 +542,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+ }
+
+ private static boolean isEmptyContainer(Container inv, Direction facing) {
+- return allMatch(inv, facing, IS_EMPTY_TEST);
++ // Paper start
++ // Pufferfish start - use bitsets
++ //return allMatch(inv, facing, IS_EMPTY_TEST);
++ return inv.isCompletelyEmpty(facing);
++ // Pufferfish end
+ }
+
+ public static boolean suckInItems(Level world, Hopper hopper) {
+@@ -716,7 +747,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+
+ if (HopperBlockEntity.canPlaceItemInContainer(to, stack, slot, side)) {
+ boolean flag = false;
+- boolean flag1 = to.isEmpty();
++ boolean flag1 = to.isCompletelyEmpty(side); // Pufferfish
+
+ if (itemstack1.isEmpty()) {
+ // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
+@@ -911,7 +942,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+
+ @Override
+ protected void setItems(NonNullList list) {
+- this.items = list;
++ // Pufferfish start
++ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
++ this.items = this.optimizedItems.nonNullList;
++ // Pufferfish end
+ }
+
+ public static void entityInside(Level world, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) {
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+index 3e638f12956e57548f76c7e2403ba370f7baa249..02364a148b347e3669275553004391e31d77c0b5 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+@@ -96,12 +96,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
+ public boolean isEmpty() {
+ this.unpackLootTable((Player)null);
+ // Paper start
+- for (final ItemStack itemStack : this.getItems()) {
+- if (!itemStack.isEmpty()) {
+- return false;
+- }
+- }
+- return true;
++ return this.isCompletelyEmpty(null); // Pufferfish - use super
+ // Paper end
+ }
+
diff --git a/patches/server/0025-Pufferfish-Fix-Paper-6045-block-goal-shouldn-t-load-.patch b/patches/server/0025-Pufferfish-Fix-Paper-6045-block-goal-shouldn-t-load-.patch
new file mode 100644
index 0000000..a99cc9f
--- /dev/null
+++ b/patches/server/0025-Pufferfish-Fix-Paper-6045-block-goal-shouldn-t-load-.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:09:27 +0800
+Subject: [PATCH] Pufferfish Fix Paper#6045, block goal shouldn't load chunks
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+index 34f319ad09276c6f68dde449c79351de0d7d86f5..a719af0b512d9ef243d0d54f3b744b1b1a5f2772 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
++++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+@@ -119,6 +119,7 @@ public abstract class MoveToBlockGoal extends Goal {
+ for(int m = 0; m <= l; m = m > 0 ? -m : 1 - m) {
+ for(int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) {
+ mutableBlockPos.setWithOffset(blockPos, m, k - 1, n);
++ if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Pufferfish - if this block isn't loaded, continue
+ if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) {
+ this.blockPos = mutableBlockPos;
+ setTargetPosition(mutableBlockPos.immutable()); // Paper
diff --git a/patches/server/0026-Pufferfish-Reduce-entity-fluid-lookups-if-no-fluids.patch b/patches/server/0026-Pufferfish-Reduce-entity-fluid-lookups-if-no-fluids.patch
new file mode 100644
index 0000000..7e96f61
--- /dev/null
+++ b/patches/server/0026-Pufferfish-Reduce-entity-fluid-lookups-if-no-fluids.patch
@@ -0,0 +1,153 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:10:26 +0800
+Subject: [PATCH] Pufferfish Reduce entity fluid lookups if no fluids
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 3c9ec0f5fef49b0abc42382551de11ab942b3b20..967c7a953084dc68a0ecd4b1a0f13ead7e72cb3d 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -5132,16 +5132,18 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ }
+
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) {
+- if (this.touchingUnloadedChunk()) {
++ if (false && this.touchingUnloadedChunk()) { // Pufferfish - cost of a lookup here is the same cost as below, so skip
+ return false;
+ } else {
+ AABB axisalignedbb = this.getBoundingBox().deflate(0.001D);
+- int i = Mth.floor(axisalignedbb.minX);
+- int j = Mth.ceil(axisalignedbb.maxX);
+- int k = Mth.floor(axisalignedbb.minY);
+- int l = Mth.ceil(axisalignedbb.maxY);
+- int i1 = Mth.floor(axisalignedbb.minZ);
+- int j1 = Mth.ceil(axisalignedbb.maxZ);
++ // Pufferfish start - rename
++ int minBlockX = Mth.floor(axisalignedbb.minX);
++ int maxBlockX = Mth.ceil(axisalignedbb.maxX);
++ int minBlockY = Mth.floor(axisalignedbb.minY);
++ int maxBlockY = Mth.ceil(axisalignedbb.maxY);
++ int minBlockZ = Mth.floor(axisalignedbb.minZ);
++ int maxBlockZ = Mth.ceil(axisalignedbb.maxZ);
++ // Pufferfish end
+ double d1 = 0.0D;
+ boolean flag = this.isPushedByFluid();
+ boolean flag1 = false;
+@@ -5149,14 +5151,61 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ int k1 = 0;
+ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+
+- for (int l1 = i; l1 < j; ++l1) {
+- for (int i2 = k; i2 < l; ++i2) {
+- for (int j2 = i1; j2 < j1; ++j2) {
+- blockposition_mutableblockposition.set(l1, i2, j2);
+- FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
++ // Pufferfish start - based off CollisionUtil.getCollisionsForBlocksOrWorldBorder
++ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this.level());
++ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(this.level());
++ final int minBlock = minSection << 4;
++ final int maxBlock = (maxSection << 4) | 15;
++
++ // special cases:
++ if (minBlockY > maxBlock || maxBlockY < minBlock) {
++ // no point in checking
++ return false;
++ }
++
++ int minYIterate = Math.max(minBlock, minBlockY);
++ int maxYIterate = Math.min(maxBlock, maxBlockY);
++
++ int minChunkX = minBlockX >> 4;
++ int maxChunkX = maxBlockX >> 4;
++
++ int minChunkZ = minBlockZ >> 4;
++ int maxChunkZ = maxBlockZ >> 4;
++
++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
++ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
++ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 16; // coordinate in chunk
++
++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
++ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
++ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 16; // coordinate in chunk
++
++ net.minecraft.world.level.chunk.ChunkAccess chunk = this.level().getChunkIfLoadedImmediately(currChunkX, currChunkZ);
++ if (chunk == null) {
++ return false; // if we're touching an unloaded chunk then it's false
++ }
++
++ net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
++
++ for (int currY = minYIterate; currY < maxYIterate; ++currY) {
++ net.minecraft.world.level.chunk.LevelChunkSection section = sections[(currY >> 4) - minSection];
++
++ if (section == null || section.hasOnlyAir() || section.fluidStateCount == 0) { // if no fluids, nothing in this section
++ // empty
++ // skip to next section
++ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
++ continue;
++ }
++
++ net.minecraft.world.level.chunk.PalettedContainer blocks = section.states;
++
++ for (int currZ = minZ; currZ < maxZ; ++currZ) {
++ for (int currX = minX; currX < maxX; ++currX) {
++ FluidState fluid = blocks.get(currX & 15, currY & 15, currZ & 15).getFluidState();
+
+ if (fluid.is(tag)) {
+- double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition));
++ blockposition_mutableblockposition.set((currChunkX << 4) + currX, currY, (currChunkZ << 4) + currZ);
++ double d2 = (double) ((float) currY + fluid.getHeight(this.level(), blockposition_mutableblockposition));
+
+ if (d2 >= axisalignedbb.minY) {
+ flag1 = true;
+@@ -5178,9 +5227,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ // CraftBukkit end
+ }
+ }
++ }
++ }
+ }
+ }
+ }
++ // Pufferfish end
+
+ if (vec3d.length() > 0.0D) {
+ if (k1 > 0) {
+diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+index f0de72afad4bb571153436399386a6a8a70582a6..45b7527341fcb6d24f35318cedb522646b5ee1c2 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -25,6 +25,7 @@ public class LevelChunkSection {
+ public final PalettedContainer states;
+ // CraftBukkit start - read/write
+ private PalettedContainer> biomes;
++ public short fluidStateCount; // Pufferfish
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
+ // Paper start - optimise collisions
+ private int specialCollidingBlocks;
+@@ -102,6 +103,7 @@ public class LevelChunkSection {
+
+ if (!fluid.isEmpty()) {
+ --this.tickingFluidCount;
++ --this.fluidStateCount; // Pufferfish
+ }
+
+ if (!state.isAir()) {
+@@ -116,6 +118,7 @@ public class LevelChunkSection {
+
+ if (!fluid1.isEmpty()) {
+ ++this.tickingFluidCount;
++ ++this.fluidStateCount; // Pufferfish
+ }
+
+ this.updateBlockCallback(x, y, z, iblockdata1, state); // Paper - optimise collisions
+@@ -161,6 +164,7 @@ public class LevelChunkSection {
+ if (fluid.isRandomlyTicking()) {
+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
+ }
++ this.fluidStateCount++; // Pufferfish
+ }
+
+ // Paper start - optimise collisions
diff --git a/patches/server/0027-Pufferfish-Only-check-for-spooky-season-once-an-hour.patch b/patches/server/0027-Pufferfish-Only-check-for-spooky-season-once-an-hour.patch
new file mode 100644
index 0000000..1e35d6c
--- /dev/null
+++ b/patches/server/0027-Pufferfish-Only-check-for-spooky-season-once-an-hour.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:15:28 +0800
+Subject: [PATCH] Pufferfish Only check for spooky season once an hour
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
+index 5beaa849a250ea005733250ad3edfa8382224667..2028ae6cc50d86c579ec062536e8c6339196ce09 100644
+--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
++++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
+@@ -3,6 +3,10 @@ package net.minecraft.world.entity.ambient;
+ import java.time.LocalDate;
+ import java.time.temporal.ChronoField;
+ import javax.annotation.Nullable;
++
++import io.papermc.paper.threadedregions.ThreadedRegionizer;
++import io.papermc.paper.threadedregions.TickRegionScheduler;
++import io.papermc.paper.threadedregions.TickRegions;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.syncher.EntityDataAccessor;
+@@ -237,13 +241,25 @@ public class Bat extends AmbientCreature {
+ }
+ }
+
++ // Pufferfish start - only check for spooky season once an hour
++ private static boolean isSpookySeason = false;
++ private static final int ONE_HOUR = 20 * 60 * 60;
++ private static int lastSpookyCheck = -ONE_HOUR;
+ private static boolean isHalloween() {
++ final ThreadedRegionizer.ThreadedRegion region =
++ TickRegionScheduler.getCurrentRegion();
++ final long tickCount = region.getData().getCurrentTick();
++ if (tickCount - lastSpookyCheck > ONE_HOUR) {
+ LocalDate localdate = LocalDate.now();
+ int i = localdate.get(ChronoField.DAY_OF_MONTH);
+ int j = localdate.get(ChronoField.MONTH_OF_YEAR);
+
+- return j == 10 && i >= 20 || j == 11 && i <= 3;
++ isSpookySeason = j == 10 && i >= 20 || j == 11 && i <= 3;
++ lastSpookyCheck = (int) tickCount;
++ }
++ return isSpookySeason;
+ }
++ // Pufferfish end
+
+ @Override
+ protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
diff --git a/patches/server/0028-Pufferfish-Entity-TTL.patch b/patches/server/0028-Pufferfish-Entity-TTL.patch
new file mode 100644
index 0000000..361ec7d
--- /dev/null
+++ b/patches/server/0028-Pufferfish-Entity-TTL.patch
@@ -0,0 +1,87 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:30:24 +0800
+Subject: [PATCH] Pufferfish Entity TTL
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index de0855656ad3882b182aa5674fd0117288268e71..d0c0b4daec59f23a989a8b8f66ea3c704b0e309c 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -4,13 +4,16 @@ import dev.kaiijumc.kaiiju.region.RegionFileFormat;
+ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+ import me.earthme.luminol.commands.TpsBarCommand;
+ import me.earthme.luminol.functions.GlobalServerTpsBar;
++import net.minecraft.core.registries.BuiltInRegistries;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.world.entity.EntityType;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+ import org.bukkit.Bukkit;
+
+ import java.util.Arrays;
+ import java.util.List;
++import java.util.Locale;
+ import java.util.logging.Level;
+ import java.io.File;
+ import java.io.IOException;
+@@ -65,6 +68,22 @@ public class LuminolConfig {
+ }
+ }
+
++ private static void initEntityTTL() {
++ // Set some defaults
++ get("optimizations.entity_timeouts.SNOWBALL", -1);
++ get("optimizations.entity_timeouts.LLAMA_SPIT", -1);
++ MAIN_CONFIG.setComment("optimizations.entity_timeouts",
++ """
++ These values define a entity's maximum lifespan. If an
++ entity is in this list and it has survived for longer than
++ that number of ticks, then it will be removed. Setting a value to
++ -1 disables this feature.""");
++ for (EntityType> entityType : BuiltInRegistries.ENTITY_TYPE) {
++ String type = EntityType.getKey(entityType).getPath().toUpperCase(Locale.ROOT);
++ entityType.ttl = get("optimizations.entity_timeouts." + type, -1);
++ }
++ }
++
+ public static void initValues(){
+ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
+@@ -106,6 +125,7 @@ public class LuminolConfig {
+
+ reduceSensorWork = get("optimizations.reduce_sensor_work",reduceSensorWork,"This optimization is from petal.You can find out more about it on petal's repository");
+ enableSuffocationOptimization = get("optimizations.optimize_suffocation_check",enableSuffocationOptimization);
++ initEntityTTL();
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 967c7a953084dc68a0ecd4b1a0f13ead7e72cb3d..8bdaab46c2e128aa58d13101170ce358146377a8 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -798,6 +798,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ }
+
+ public void tick() {
++ // Pufferfish start - entity TTL
++ if (type != EntityType.PLAYER && type.ttl >= 0 && this.tickCount >= type.ttl) {
++ discard();
++ return;
++ }
++ // Pufferfish end - entity TTL
+ this.baseTick();
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
+index 3f3494c20cd15a721090f1b36293562a6b834b14..4d60ac50a1d3860f2a2e9265aef9507d790220a3 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityType.java
++++ b/src/main/java/net/minecraft/world/entity/EntityType.java
+@@ -309,6 +309,7 @@ public class EntityType implements FeatureElement, EntityTypeT
+ private ResourceLocation lootTable;
+ private final EntityDimensions dimensions;
+ private final FeatureFlagSet requiredFeatures;
++ public int ttl = -1; // Pufferfish
+
+ private static EntityType register(String id, EntityType.Builder type) { // CraftBukkit - decompile error
+ return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType) type.build(id)); // CraftBukkit - decompile error
diff --git a/patches/server/0029-Pufferfish-Reduce-projectile-chunk-loading.patch b/patches/server/0029-Pufferfish-Reduce-projectile-chunk-loading.patch
new file mode 100644
index 0000000..e6c5193
--- /dev/null
+++ b/patches/server/0029-Pufferfish-Reduce-projectile-chunk-loading.patch
@@ -0,0 +1,78 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:35:37 +0800
+Subject: [PATCH] Pufferfish Reduce projectile chunk loading
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index d0c0b4daec59f23a989a8b8f66ea3c704b0e309c..11c1a367fbc25cb63738a00ad93fb0b0b3500e7d 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -46,6 +46,8 @@ public class LuminolConfig {
+
+ public static boolean reduceSensorWork = true;
+ public static boolean enableSuffocationOptimization = true;
++ public static int maxProjectileLoadsPerTick;
++ public static int maxProjectileLoadsPerProjectile;
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -126,6 +128,8 @@ public class LuminolConfig {
+ reduceSensorWork = get("optimizations.reduce_sensor_work",reduceSensorWork,"This optimization is from petal.You can find out more about it on petal's repository");
+ enableSuffocationOptimization = get("optimizations.optimize_suffocation_check",enableSuffocationOptimization);
+ initEntityTTL();
++ maxProjectileLoadsPerTick = get("optimizations.projectile.max-loads-per-tick", maxProjectileLoadsPerTick, "Controls how many chunks are allowed \nto be sync loaded by projectiles in a tick.");
++ maxProjectileLoadsPerProjectile = get("optimizations.projectile.max-loads-per-projectile", maxProjectileLoadsPerProjectile, "Controls how many chunks a projectile \n can load in its lifetime before it gets \nautomatically removed.");
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+index 0c8f90a904c01105ba5fa6a8037150697bc2621e..527168199605c8c606c6dde563e4fe096b6f17ac 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+@@ -4,6 +4,8 @@ import com.google.common.base.MoreObjects;
+ import java.util.Iterator;
+ import java.util.UUID;
+ import javax.annotation.Nullable;
++
++import io.papermc.paper.threadedregions.TickRegionScheduler;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.protocol.Packet;
+@@ -44,6 +46,36 @@ public abstract class Projectile extends Entity implements TraceableEntity {
+ super(type, world);
+ }
+
++ // Pufferfish start
++ private static long loadedThisTick = 0;
++ private static long loadedTick;
++
++ private int loadedLifetime = 0;
++ @Override
++ public void setPos(double x, double y, double z) {
++ long currentTick = TickRegionScheduler.getCurrentRegion().getData().getCurrentTick();
++ if (loadedTick != currentTick) {
++ loadedTick = currentTick;
++ loadedThisTick = 0;
++ }
++ int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4;
++ int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4;
++ if (previousX != newX || previousZ != newZ) {
++ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedMainThread(newX, newZ) != null;
++ if (!isLoaded) {
++ if (Projectile.loadedThisTick > me.earthme.luminol.LuminolConfig.maxProjectileLoadsPerTick) {
++ if (++this.loadedLifetime > me.earthme.luminol.LuminolConfig.maxProjectileLoadsPerProjectile) {
++ this.discard();
++ }
++ return;
++ }
++ Projectile.loadedThisTick++;
++ }
++ }
++ super.setPos(x, y, z);
++ }
++ // Pufferfish end
++
+ public void setOwner(@Nullable Entity entity) {
+ if (entity != null) {
+ this.ownerUUID = entity.getUUID();
diff --git a/patches/server/0030-Pufferfish-Dynamic-Activation-of-Brain.patch b/patches/server/0030-Pufferfish-Dynamic-Activation-of-Brain.patch
new file mode 100644
index 0000000..5517e33
--- /dev/null
+++ b/patches/server/0030-Pufferfish-Dynamic-Activation-of-Brain.patch
@@ -0,0 +1,418 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 16:44:23 +0800
+Subject: [PATCH] Pufferfish Dynamic Activation of Brain
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 11c1a367fbc25cb63738a00ad93fb0b0b3500e7d..4f6af1fa55047e7be9e57c1dd1c60e9d96d12187 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -11,13 +11,16 @@ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+ import org.bukkit.Bukkit;
+
+-import java.util.Arrays;
+-import java.util.List;
+-import java.util.Locale;
++import java.util.*;
+ import java.util.logging.Level;
+ import java.io.File;
+ import java.io.IOException;
+
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.configuration.ConfigurationSection;
++import net.minecraft.world.entity.EntityType;
++
+ public class LuminolConfig {
+ private static final Logger logger = LogManager.getLogger();
+ private static final File PARENT_FOLDER = new File("luminol_config");
+@@ -48,6 +51,12 @@ public class LuminolConfig {
+ public static boolean enableSuffocationOptimization = true;
+ public static int maxProjectileLoadsPerTick;
+ public static int maxProjectileLoadsPerProjectile;
++ public static boolean dearEnabled;
++ public static int startDistance;
++ public static int startDistanceSquared;
++ public static int maximumActivationPrio;
++ public static int activationDistanceMod;
++
+
+ public static void init() throws IOException {
+ PARENT_FOLDER.mkdir();
+@@ -86,6 +95,33 @@ public class LuminolConfig {
+ }
+ }
+
++ private static void initDAB(){
++ dearEnabled = get("optimizations.dab.enabled", true);
++ startDistance = get("optimizations.dab.start-distance", 12,
++ "This value determines how far away an entity has to be\n"+
++ "from the player to start being effected by DEAR.");
++ startDistanceSquared = startDistance * startDistance;
++ maximumActivationPrio = get("optimizations.dab.max-tick-freq",20,
++ "This value defines how often in ticks, the furthest entity\n"+
++ "will get their pathfinders and behaviors ticked. 20 = 1s");
++ activationDistanceMod = get("optimizations.dab.activation-dist-mod",8,
++ """
++ This value defines how much distance modifies an entity's
++ tick frequency. freq = (distanceToPlayer^2) / (2^value)
++ If you want further away entities to tick less often, use 7.
++ If you want further away entities to tick more often, try 9.""");
++
++ for (EntityType> entityType : BuiltInRegistries.ENTITY_TYPE) {
++ entityType.dabEnabled = true; // reset all, before setting the ones to true
++ }
++ get("optimizations.dab.blacklisted-entities",Collections.emptyList(), "A list of entities to ignore for activation")
++ .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> {
++ entityType.dabEnabled = false;
++ }, () -> MinecraftServer.LOGGER.warn("Unknown entity \"" + name + "\"")));
++
++ MAIN_CONFIG.setComment("optimizations.dab", "Optimizes entity brains when\n"+"they're far away from the player");
++ }
++
+ public static void initValues(){
+ serverModName = get("misc.server_mod_name",serverModName,"The servermod name will be sent to players,and you can see it in F3 or motd responses");
+ fakeVanillaModeEnabled = get("misc.enable_fake_vanilla_mode",fakeVanillaModeEnabled,"Enable this to make the ping response of your server like a vanilla server");
+@@ -130,6 +166,7 @@ public class LuminolConfig {
+ initEntityTTL();
+ maxProjectileLoadsPerTick = get("optimizations.projectile.max-loads-per-tick", maxProjectileLoadsPerTick, "Controls how many chunks are allowed \nto be sync loaded by projectiles in a tick.");
+ maxProjectileLoadsPerProjectile = get("optimizations.projectile.max-loads-per-projectile", maxProjectileLoadsPerProjectile, "Controls how many chunks a projectile \n can load in its lifetime before it gets \nautomatically removed.");
++ initDAB();
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 561681deaf647277ecde64eed4cfbd9f38b5fed1..2ba86122ccb444908c35ea5cc1e245f5068a054a 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -982,6 +982,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ this.timings.entityTick.startTiming(); // Spigot
+ profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); try { // Folia - profiler
+ regionizedWorldData.forEachTickingEntity((entity) -> { // Folia - regionised ticking
++ entity.activatedPriorityReset = false; // Pufferfish - DAB
+ if (!entity.isRemoved()) {
+ if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
+ entity.discard();
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 8bdaab46c2e128aa58d13101170ce358146377a8..56efbcc29adca0239ef09a269f0899a3a6e2801b 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -436,6 +436,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ return this.originWorld;
+ }
+ // Paper end
++ // Pufferfish start
++ public boolean activatedPriorityReset = false; // DAB
++ public int activatedPriority = LuminolConfig.maximumActivationPrio; // golf score
++ // Pufferfish end
++
+ public float getBukkitYaw() {
+ return this.yRot;
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
+index 4d60ac50a1d3860f2a2e9265aef9507d790220a3..0867c85964952ec118ea7517cd5ef570be1ee982 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityType.java
++++ b/src/main/java/net/minecraft/world/entity/EntityType.java
+@@ -301,6 +301,7 @@ public class EntityType implements FeatureElement, EntityTypeT
+ private final boolean canSpawnFarFromPlayer;
+ private final int clientTrackingRange;
+ private final int updateInterval;
++ public boolean dabEnabled = false; // Pufferfish
+ @Nullable
+ private String descriptionId;
+ @Nullable
+diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
+index d3f8aa29b05a3813c0ec6e2ea5a253868abd6b07..54e821351e46d25e8b0ead52d2c8dfecd1957544 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -234,10 +234,10 @@ public abstract class Mob extends LivingEntity implements Targeting {
+ @Override
+ public void inactiveTick() {
+ super.inactiveTick();
+- if (this.goalSelector.inactiveTick()) {
++ if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity
+ this.goalSelector.tick();
+ }
+- if (this.targetSelector.inactiveTick()) {
++ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority
+ this.targetSelector.tick();
+ }
+ }
+@@ -934,16 +934,20 @@ public abstract class Mob extends LivingEntity implements Targeting {
+
+ if (i % 2 != 0 && this.tickCount > 1) {
+ this.level().getProfiler().push("targetSelector");
++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
+ this.targetSelector.tickRunningGoals(false);
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("goalSelector");
++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
+ this.goalSelector.tickRunningGoals(false);
+ this.level().getProfiler().pop();
+ } else {
+ this.level().getProfiler().push("targetSelector");
++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
+ this.targetSelector.tick();
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("goalSelector");
++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
+ this.goalSelector.tick();
+ this.level().getProfiler().pop();
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+index b738ee2d3801fadfd09313f05ae24593e56b0ec6..9306ab8d2b6eeb73f86d4d94c0065d461905d702 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+@@ -11,6 +11,8 @@ import java.util.Set;
+ import java.util.function.Predicate;
+ import java.util.function.Supplier;
+ import java.util.stream.Stream;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.util.profiling.ProfilerFiller;
+ import org.slf4j.Logger;
+
+@@ -53,9 +55,12 @@ public class GoalSelector {
+ }
+
+ // Paper start
+- public boolean inactiveTick() {
++ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start
++ if (inactive && !LuminolConfig.dearEnabled) tickRate = 4; // reset to Paper's
++ tickRate = Math.min(tickRate, this.newGoalRate);
+ this.curRate++;
+- return this.curRate % this.newGoalRate == 0;
++ return this.curRate % tickRate == 0;
++ // Pufferfish end
+ }
+ public boolean hasTasks() {
+ for (WrappedGoal task : this.availableGoals) {
+diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
+index 5ad5f22e5aa26445e5eb229958e7bf356bdd460e..d241ca4d0295f9fce39c11197bd435cfac7f6e54 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
++++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
+@@ -221,9 +221,11 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
+ return 0.4F;
+ }
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("allayBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel) this.level(), this);
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("allayActivityUpdate");
+diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+index d5b97d4316390028f54aa9bb9fa52b0b003e32a0..b4793b88688bd568a428aa520e880f0038de45a7 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
++++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+@@ -280,9 +280,11 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder {
+ return true;
+ }
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("frogBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel)this.level(), this);
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("frogActivityUpdate");
+diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+index 4aeab90e778629c355189dfe79c39c4b21f5f5ac..6ed4ac06c76b8d0d6e8db778cade15dbd1e3e5f5 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+@@ -77,9 +77,11 @@ public class Tadpole extends AbstractFish {
+ return SoundEvents.TADPOLE_FLOP;
+ }
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("tadpoleBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel) this.level(), this);
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("tadpoleActivityUpdate");
+diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+index 111a244087e24f25ba8524a46a228da10cd9498a..ff12ba2b79cb2e7e0bfd0e3b58ff6cb9e770092b 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+@@ -191,9 +191,11 @@ public class Goat extends Animal {
+ return (Brain) super.getBrain(); // CraftBukkit - decompile error
+ }
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("goatBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel) this.level(), this);
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("goatActivityUpdate");
+diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+index 4257f2282152aee09533c9a2e53018d3e49effa4..e703320717ff620a19ff76d1c10066117c9895d5 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+@@ -128,9 +128,11 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
+ return (Brain) super.getBrain(); // Paper - decompile fix
+ }
+
++ private int behaviorTick; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("hoglinBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel)this.level(), this);
+ this.level().getProfiler().pop();
+ HoglinAi.updateActivity(this);
+diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+index 6407ddef8442fce4f310ac4babf3e3de0dd5fc9a..cfdc1650783d6855e0d4f33ec68aab48dbee09f0 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+@@ -300,9 +300,11 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
+ return !this.cannotHunt;
+ }
+
++ private int behaviorTick; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ this.level().getProfiler().push("piglinBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick((ServerLevel) this.level(), this);
+ this.level().getProfiler().pop();
+ PiglinAi.updateActivity(this);
+diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
+index b2bc3a832c310448046ccde37a04918aa6d63197..5e43912708f9074dee1bb351efa737a7e6796fc3 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
++++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
+@@ -272,11 +272,13 @@ public class Warden extends Monster implements VibrationSystem {
+
+ }
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ protected void customServerAiStep() {
+ ServerLevel worldserver = (ServerLevel) this.level();
+
+ worldserver.getProfiler().push("wardenBrain");
++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ this.getBrain().tick(worldserver, this);
+ this.level().getProfiler().pop();
+ super.customServerAiStep();
+diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
+index 4e9ccc518f37755e86687653f7724240db754682..a94661deaa6e1288bb957dc5d7711c5d03b9e460 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
+@@ -142,6 +142,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+ return holder.is(PoiTypes.MEETING);
+ });
+
++ public long nextGolemPanic = -1; // Pufferfish
++
+ public Villager(EntityType extends Villager> entityType, Level world) {
+ this(entityType, world, VillagerType.PLAINS);
+ }
+@@ -245,6 +247,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+ }
+ // Spigot End
+
++ private int behaviorTick = 0; // Pufferfish
+ @Override
+ @Deprecated // Paper
+ protected void customServerAiStep() {
+@@ -254,7 +257,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+ protected void customServerAiStep(final boolean inactive) {
+ // Paper end
+ this.level().getProfiler().push("villagerBrain");
+- if (!inactive) this.getBrain().tick((ServerLevel) this.level(), this); // Paper
++ // Pufferfish start
++ if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) {
++ this.getBrain().tick((ServerLevel) this.level(), this); // Paper
++ }
++ // Pufferfish end
+ this.level().getProfiler().pop();
+ if (this.assignProfessionWhenSpawned) {
+ this.assignProfessionWhenSpawned = false;
+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
+index 22daed525b023998a05884db603e2c7385ce0873..e77e1c60a611c49489c65d66e54236a86c3093e3 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -1,5 +1,6 @@
+ package org.spigotmc;
+
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.server.MinecraftServer;
+ import net.minecraft.server.level.ServerChunkCache;
+@@ -38,6 +39,10 @@ import co.aikar.timings.MinecraftTimings;
+ import net.minecraft.world.entity.schedule.Activity;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.AABB;
++// Pufferfish start
++import net.minecraft.world.phys.Vec3;
++import java.util.List;
++// Pufferfish end
+
+ public class ActivationRange
+ {
+@@ -230,6 +235,25 @@ public class ActivationRange
+ }
+ // Paper end - configurable marker ticking
+ ActivationRange.activateEntity(entity);
++
++ // Pufferfish start
++ if (LuminolConfig.dearEnabled && entity.getType().dabEnabled) {
++ if (!entity.activatedPriorityReset) {
++ entity.activatedPriorityReset = true;
++ entity.activatedPriority = LuminolConfig.maximumActivationPrio;
++ }
++ Vec3 playerVec = player.position();
++ Vec3 entityVec = entity.position();
++ double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z;
++ int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ);
++ entity.activatedPriority = squaredDistance > LuminolConfig.startDistanceSquared ?
++ Math.max(1, Math.min(squaredDistance >> LuminolConfig.activationDistanceMod, entity.activatedPriority)) :
++ 1;
++ } else {
++ entity.activatedPriority = 1;
++ }
++ // Pufferfish end
++
+ }
+ // Paper end
+ }
+@@ -246,12 +270,12 @@ public class ActivationRange
+ if ( io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() > entity.activatedTick ) // Folia - threaded regions
+ {
+ if ( entity.defaultActivationState )
+- {
++ { // Pufferfish - diff on change
+ entity.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - threaded regions
+ return;
+ }
+ if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) )
+- {
++ { // Pufferfish - diff on change
+ entity.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - threaded regions
+ }
+ }
diff --git a/patches/server/0031-Gale-Variable-entity-wake-up-duration.patch b/patches/server/0031-Gale-Variable-entity-wake-up-duration.patch
new file mode 100644
index 0000000..6ac5e1e
--- /dev/null
+++ b/patches/server/0031-Gale-Variable-entity-wake-up-duration.patch
@@ -0,0 +1,75 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 17:08:22 +0800
+Subject: [PATCH] Gale Variable entity wake-up duration
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 4f6af1fa55047e7be9e57c1dd1c60e9d96d12187..793dc5b35e9a0665d486a74ce5b776b43b941ee2 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -56,6 +56,7 @@ public class LuminolConfig {
+ public static int startDistanceSquared;
+ public static int maximumActivationPrio;
+ public static int activationDistanceMod;
++ public static double entityWakeUpDurationRatioStandardDeviation = 0.2;
+
+
+ public static void init() throws IOException {
+@@ -167,6 +168,7 @@ public class LuminolConfig {
+ maxProjectileLoadsPerTick = get("optimizations.projectile.max-loads-per-tick", maxProjectileLoadsPerTick, "Controls how many chunks are allowed \nto be sync loaded by projectiles in a tick.");
+ maxProjectileLoadsPerProjectile = get("optimizations.projectile.max-loads-per-projectile", maxProjectileLoadsPerProjectile, "Controls how many chunks a projectile \n can load in its lifetime before it gets \nautomatically removed.");
+ initDAB();
++ entityWakeUpDurationRatioStandardDeviation = get("optimizations.entity_wakeup_duration_ratio_standard_deviation",entityWakeUpDurationRatioStandardDeviation);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
+index e77e1c60a611c49489c65d66e54236a86c3093e3..51adf24b9274b6bc78ef94e77a0b2d73b1f2bbbb 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -76,28 +76,40 @@ public class ActivationRange
+ if (entity.activationType == ActivationType.VILLAGER) {
+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && worldData.wakeupInactiveRemainingVillagers > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingVillagers--; // Folia - threaded regions
+- return config.wakeUpInactiveVillagersFor;
++ return getWakeUpDurationWithVariance(entity, config.wakeUpInactiveVillagersFor); // Gale - variable entity wake-up duration
+ }
+ } else if (entity.activationType == ActivationType.ANIMAL) {
+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && worldData.wakeupInactiveRemainingAnimals > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingAnimals--; // Folia - threaded regions
+- return config.wakeUpInactiveAnimalsFor;
++ return getWakeUpDurationWithVariance(entity, config.wakeUpInactiveAnimalsFor); // Gale - variable entity wake-up duration
+ }
+ } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && worldData.wakeupInactiveRemainingFlying > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingFlying--; // Folia - threaded regions
+- return config.wakeUpInactiveFlyingFor;
++ return getWakeUpDurationWithVariance(entity, config.wakeUpInactiveFlyingFor); // Gale - variable entity wake-up duration
+ }
+ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && worldData.wakeupInactiveRemainingMonsters > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingMonsters--; // Folia - threaded regions
+- return config.wakeUpInactiveMonstersFor;
++ return getWakeUpDurationWithVariance(entity, config.wakeUpInactiveMonstersFor); // Gale - variable entity wake-up duration
+ }
+ }
+ return -1;
+ }
+ // Paper end
+
++ // Gale start - variable entity wake-up duration
++ private static final java.util.Random wakeUpDurationRandom = new java.util.Random();
++
++ private static int getWakeUpDurationWithVariance(Entity entity, int wakeUpDuration) {
++ double deviation = LuminolConfig.entityWakeUpDurationRatioStandardDeviation;
++ if (deviation <= 0) {
++ return wakeUpDuration;
++ }
++ return (int) Math.min(Integer.MAX_VALUE, Math.max(1, Math.round(wakeUpDuration * wakeUpDurationRandom.nextGaussian(1, deviation))));
++ }
++ // Gale end - variable entity wake-up duration
++
+ static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 );
+
+ /**
diff --git a/patches/server/0032-Gale-Don-t-load-chunks-to-activate-climbing-entities.patch b/patches/server/0032-Gale-Don-t-load-chunks-to-activate-climbing-entities.patch
new file mode 100644
index 0000000..90ed9c4
--- /dev/null
+++ b/patches/server/0032-Gale-Don-t-load-chunks-to-activate-climbing-entities.patch
@@ -0,0 +1,98 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 17:21:11 +0800
+Subject: [PATCH] Gale Don't load chunks to activate climbing entities
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index 793dc5b35e9a0665d486a74ce5b776b43b941ee2..fcd532898e099a809969603941b90fdd415b03c3 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -57,6 +57,7 @@ public class LuminolConfig {
+ public static int maximumActivationPrio;
+ public static int activationDistanceMod;
+ public static double entityWakeUpDurationRatioStandardDeviation = 0.2;
++ public static boolean loadChunksToActiveClimbingEntities = false;
+
+
+ public static void init() throws IOException {
+@@ -169,6 +170,7 @@ public class LuminolConfig {
+ maxProjectileLoadsPerProjectile = get("optimizations.projectile.max-loads-per-projectile", maxProjectileLoadsPerProjectile, "Controls how many chunks a projectile \n can load in its lifetime before it gets \nautomatically removed.");
+ initDAB();
+ entityWakeUpDurationRatioStandardDeviation = get("optimizations.entity_wakeup_duration_ratio_standard_deviation",entityWakeUpDurationRatioStandardDeviation);
++ loadChunksToActiveClimbingEntities = get("optimizations.load_chunks_to_active_climbing_entities",loadChunksToActiveClimbingEntities);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 56efbcc29adca0239ef09a269f0899a3a6e2801b..54daa1744c89e98136d0f594a35119d5fd3d6bc8 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -5330,6 +5330,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ return this.feetBlockState;
+ }
+
++ // Gale start - don't load chunks to activate climbing entities
++ public @Nullable BlockState getFeetBlockStateIfLoaded() {
++ if (this.feetBlockState == null) {
++ this.feetBlockState = this.level.getBlockStateIfLoaded(this.blockPosition());
++ }
++
++ return this.feetBlockState;
++ }
++ // Gale end - don't load chunks to activate climbing entities
++
+ public ChunkPos chunkPosition() {
+ return this.chunkPosition;
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index f13d26b280f095d006ffccb36af66bb7487cb8da..ecbacbed29af51d949122b21c3ae9fc95885c6d8 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -2027,19 +2027,43 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ public boolean onClimableCached() {
+ if (!this.blockPosition().equals(this.lastClimbingPosition)) {
+- this.cachedOnClimable = this.onClimbable();
+- this.lastClimbingPosition = this.blockPosition();
++ // Gale start - don't load chunks to activate climbing entities
++ Boolean onClimbableIfLoaded = this.onClimbable(LuminolConfig.loadChunksToActiveClimbingEntities);
++ if (onClimbableIfLoaded != null) {
++ this.cachedOnClimable = onClimbableIfLoaded;
++ this.lastClimbingPosition = this.blockPosition();
++ } else {
++ this.cachedOnClimable = false;
++ this.lastClimbingPosition = null;
++ }
++ // Gale end - don't load chunks to activate climbing entities
+ }
+ return this.cachedOnClimable;
+ }
+ // Pufferfish end
+
+ public boolean onClimbable() {
++ // Gale start - don't load chunks to activate climbing entities
++ return onClimbable(true);
++ }
++
++ public Boolean onClimbable(boolean loadChunk) {
++ // Gale end - don't load chunks to activate climbing entities
+ if (this.isSpectator()) {
+ return false;
+ } else {
+ BlockPos blockposition = this.blockPosition();
+- BlockState iblockdata = this.getFeetBlockState();
++ // Gale start - don't load chunks to activate climbing entities
++ BlockState iblockdata;
++ if (loadChunk) {
++ iblockdata = this.getFeetBlockState();
++ } else {
++ iblockdata = this.getFeetBlockStateIfLoaded();
++ if (iblockdata == null) {
++ return null;
++ }
++ }
++ // Gale end - don't load chunks to activate climbing entities
+
+ if (iblockdata.is(BlockTags.CLIMBABLE)) {
+ this.lastClimbablePos = Optional.of(blockposition);
diff --git a/patches/server/0033-Gale-Optimize-sun-burn-tick.patch b/patches/server/0033-Gale-Optimize-sun-burn-tick.patch
new file mode 100644
index 0000000..cb783cc
--- /dev/null
+++ b/patches/server/0033-Gale-Optimize-sun-burn-tick.patch
@@ -0,0 +1,75 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 17:21:44 +0800
+Subject: [PATCH] Gale Optimize sun burn tick
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 54daa1744c89e98136d0f594a35119d5fd3d6bc8..6f3e87661c3181b6537e4d6d96e50d8d5680c040 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -307,7 +307,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ public double xo;
+ public double yo;
+ public double zo;
+- private Vec3 position;
++ public Vec3 position; // Gale - JettPack - optimize sun burn tick - private -> public
+ public BlockPos blockPosition; // Pufferfish - private->public
+ private ChunkPos chunkPosition;
+ private Vec3 deltaMovement;
+@@ -2026,9 +2026,17 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+ /** @deprecated */
+ @Deprecated
+ public float getLightLevelDependentMagicValue() {
+- return this.level().hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level().getLightLevelDependentMagicValue(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())) : 0.0F;
++ return this.getLightLevelDependentMagicValue(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())); // Gale - JettPack - optimize sun burn tick - allow passing BlockPos to getLightLevelDependentMagicValue
+ }
+
++ // Gale start - JettPack - optimize sun burn tick - allow passing BlockPos to getLightLevelDependentMagicValue
++ /** @deprecated */
++ @Deprecated
++ public float getLightLevelDependentMagicValue(BlockPos pos) {
++ return this.level().hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getLightLevelDependentMagicValue(pos) : 0.0F;
++ }
++ // Gale end - JettPack - optimize sun burn tick - allow passing BlockPos to getLightLevelDependentMagicValue
++
+ public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
+ this.absMoveTo(x, y, z);
+ this.setYRot(yaw % 360.0F);
+diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
+index 54e821351e46d25e8b0ead52d2c8dfecd1957544..394913950ac8fbcfcdbce817bac95f3b6604beb7 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -1736,13 +1736,29 @@ public abstract class Mob extends LivingEntity implements Targeting {
+
+ }
+
++ // Gale start - JettPack - optimize sun burn tick - cache eye blockpos
++ private BlockPos cached_eye_blockpos;
++ private int cached_position_hashcode;
++ // Gale end - JettPack - optimize sun burn tick - cache eye blockpos
++
+ public boolean isSunBurnTick() {
+ if (this.level().isDay() && !this.level().isClientSide) {
+- float f = this.getLightLevelDependentMagicValue();
+- BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
++ // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
++ int positionHashCode = this.position.hashCode();
++ if (this.cached_position_hashcode != positionHashCode) {
++ this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
++ this.cached_position_hashcode = positionHashCode;
++ }
++
++ float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness
++
++ // Check brightness first
++ if (f <= 0.5F) return false;
++ if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false;
++ // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
+ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
+
+- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) {
++ if (!flag && this.level().canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
+ return true;
+ }
+ }
diff --git a/patches/server/0034-Gale-Reduce-acquire-POI-for-stuck-entities.patch b/patches/server/0034-Gale-Reduce-acquire-POI-for-stuck-entities.patch
new file mode 100644
index 0000000..765f6e5
--- /dev/null
+++ b/patches/server/0034-Gale-Reduce-acquire-POI-for-stuck-entities.patch
@@ -0,0 +1,90 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 17:30:32 +0800
+Subject: [PATCH] Gale Reduce acquire POI for stuck entities
+
+
+diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
+index fcd532898e099a809969603941b90fdd415b03c3..d032786938db9725e1be72dae63a1387bcb69d79 100644
+--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
++++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
+@@ -12,14 +12,10 @@ import org.apache.logging.log4j.Logger;
+ import org.bukkit.Bukkit;
+
+ import java.util.*;
+-import java.util.logging.Level;
+ import java.io.File;
+ import java.io.IOException;
+
+-import net.minecraft.core.registries.BuiltInRegistries;
+ import net.minecraft.server.MinecraftServer;
+-import org.bukkit.configuration.ConfigurationSection;
+-import net.minecraft.world.entity.EntityType;
+
+ public class LuminolConfig {
+ private static final Logger logger = LogManager.getLogger();
+@@ -58,6 +54,7 @@ public class LuminolConfig {
+ public static int activationDistanceMod;
+ public static double entityWakeUpDurationRatioStandardDeviation = 0.2;
+ public static boolean loadChunksToActiveClimbingEntities = false;
++ public static int acquirePoiForStuckEntityInterval = 60;
+
+
+ public static void init() throws IOException {
+@@ -171,6 +168,7 @@ public class LuminolConfig {
+ initDAB();
+ entityWakeUpDurationRatioStandardDeviation = get("optimizations.entity_wakeup_duration_ratio_standard_deviation",entityWakeUpDurationRatioStandardDeviation);
+ loadChunksToActiveClimbingEntities = get("optimizations.load_chunks_to_active_climbing_entities",loadChunksToActiveClimbingEntities);
++ acquirePoiForStuckEntityInterval = get("optimizations.acquire_poi_for_stuck_entity_interval", acquirePoiForStuckEntityInterval);
+ }
+
+ public static T get(String key,T def){
+diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+index 8f8b29f80d1573981ccffd207dd6e0941e71a352..ba4cdaf499c7ffef0c7fbdd575bdba841bcb7282 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+@@ -7,12 +7,14 @@ import java.util.HashSet;
+ import java.util.Optional;
+ import java.util.Set;
+ import java.util.function.Predicate;
+-import java.util.stream.Collectors;
+ import javax.annotation.Nullable;
++
++import me.earthme.luminol.LuminolConfig;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.GlobalPos;
+ import net.minecraft.core.Holder;
+ import net.minecraft.network.protocol.game.DebugPackets;
++import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.world.entity.Mob;
+ import net.minecraft.world.entity.PathfinderMob;
+@@ -26,6 +28,13 @@ import org.apache.commons.lang3.mutable.MutableLong;
+ public class AcquirePoi {
+ public static final int SCAN_RANGE = 48;
+
++ // Gale start - Airplane - reduce acquire POI for stuck entities
++ public static void addAdditionalTimeToMutableLongIfEntityIsStuck(MutableLong mutableLong, ServerLevel world, PathfinderMob entity) {
++ long stuckEntityAdditionalWaitTime = LuminolConfig.acquirePoiForStuckEntityInterval;
++ mutableLong.add(stuckEntityAdditionalWaitTime <= 0L ? 0L : entity.getNavigation().isStuck() ? stuckEntityAdditionalWaitTime : 0L);
++ }
++ // Gale end - Airplane - reduce acquire POI for stuck entities
++
+ public static BehaviorControl create(Predicate> poiPredicate, MemoryModuleType poiPosModule, boolean onlyRunIfChild, Optional entityStatus) {
+ return create(poiPredicate, poiPosModule, poiPosModule, onlyRunIfChild, entityStatus);
+ }
+@@ -42,12 +51,13 @@ public class AcquirePoi {
+ return false;
+ } else if (mutableLong.getValue() == 0L) {
+ mutableLong.setValue(world.getGameTime() + (long)world.random.nextInt(20));
++ addAdditionalTimeToMutableLongIfEntityIsStuck(mutableLong, world, entity); // Gale - Airplane - reduce acquire POI for stuck entities
+ return false;
+ } else if (world.getGameTime() < mutableLong.getValue()) {
+ return false;
+ } else {
+ mutableLong.setValue(time + 20L + (long)world.getRandom().nextInt(20));
+- if (entity.getNavigation().isStuck()) mutableLong.add(200); // Paper - Wait an additional 10s to check again if they're stuck
++ addAdditionalTimeToMutableLongIfEntityIsStuck(mutableLong, world, entity); // Gale - Airplane - reduce acquire POI for stuck entities
+ PoiManager poiManager = world.getPoiManager();
+ long2ObjectMap.long2ObjectEntrySet().removeIf((entry) -> {
+ return !entry.getValue().isStillValid(time);
diff --git a/patches/server/0035-Gale-Skip-secondary-POI-sensor-if-absent.patch b/patches/server/0035-Gale-Skip-secondary-POI-sensor-if-absent.patch
new file mode 100644
index 0000000..c4a0900
--- /dev/null
+++ b/patches/server/0035-Gale-Skip-secondary-POI-sensor-if-absent.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
+Date: Sun, 26 Nov 2023 17:30:32 +0800
+Subject: [PATCH] Gale Skip secondary POI sensor if absent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
+index cb1d91f9fe98f21c2afbe3894dfd9bca3bdd3ba6..75dc06a3041bfdfb08c914eb50cfa282ae9eb2fe 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
+@@ -22,6 +22,13 @@ public class SecondaryPoiSensor extends Sensor {
+
+ @Override
+ protected void doTick(ServerLevel world, Villager entity) {
++ // Gale start - Lithium - skip secondary POI sensor if absent
++ var secondaryPoi = entity.getVillagerData().getProfession().secondaryPoi();
++ if (secondaryPoi.isEmpty()) {
++ entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE);
++ return;
++ }
++ // Gale end - Lithium - skip secondary POI sensor if absent
+ ResourceKey resourceKey = world.dimension();
+ BlockPos blockPos = entity.blockPosition();
+ List list = Lists.newArrayList();
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..1e612af
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,15 @@
+import java.util.Locale
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ maven("https://repo.papermc.io/repository/maven-public/")
+ }
+}
+
+rootProject.name = "Luminol"
+for (name in listOf("Luminol-API", "Luminol-Server")) {
+ val projName = name.toLowerCase(Locale.ENGLISH)
+ include(projName)
+ findProject(":$projName")!!.projectDir = file(name)
+}