├── .github └── workflows │ └── build.yml ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── nms ├── NMS_V1201 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── i │ │ └── mrhua269 │ │ └── zutils │ │ └── nms │ │ └── v1_20_1 │ │ └── impl │ │ └── FoliaWorldManagerImpl.java ├── NMS_V1202 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── i │ │ └── mrhua269 │ │ └── zutils │ │ └── nms │ │ └── v1_20_2 │ │ └── impl │ │ └── FoliaWorldManagerImpl.java ├── NMS_V1204 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── i │ │ └── mrhua269 │ │ └── zutils │ │ └── nms │ │ └── v1_20_4 │ │ └── impl │ │ └── FoliaWorldManagerImpl.java ├── NMS_V1206 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── i │ │ └── mrhua269 │ │ └── zutils │ │ └── nms │ │ └── v1_20_6 │ │ └── impl │ │ └── FoliaWorldManagerImpl.java ├── NMS_V1211 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── i │ │ └── mrhua269 │ │ └── zutils │ │ └── nms │ │ └── v1_21_1 │ │ └── impl │ │ └── FoliaWorldManagerImpl.java └── NMS_V1213 │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── i │ └── mrhua269 │ └── zutils │ └── nms │ └── v1_21_3 │ └── impl │ └── FoliaWorldManagerImpl.java ├── settings.gradle.kts ├── zutils-based-api ├── build.gradle.kts └── src │ └── main │ └── java │ └── i │ └── mrhua269 │ └── zutils │ ├── api │ ├── WorldManager.java │ ├── ZAPIEntryPoint.java │ ├── scheduler │ │ ├── ScheduledTask.java │ │ └── SchedulerService.java │ └── teleporter │ │ └── Teleporter.java │ ├── impl │ ├── BukkitWorldManagerImpl.java │ ├── scheduler │ │ ├── BukkitSchedulerServiceImpl.java │ │ ├── FoliaSchedulerServiceImpl.java │ │ └── task │ │ │ └── NormalScheduledTaskImpl.java │ └── teleporter │ │ ├── BukkitTeleporterImpl.java │ │ └── FoliaTeleporterImpl.java │ └── shared │ └── Utils.java └── zutils-plugin ├── build.gradle.kts └── src └── main ├── java └── i │ └── mrhua269 │ └── zutilsplugin │ └── ZUtils.java └── resources └── plugin.yml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: MikuMC CI - ZetaUtils 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: write-all 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | environment: default 15 | env: 16 | MAVEN_REPO_USER: ${{ secrets.MAVEN_REPO_USER }} 17 | MAVEN_REPO_PASSWORD: ${{ secrets.MAVEN_REPO_PASSWD }} 18 | steps: 19 | - name: Checkout Git Repository 20 | uses: actions/checkout@v4 21 | - name: Validate Gradle wrapper 22 | uses: gradle/actions/wrapper-validation@v3 23 | - name: Setup Gradle 24 | uses: gradle/actions/setup-gradle@v3 25 | with: 26 | gradle-version: 8.7 27 | - name: Set up JDK 28 | uses: actions/setup-java@v4 29 | with: 30 | distribution: 'zulu' 31 | java-version: '21' 32 | - name: Build 33 | run: gradle shadowJar 34 | - name: Publish to repo 35 | run: gradle publish 36 | - name: Upload Artifact 37 | uses: "actions/upload-artifact@v4" 38 | with: 39 | name: "${{ env.project_id_b }} CI Artifacts" 40 | path: "build/libs/*.jar" 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZetaUtils 2 | A simple packaged easy to use MC bukkit plugin library that make plugins work with folia easier 3 | 4 | ## What's inside 5 | - World APIs(Including folia) 6 | - Scheduler APIs 7 | - Teleporting APIS 8 | 9 | ## Build 10 | 11 | ```shell 12 | ./gradlew shadowJar 13 | ``` 14 | 15 | Environment requirements: Java 21 16 | After building, you cloud find the final jar in 'zutils-plugin/build/libs/' 17 | 18 | ## Download 19 | All built jars are in the CI artifacts, you could find them in Action 20 | 21 | ## Credits 22 | - [DirtyFolia(World API #loadWorld)](https://github.com/killerprojecte/Folia) 23 | - [Skyllia(World API #loadWorld)](https://github.com/Euphillya/Skyllia) 24 | - [MorePaperlib(Schedulers model)](https://github.com/A248/MorePaperLib) 25 | - [RebirthUtils(The actual framework)](https://github.com/RebirthUnion/RebirthUtils) -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.9.21" 3 | id("java-library") 4 | id("java") 5 | id("com.github.johnrengelman.shadow") version "8.1.1" 6 | `maven-publish` 7 | } 8 | 9 | group = "i.mrhua269" 10 | version = "1.0-SNAPSHOT" 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | allprojects { 17 | apply(plugin = "java") 18 | apply(plugin = "java-library") 19 | apply(plugin = "maven-publish") 20 | apply(plugin = "com.github.johnrengelman.shadow") 21 | 22 | repositories { 23 | mavenCentral() 24 | maven("https://repo.papermc.io/repository/maven-public/") 25 | maven("https://oss.sonatype.org/content/groups/public/") 26 | maven("https://maven.moliatopia.icu/repository/maven-public/") 27 | maven("https://maven.nostal.ink/repository/maven-snapshots/") 28 | } 29 | 30 | publishing { 31 | repositories { 32 | maven { 33 | name = "moliaMavenRepo" 34 | url = uri("https://maven.moliatopia.icu/repository/maven-snapshots/") 35 | 36 | credentials.username = System.getenv("MAVEN_REPO_USER") 37 | credentials.password = System.getenv("MAVEN_REPO_PASSWORD") 38 | } 39 | } 40 | } 41 | 42 | tasks { 43 | compileJava { 44 | options.encoding = "UTF-8" 45 | } 46 | processResources { 47 | filesMatching("**/plugin.yml") { 48 | expand(rootProject.project.properties) 49 | } 50 | 51 | // Always re-run this task 52 | outputs.upToDateWhen { false } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikuMC/ZetaUtils/81b64a54453c38f6c6517901f0fb3f950241f6dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 14 14:09:32 CST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /nms/NMS_V1201/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.1" 3 | } 4 | 5 | dependencies { 6 | paperweight.foliaDevBundle("1.20.1-R0.1-SNAPSHOT") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1201/src/main/java/i/mrhua269/zutils/nms/v1_20_1/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_20_1.impl; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import com.mojang.datafixers.util.Pair; 6 | import com.mojang.serialization.DynamicOps; 7 | import com.mojang.serialization.Lifecycle; 8 | import i.mrhua269.zutils.api.WorldManager; 9 | import io.papermc.paper.chunk.system.io.RegionFileIOThread; 10 | import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; 11 | import io.papermc.paper.chunk.system.scheduling.NewChunkHolder; 12 | import io.papermc.paper.threadedregions.RegionizedServer; 13 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 14 | import net.minecraft.core.registries.Registries; 15 | import net.minecraft.nbt.NbtOps; 16 | import net.minecraft.nbt.Tag; 17 | import net.minecraft.resources.RegistryOps; 18 | import net.minecraft.resources.ResourceKey; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.server.WorldLoader; 21 | import net.minecraft.server.dedicated.DedicatedServer; 22 | import net.minecraft.server.dedicated.DedicatedServerProperties; 23 | import net.minecraft.server.level.ServerLevel; 24 | import net.minecraft.server.level.TicketType; 25 | import net.minecraft.util.GsonHelper; 26 | import net.minecraft.util.datafix.DataFixers; 27 | import net.minecraft.world.Difficulty; 28 | import net.minecraft.world.entity.ai.village.VillageSiege; 29 | import net.minecraft.world.entity.npc.CatSpawner; 30 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 31 | import net.minecraft.world.level.*; 32 | import net.minecraft.world.level.biome.BiomeManager; 33 | import net.minecraft.world.level.dimension.LevelStem; 34 | import net.minecraft.world.level.levelgen.PatrolSpawner; 35 | import net.minecraft.world.level.levelgen.PhantomSpawner; 36 | import net.minecraft.world.level.levelgen.WorldDimensions; 37 | import net.minecraft.world.level.levelgen.WorldOptions; 38 | import net.minecraft.world.level.storage.LevelStorageSource; 39 | import net.minecraft.world.level.storage.PrimaryLevelData; 40 | import net.minecraft.world.level.storage.WorldData; 41 | import org.bukkit.Bukkit; 42 | import org.bukkit.World; 43 | import org.bukkit.WorldCreator; 44 | import org.bukkit.event.world.WorldLoadEvent; 45 | import org.bukkit.event.world.WorldUnloadEvent; 46 | import org.bukkit.generator.BiomeProvider; 47 | import org.bukkit.generator.ChunkGenerator; 48 | import org.bukkit.generator.WorldInfo; 49 | import org.jetbrains.annotations.NotNull; 50 | import org.bukkit.craftbukkit.v1_20_R1.CraftServer; 51 | import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; 52 | import org.bukkit.craftbukkit.v1_20_R1.generator.CraftWorldInfo; 53 | 54 | import java.io.File; 55 | import java.io.IOException; 56 | import java.lang.reflect.Field; 57 | import java.lang.reflect.Method; 58 | import java.text.DecimalFormat; 59 | import java.util.List; 60 | import java.util.Locale; 61 | import java.util.Map; 62 | import java.util.concurrent.TimeUnit; 63 | 64 | public class FoliaWorldManagerImpl implements WorldManager { 65 | 66 | //TODO Did we ACTUALLY kill the region? 67 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 68 | level.regioniser.computeForAllRegions(region -> { 69 | for (;;) { 70 | boolean result; 71 | 72 | try { 73 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 74 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 75 | tryKillMethod.setAccessible(true); 76 | result = (boolean) tryKillMethod.invoke(region); 77 | }catch (Exception e){ 78 | break; 79 | } 80 | 81 | if (result) { 82 | break; 83 | } 84 | } 85 | }); 86 | } 87 | 88 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 89 | if (first && halt) { 90 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 91 | if (!world.chunkTaskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { 92 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 93 | } else { 94 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 95 | } 96 | } 97 | 98 | if (save) { 99 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); 100 | } 101 | 102 | if (last) { 103 | if (world.chunkDataControllerNew.hasTasks() || world.entityDataControllerNew.hasTasks() || world.poiDataControllerNew.hasTasks()) { 104 | RegionFileIOThread.flush(); 105 | } 106 | 107 | try { 108 | world.chunkDataControllerNew.getCache().close(); 109 | } catch (final IOException ex) { 110 | MinecraftServer.LOGGER.error("Failed to close chunk regionfile cache for world '" + world.getWorld().getName() + "'", ex); 111 | } 112 | try { 113 | world.entityDataControllerNew.getCache().close(); 114 | } catch (final IOException ex) { 115 | MinecraftServer.LOGGER.error("Failed to close entity regionfile cache for world '" + world.getWorld().getName() + "'", ex); 116 | } 117 | try { 118 | world.poiDataControllerNew.getCache().close(); 119 | } catch (final IOException ex) { 120 | MinecraftServer.LOGGER.error("Failed to close poi regionfile cache for world '" + world.getWorld().getName() + "'", ex); 121 | } 122 | } 123 | } 124 | 125 | private void saveAllChunksNoCheck(ServerLevel level, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 126 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 127 | 128 | final List holders = holderManager.getChunkHolders(); //We don't need current region because all regions are killed right now 129 | final DecimalFormat format = new DecimalFormat("#0.00"); 130 | 131 | int saved = 0; 132 | 133 | final long start = System.nanoTime(); 134 | long lastLog = start; 135 | boolean needsFlush = false; 136 | final int flushInterval = 50; 137 | 138 | int savedChunk = 0; 139 | int savedEntity = 0; 140 | int savedPoi = 0; 141 | 142 | for (int i = 0, len = holders.size(); i < len; ++i) { 143 | final NewChunkHolder holder = holders.get(i); 144 | if (!RegionizedServer.isGlobalTickThread()) { 145 | continue; 146 | } 147 | try { 148 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); 149 | if (saveStat != null) { 150 | ++saved; 151 | needsFlush = flush; 152 | if (saveStat.savedChunk()) { 153 | ++savedChunk; 154 | } 155 | if (saveStat.savedEntityChunk()) { 156 | ++savedEntity; 157 | } 158 | if (saveStat.savedPoiChunk()) { 159 | ++savedPoi; 160 | } 161 | } 162 | } catch (ThreadDeath killSignal) { 163 | throw killSignal; 164 | } catch (final Throwable ex) { 165 | MinecraftServer.LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + level.getWorld().getName() + "'", ex); 166 | } 167 | 168 | if (needsFlush && (saved % flushInterval) == 0) { 169 | needsFlush = false; 170 | RegionFileIOThread.partialFlush(flushInterval / 2); 171 | } 172 | 173 | if (logProgress) { 174 | final long currTime = System.nanoTime(); 175 | if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { 176 | lastLog = currTime; 177 | MinecraftServer.LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + level.getWorld().getName() + "'"); 178 | } 179 | } 180 | } 181 | 182 | if (last && flush) { 183 | RegionFileIOThread.flush(); 184 | if (level.paperConfig().chunks.flushRegionsOnSave) { 185 | try { 186 | level.chunkSource.chunkMap.regionFileCache.flush(); 187 | } catch (IOException ex) { 188 | MinecraftServer.LOGGER.error("Exception when flushing regions in world {}", level.getWorld().getName(), ex); 189 | } 190 | } 191 | } 192 | } 193 | 194 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 195 | // Paper start - rewrite chunk system - add close param 196 | this.save(level, flush, savingDisabled, false); 197 | } 198 | 199 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 200 | if (!savingDisabled) { 201 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 202 | 203 | level.saveLevelData(); 204 | 205 | if (!close) this.saveAllChunksNoCheck(level, level.chunkTaskScheduler.chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 206 | if (close) this.closeChunkProvider(level, true); 207 | } else if (close) { 208 | this.closeChunkProvider(level, false); 209 | } 210 | } 211 | 212 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 213 | this.closeChunkHolderManager(handle, handle.chunkTaskScheduler.chunkHolderManager, save, true,true, true, false); 214 | } 215 | 216 | private void removeWorldFromRegionizedServer(ServerLevel level){ 217 | try { 218 | final Class targetClass = RegionizedServer.class; 219 | final Field worldListField = targetClass.getDeclaredField("worlds"); 220 | worldListField.setAccessible(true); 221 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 222 | 223 | worldList.remove(level); 224 | }catch (Exception e){ 225 | throw new RuntimeException(e); 226 | } 227 | } 228 | 229 | @Override 230 | public boolean unloadWorld(@NotNull World world, boolean save) { 231 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 232 | 233 | if (world == null) { 234 | return false; 235 | } 236 | 237 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 238 | final MinecraftServer console = craftServer.getServer(); 239 | ServerLevel handle = ((CraftWorld) world).getHandle(); 240 | 241 | if (console.getLevel(handle.dimension()) == null) { 242 | return false; 243 | } 244 | 245 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 246 | return false; 247 | } 248 | 249 | if (!handle.players().isEmpty()) { 250 | return false; 251 | } 252 | 253 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 254 | Bukkit.getPluginManager().callEvent(e); 255 | 256 | if (e.isCancelled()) { 257 | return false; 258 | } 259 | 260 | try { 261 | this.removeWorldFromRegionizedServer(handle); 262 | this.killAllThreadedRegionsOnce(handle); 263 | 264 | if (save) { 265 | this.save(handle, true, false); 266 | } 267 | 268 | this.closeChunkProvider(handle, save); 269 | handle.convertable.close(); 270 | } catch (Exception ex) { 271 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 272 | } 273 | 274 | final Map worlds; 275 | 276 | //Ugly reflection :( 277 | try { 278 | final Class craftServerClass = CraftServer.class; 279 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 280 | worldsField.setAccessible(true); 281 | worlds = ((Map) worldsField.get(craftServer)); 282 | }catch (Exception ex){ 283 | throw new RuntimeException(ex); 284 | } 285 | 286 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 287 | console.removeLevel(handle); 288 | return true; 289 | } 290 | 291 | @Override 292 | public boolean unloadWorld(@NotNull String name, boolean save) { 293 | return this.unloadWorld(Bukkit.getWorld(name),save); 294 | } 295 | 296 | @Override 297 | public World createWorld(@NotNull WorldCreator creator) { 298 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 299 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 300 | DedicatedServer console = craftServer.getServer(); 301 | 302 | String name = creator.name(); 303 | 304 | String levelName = console.getProperties().levelName; 305 | if (name.equals(levelName) 306 | || (console.isNetherEnabled() && name.equals(levelName + "_nether")) 307 | || (craftServer.getAllowEnd() && name.equals(levelName + "_the_end")) 308 | ) { 309 | return null; 310 | } 311 | 312 | ChunkGenerator generator = creator.generator(); 313 | BiomeProvider biomeProvider = creator.biomeProvider(); 314 | File folder = new File(craftServer.getWorldContainer(), name); 315 | org.bukkit.World world = craftServer.getWorld(name); 316 | 317 | CraftWorld worldByKey = (CraftWorld) craftServer.getWorld(creator.key()); 318 | if (world != null || worldByKey != null) { 319 | if (world != worldByKey) { 320 | return world; 321 | } 322 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 323 | } 324 | 325 | if (folder.exists()) { 326 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 327 | } 328 | 329 | if (generator == null) { 330 | generator = craftServer.getGenerator(name); 331 | } 332 | 333 | if (biomeProvider == null) { 334 | biomeProvider = craftServer.getBiomeProvider(name); 335 | } 336 | 337 | ResourceKey actualDimension = switch (creator.environment()) { 338 | case NORMAL -> LevelStem.OVERWORLD; 339 | case NETHER -> LevelStem.NETHER; 340 | case THE_END -> LevelStem.END; 341 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 342 | }; 343 | 344 | LevelStorageSource.LevelStorageAccess worldSession; 345 | try { 346 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).createAccess(name, actualDimension); 347 | } catch (IOException ex) { 348 | throw new RuntimeException(ex); 349 | } 350 | 351 | boolean hardcore = creator.hardcore(); 352 | 353 | PrimaryLevelData worlddata; 354 | WorldLoader.DataLoadContext worldloader_a = console.worldLoader; 355 | net.minecraft.core.Registry iregistry = worldloader_a.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); 356 | DynamicOps dynamicops = RegistryOps.create(NbtOps.INSTANCE, worldloader_a.datapackWorldgen()); 357 | Pair pair = worldSession.getDataTag(dynamicops, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 358 | 359 | 360 | if (pair != null) { 361 | worlddata = (PrimaryLevelData) pair.getFirst(); 362 | iregistry = pair.getSecond().dimensions(); 363 | } else { 364 | LevelSettings worldsettings; 365 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 366 | WorldDimensions worlddimensions; 367 | 368 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 369 | 370 | worldsettings = new LevelSettings(name, GameType.byId(Bukkit.getServer().getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldloader_a.dataConfiguration()); 371 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 372 | 373 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 374 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 375 | 376 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 377 | iregistry = worlddimensions_b.dimensions(); 378 | } 379 | worlddata.customDimensions = iregistry; 380 | worlddata.checkName(name); 381 | worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); 382 | 383 | long j = BiomeManager.obfuscateSeed(creator.seed()); 384 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 385 | LevelStem worlddimension = iregistry.get(actualDimension); 386 | 387 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper 388 | if (biomeProvider == null && generator != null) { 389 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 390 | } 391 | 392 | if (console.options.has("forceUpgrade")) { 393 | net.minecraft.server.Main.convertWorldButItWorks( 394 | actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), console.options.has("eraseCache") 395 | ); 396 | } 397 | 398 | ResourceKey worldKey; 399 | worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper 400 | 401 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 402 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 403 | 404 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 405 | int loadRegionRadius = ((32) >> 4); 406 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 407 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 408 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 409 | internal.chunkSource.addTicketAtLevel( 410 | TicketType.UNKNOWN, pos, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, pos 411 | ); 412 | } 413 | } 414 | 415 | console.addLevel(internal); 416 | 417 | internal.setSpawnSettings(true, true); 418 | 419 | internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper 420 | 421 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 422 | 423 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 424 | 425 | return internal.getWorld(); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /nms/NMS_V1202/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.1" 3 | } 4 | 5 | dependencies { 6 | paperweight.foliaDevBundle("1.20.2-R0.1-SNAPSHOT") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1202/src/main/java/i/mrhua269/zutils/nms/v1_20_2/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_20_2.impl; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import com.mojang.datafixers.util.Pair; 6 | import com.mojang.serialization.DynamicOps; 7 | import com.mojang.serialization.Lifecycle; 8 | import i.mrhua269.zutils.api.WorldManager; 9 | import io.papermc.paper.chunk.system.io.RegionFileIOThread; 10 | import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; 11 | import io.papermc.paper.chunk.system.scheduling.NewChunkHolder; 12 | import io.papermc.paper.threadedregions.RegionizedServer; 13 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 14 | import net.minecraft.core.registries.Registries; 15 | import net.minecraft.nbt.NbtOps; 16 | import net.minecraft.nbt.Tag; 17 | import net.minecraft.resources.RegistryOps; 18 | import net.minecraft.resources.ResourceKey; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.server.WorldLoader; 21 | import net.minecraft.server.dedicated.DedicatedServer; 22 | import net.minecraft.server.dedicated.DedicatedServerProperties; 23 | import net.minecraft.server.level.ServerLevel; 24 | import net.minecraft.server.level.TicketType; 25 | import net.minecraft.util.GsonHelper; 26 | import net.minecraft.util.datafix.DataFixers; 27 | import net.minecraft.world.Difficulty; 28 | import net.minecraft.world.entity.ai.village.VillageSiege; 29 | import net.minecraft.world.entity.npc.CatSpawner; 30 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 31 | import net.minecraft.world.level.*; 32 | import net.minecraft.world.level.biome.BiomeManager; 33 | import net.minecraft.world.level.dimension.LevelStem; 34 | import net.minecraft.world.level.levelgen.PatrolSpawner; 35 | import net.minecraft.world.level.levelgen.PhantomSpawner; 36 | import net.minecraft.world.level.levelgen.WorldDimensions; 37 | import net.minecraft.world.level.levelgen.WorldOptions; 38 | import net.minecraft.world.level.storage.LevelStorageSource; 39 | import net.minecraft.world.level.storage.PrimaryLevelData; 40 | import net.minecraft.world.level.storage.WorldData; 41 | import org.bukkit.Bukkit; 42 | import org.bukkit.World; 43 | import org.bukkit.WorldCreator; 44 | import org.bukkit.craftbukkit.v1_20_R2.CraftServer; 45 | import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; 46 | import org.bukkit.craftbukkit.v1_20_R2.generator.CraftWorldInfo; 47 | import org.bukkit.event.world.WorldLoadEvent; 48 | import org.bukkit.event.world.WorldUnloadEvent; 49 | import org.bukkit.generator.BiomeProvider; 50 | import org.bukkit.generator.ChunkGenerator; 51 | import org.bukkit.generator.WorldInfo; 52 | import org.jetbrains.annotations.NotNull; 53 | 54 | import java.io.File; 55 | import java.io.IOException; 56 | import java.lang.reflect.Field; 57 | import java.lang.reflect.Method; 58 | import java.text.DecimalFormat; 59 | import java.util.List; 60 | import java.util.Locale; 61 | import java.util.Map; 62 | import java.util.concurrent.TimeUnit; 63 | 64 | public class FoliaWorldManagerImpl implements WorldManager { 65 | 66 | //TODO Did we ACTUALLY kill the region? 67 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 68 | level.regioniser.computeForAllRegions(region -> { 69 | for (;;) { 70 | boolean result; 71 | 72 | try { 73 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 74 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 75 | tryKillMethod.setAccessible(true); 76 | result = (boolean) tryKillMethod.invoke(region); 77 | }catch (Exception e){ 78 | break; 79 | } 80 | 81 | if (result) { 82 | break; 83 | } 84 | } 85 | }); 86 | } 87 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 88 | if (first && halt) { 89 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 90 | if (!world.chunkTaskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { 91 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 92 | } else { 93 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 94 | } 95 | } 96 | 97 | if (save) { 98 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); 99 | } 100 | 101 | if (last) { 102 | if (world.chunkDataControllerNew.hasTasks() || world.entityDataControllerNew.hasTasks() || world.poiDataControllerNew.hasTasks()) { 103 | RegionFileIOThread.flush(); 104 | } 105 | 106 | try { 107 | world.chunkDataControllerNew.getCache().close(); 108 | } catch (final IOException ex) { 109 | MinecraftServer.LOGGER.error("Failed to close chunk regionfile cache for world '" + world.getWorld().getName() + "'", ex); 110 | } 111 | try { 112 | world.entityDataControllerNew.getCache().close(); 113 | } catch (final IOException ex) { 114 | MinecraftServer.LOGGER.error("Failed to close entity regionfile cache for world '" + world.getWorld().getName() + "'", ex); 115 | } 116 | try { 117 | world.poiDataControllerNew.getCache().close(); 118 | } catch (final IOException ex) { 119 | MinecraftServer.LOGGER.error("Failed to close poi regionfile cache for world '" + world.getWorld().getName() + "'", ex); 120 | } 121 | } 122 | } 123 | 124 | private void saveAllChunksNoCheck(ServerLevel level, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 125 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 126 | 127 | final List holders = holderManager.getChunkHolders(); //We don't need current region because all regions are killed right now 128 | 129 | final DecimalFormat format = new DecimalFormat("#0.00"); 130 | 131 | int saved = 0; 132 | 133 | final long start = System.nanoTime(); 134 | long lastLog = start; 135 | boolean needsFlush = false; 136 | final int flushInterval = 50; 137 | 138 | int savedChunk = 0; 139 | int savedEntity = 0; 140 | int savedPoi = 0; 141 | 142 | for (int i = 0, len = holders.size(); i < len; ++i) { 143 | final NewChunkHolder holder = holders.get(i); 144 | if (!RegionizedServer.isGlobalTickThread()) { 145 | continue; 146 | } 147 | try { 148 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); 149 | if (saveStat != null) { 150 | ++saved; 151 | needsFlush = flush; 152 | if (saveStat.savedChunk()) { 153 | ++savedChunk; 154 | } 155 | if (saveStat.savedEntityChunk()) { 156 | ++savedEntity; 157 | } 158 | if (saveStat.savedPoiChunk()) { 159 | ++savedPoi; 160 | } 161 | } 162 | } catch (ThreadDeath killSignal) { 163 | throw killSignal; 164 | } catch (final Throwable ex) { 165 | MinecraftServer.LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + level.getWorld().getName() + "'", ex); 166 | } 167 | 168 | if (needsFlush && (saved % flushInterval) == 0) { 169 | needsFlush = false; 170 | RegionFileIOThread.partialFlush(flushInterval / 2); 171 | } 172 | } 173 | 174 | if (last && flush) { 175 | RegionFileIOThread.flush(); 176 | if (level.paperConfig().chunks.flushRegionsOnSave) { 177 | try { 178 | level.chunkSource.chunkMap.regionFileCache.flush(); 179 | } catch (IOException ex) { 180 | MinecraftServer.LOGGER.error("Exception when flushing regions in world {}", level.getWorld().getName(), ex); 181 | } 182 | } 183 | } 184 | } 185 | 186 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 187 | // Paper start - rewrite chunk system - add close param 188 | this.save(level, flush, savingDisabled, false); 189 | } 190 | 191 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 192 | if (!savingDisabled) { 193 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 194 | 195 | level.saveLevelData(); 196 | 197 | if (!close) this.saveAllChunksNoCheck(level, level.chunkTaskScheduler.chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 198 | if (close) this.closeChunkProvider(level, true); 199 | 200 | } else if (close) { 201 | this.closeChunkProvider(level, false); 202 | } 203 | } 204 | 205 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 206 | this.closeChunkHolderManager(handle, handle.chunkTaskScheduler.chunkHolderManager, save, true,true, true, false); 207 | } 208 | 209 | private void removeWorldFromRegionizedServer(ServerLevel level){ 210 | try { 211 | final Class targetClass = RegionizedServer.class; 212 | final Field worldListField = targetClass.getDeclaredField("worlds"); 213 | worldListField.setAccessible(true); 214 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 215 | 216 | worldList.remove(level); 217 | }catch (Exception e){ 218 | throw new RuntimeException(e); 219 | } 220 | } 221 | 222 | @Override 223 | public boolean unloadWorld(@NotNull World world, boolean save) { 224 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 225 | 226 | if (world == null) { 227 | return false; 228 | } 229 | 230 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 231 | final MinecraftServer console = craftServer.getServer(); 232 | ServerLevel handle = ((CraftWorld) world).getHandle(); 233 | 234 | if (console.getLevel(handle.dimension()) == null) { 235 | return false; 236 | } 237 | 238 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 239 | return false; 240 | } 241 | 242 | if (!handle.players().isEmpty()) { 243 | return false; 244 | } 245 | 246 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 247 | Bukkit.getPluginManager().callEvent(e); 248 | 249 | if (e.isCancelled()) { 250 | return false; 251 | } 252 | 253 | try { 254 | this.removeWorldFromRegionizedServer(handle); 255 | this.killAllThreadedRegionsOnce(handle); 256 | 257 | if (save) { 258 | this.save(handle, true, false); 259 | } 260 | 261 | this.closeChunkProvider(handle, save); 262 | handle.convertable.close(); 263 | } catch (Exception ex) { 264 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 265 | } 266 | 267 | final Map worlds; 268 | 269 | //Ugly reflection :( 270 | try { 271 | final Class craftServerClass = CraftServer.class; 272 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 273 | worldsField.setAccessible(true); 274 | worlds = ((Map) worldsField.get(craftServer)); 275 | }catch (Exception ex){ 276 | throw new RuntimeException(ex); 277 | } 278 | 279 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 280 | console.removeLevel(handle); 281 | return true; 282 | } 283 | 284 | 285 | @Override 286 | public boolean unloadWorld(@NotNull String name, boolean save) { 287 | return this.unloadWorld(Bukkit.getWorld(name),save); 288 | } 289 | 290 | @Override 291 | public World createWorld(@NotNull WorldCreator creator) { 292 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 293 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 294 | DedicatedServer console = craftServer.getServer(); 295 | 296 | String name = creator.name(); 297 | 298 | String levelName = console.getProperties().levelName; 299 | if (name.equals(levelName) 300 | || (console.isNetherEnabled() && name.equals(levelName + "_nether")) 301 | || (craftServer.getAllowEnd() && name.equals(levelName + "_the_end")) 302 | ) { 303 | return null; 304 | } 305 | 306 | ChunkGenerator generator = creator.generator(); 307 | BiomeProvider biomeProvider = creator.biomeProvider(); 308 | File folder = new File(craftServer.getWorldContainer(), name); 309 | org.bukkit.World world = craftServer.getWorld(name); 310 | 311 | CraftWorld worldByKey = (CraftWorld) craftServer.getWorld(creator.key()); 312 | if (world != null || worldByKey != null) { 313 | if (world != worldByKey) { 314 | return world; 315 | } 316 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 317 | } 318 | 319 | if (folder.exists()) { 320 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 321 | } 322 | 323 | if (generator == null) { 324 | generator = craftServer.getGenerator(name); 325 | } 326 | 327 | if (biomeProvider == null) { 328 | biomeProvider = craftServer.getBiomeProvider(name); 329 | } 330 | 331 | ResourceKey actualDimension = switch (creator.environment()) { 332 | case NORMAL -> LevelStem.OVERWORLD; 333 | case NETHER -> LevelStem.NETHER; 334 | case THE_END -> LevelStem.END; 335 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 336 | }; 337 | 338 | LevelStorageSource.LevelStorageAccess worldSession; 339 | try { 340 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).createAccess(name, actualDimension); 341 | } catch (IOException ex) { 342 | throw new RuntimeException(ex); 343 | } 344 | 345 | boolean hardcore = creator.hardcore(); 346 | 347 | PrimaryLevelData worlddata; 348 | WorldLoader.DataLoadContext worldloader_a = console.worldLoader; 349 | net.minecraft.core.Registry iregistry = worldloader_a.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); 350 | DynamicOps dynamicops = RegistryOps.create(NbtOps.INSTANCE, worldloader_a.datapackWorldgen()); 351 | Pair pair = worldSession.getDataTag(dynamicops, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 352 | 353 | 354 | if (pair != null) { 355 | worlddata = (PrimaryLevelData) pair.getFirst(); 356 | iregistry = pair.getSecond().dimensions(); 357 | } else { 358 | LevelSettings worldsettings; 359 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 360 | WorldDimensions worlddimensions; 361 | 362 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 363 | 364 | worldsettings = new LevelSettings(name, GameType.byId(Bukkit.getServer().getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldloader_a.dataConfiguration()); 365 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 366 | 367 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 368 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 369 | 370 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 371 | iregistry = worlddimensions_b.dimensions(); 372 | } 373 | worlddata.customDimensions = iregistry; 374 | worlddata.checkName(name); 375 | worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); 376 | 377 | long j = BiomeManager.obfuscateSeed(creator.seed()); 378 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 379 | LevelStem worlddimension = iregistry.get(actualDimension); 380 | 381 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper 382 | if (biomeProvider == null && generator != null) { 383 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 384 | } 385 | 386 | if (console.options.has("forceUpgrade")) { 387 | net.minecraft.server.Main.convertWorldButItWorks( 388 | actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), console.options.has("eraseCache") 389 | ); 390 | } 391 | 392 | ResourceKey worldKey; 393 | worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper 394 | 395 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 396 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 397 | 398 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 399 | int loadRegionRadius = ((32) >> 4); 400 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 401 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 402 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 403 | internal.chunkSource.addTicketAtLevel( 404 | TicketType.UNKNOWN, pos, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, pos 405 | ); 406 | } 407 | } 408 | 409 | console.addLevel(internal); 410 | 411 | internal.setSpawnSettings(true, true); 412 | 413 | internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper 414 | 415 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 416 | 417 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 418 | 419 | return internal.getWorld(); 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /nms/NMS_V1204/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.1" 3 | } 4 | 5 | dependencies { 6 | paperweight.foliaDevBundle("1.20.4-R0.1-SNAPSHOT") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1204/src/main/java/i/mrhua269/zutils/nms/v1_20_4/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_20_4.impl; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import com.mojang.serialization.Dynamic; 6 | import com.mojang.serialization.Lifecycle; 7 | import i.mrhua269.zutils.api.WorldManager; 8 | import io.papermc.paper.chunk.system.io.RegionFileIOThread; 9 | import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; 10 | import io.papermc.paper.chunk.system.scheduling.NewChunkHolder; 11 | import io.papermc.paper.threadedregions.RegionizedServer; 12 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 13 | import net.minecraft.core.registries.Registries; 14 | import net.minecraft.nbt.NbtException; 15 | import net.minecraft.nbt.ReportedNbtException; 16 | import net.minecraft.resources.ResourceKey; 17 | import net.minecraft.server.MinecraftServer; 18 | import net.minecraft.server.WorldLoader; 19 | import net.minecraft.server.dedicated.DedicatedServer; 20 | import net.minecraft.server.dedicated.DedicatedServerProperties; 21 | import net.minecraft.server.level.ServerLevel; 22 | import net.minecraft.server.level.TicketType; 23 | import net.minecraft.util.GsonHelper; 24 | import net.minecraft.util.datafix.DataFixers; 25 | import net.minecraft.world.Difficulty; 26 | import net.minecraft.world.entity.ai.village.VillageSiege; 27 | import net.minecraft.world.entity.npc.CatSpawner; 28 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 29 | import net.minecraft.world.level.*; 30 | import net.minecraft.world.level.biome.BiomeManager; 31 | import net.minecraft.world.level.dimension.LevelStem; 32 | import net.minecraft.world.level.levelgen.PatrolSpawner; 33 | import net.minecraft.world.level.levelgen.PhantomSpawner; 34 | import net.minecraft.world.level.levelgen.WorldDimensions; 35 | import net.minecraft.world.level.levelgen.WorldOptions; 36 | import net.minecraft.world.level.storage.LevelDataAndDimensions; 37 | import net.minecraft.world.level.storage.LevelStorageSource; 38 | import net.minecraft.world.level.storage.PrimaryLevelData; 39 | import org.bukkit.Bukkit; 40 | import org.bukkit.World; 41 | import org.bukkit.WorldCreator; 42 | import org.bukkit.event.world.WorldLoadEvent; 43 | import org.bukkit.event.world.WorldUnloadEvent; 44 | import org.bukkit.generator.BiomeProvider; 45 | import org.bukkit.generator.ChunkGenerator; 46 | import org.bukkit.generator.WorldInfo; 47 | import org.jetbrains.annotations.NotNull; 48 | import org.bukkit.craftbukkit.v1_20_R3.CraftServer; 49 | import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; 50 | import org.bukkit.craftbukkit.v1_20_R3.generator.CraftWorldInfo; 51 | 52 | import java.io.File; 53 | import java.io.IOException; 54 | import java.lang.reflect.Field; 55 | import java.lang.reflect.Method; 56 | import java.text.DecimalFormat; 57 | import java.util.List; 58 | import java.util.Locale; 59 | import java.util.Map; 60 | import java.util.concurrent.TimeUnit; 61 | 62 | public class FoliaWorldManagerImpl implements WorldManager { 63 | 64 | //TODO Did we ACTUALLY kill the region? 65 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 66 | level.regioniser.computeForAllRegions(region -> { 67 | for (;;) { 68 | boolean result; 69 | 70 | try { 71 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 72 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 73 | tryKillMethod.setAccessible(true); 74 | result = (boolean) tryKillMethod.invoke(region); 75 | }catch (Exception e){ 76 | break; 77 | } 78 | 79 | if (result) { 80 | break; 81 | } 82 | } 83 | }); 84 | } 85 | 86 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 87 | if (first && halt) { 88 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 89 | if (!world.chunkTaskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { 90 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 91 | } else { 92 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 93 | } 94 | } 95 | 96 | if (save) { 97 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); // Folia - region threading 98 | } 99 | 100 | if (last) { 101 | if (world.chunkDataControllerNew.hasTasks() || world.entityDataControllerNew.hasTasks() || world.poiDataControllerNew.hasTasks()) { 102 | RegionFileIOThread.flush(); 103 | } 104 | 105 | try { 106 | world.chunkDataControllerNew.getCache().close(); 107 | } catch (final IOException ex) { 108 | MinecraftServer.LOGGER.error("Failed to close chunk regionfile cache for world '" + world.getWorld().getName() + "'", ex); 109 | } 110 | try { 111 | world.entityDataControllerNew.getCache().close(); 112 | } catch (final IOException ex) { 113 | MinecraftServer.LOGGER.error("Failed to close entity regionfile cache for world '" + world.getWorld().getName() + "'", ex); 114 | } 115 | try { 116 | world.poiDataControllerNew.getCache().close(); 117 | } catch (final IOException ex) { 118 | MinecraftServer.LOGGER.error("Failed to close poi regionfile cache for world '" + world.getWorld().getName() + "'", ex); 119 | } 120 | } 121 | } 122 | 123 | 124 | private void saveAllChunksNoCheck(ServerLevel level, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 125 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 126 | 127 | final List holders = holderManager.getChunkHolders(); //We don't need current region because all regions are killed right now 128 | 129 | final DecimalFormat format = new DecimalFormat("#0.00"); 130 | 131 | int saved = 0; 132 | 133 | final long start = System.nanoTime(); 134 | long lastLog = start; 135 | boolean needsFlush = false; 136 | final int flushInterval = 50; 137 | 138 | int savedChunk = 0; 139 | int savedEntity = 0; 140 | int savedPoi = 0; 141 | 142 | for (int i = 0, len = holders.size(); i < len; ++i) { 143 | final NewChunkHolder holder = holders.get(i); 144 | if (!RegionizedServer.isGlobalTickThread()) { 145 | continue; 146 | } 147 | try { 148 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); 149 | if (saveStat != null) { 150 | ++saved; 151 | needsFlush = flush; 152 | if (saveStat.savedChunk()) { 153 | ++savedChunk; 154 | } 155 | if (saveStat.savedEntityChunk()) { 156 | ++savedEntity; 157 | } 158 | if (saveStat.savedPoiChunk()) { 159 | ++savedPoi; 160 | } 161 | } 162 | } catch (ThreadDeath killSignal) { 163 | throw killSignal; 164 | } catch (final Throwable ex) { 165 | MinecraftServer.LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + level.getWorld().getName() + "'", ex); 166 | } 167 | 168 | if (needsFlush && (saved % flushInterval) == 0) { 169 | needsFlush = false; 170 | RegionFileIOThread.partialFlush(flushInterval / 2); 171 | } 172 | } 173 | 174 | if (last && flush) { 175 | RegionFileIOThread.flush(); 176 | if (level.paperConfig().chunks.flushRegionsOnSave) { 177 | try { 178 | level.chunkSource.chunkMap.regionFileCache.flush(); 179 | } catch (IOException ex) { 180 | MinecraftServer.LOGGER.error("Exception when flushing regions in world {}", level.getWorld().getName(), ex); 181 | } 182 | } 183 | } 184 | } 185 | 186 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 187 | // Paper start - rewrite chunk system - add close param 188 | this.save(level, flush, savingDisabled, false); 189 | } 190 | 191 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 192 | if (!savingDisabled) { 193 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 194 | 195 | level.saveLevelData(!close); 196 | 197 | if (!close) this.saveAllChunksNoCheck(level, level.chunkTaskScheduler.chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 198 | if (close) this.closeChunkProvider(level, true); 199 | 200 | } else if (close) { 201 | this.closeChunkProvider(level, false); 202 | } 203 | } 204 | 205 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 206 | this.closeChunkHolderManager(handle, handle.chunkTaskScheduler.chunkHolderManager, save, true,true, true, false); 207 | try { 208 | handle.chunkSource.getDataStorage().close(); 209 | } catch (IOException exception) { 210 | MinecraftServer.LOGGER.error("Failed to close persistent world data", exception); 211 | } 212 | } 213 | 214 | private void removeWorldFromRegionizedServer(ServerLevel level){ 215 | try { 216 | final Class targetClass = RegionizedServer.class; 217 | final Field worldListField = targetClass.getDeclaredField("worlds"); 218 | worldListField.setAccessible(true); 219 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 220 | 221 | worldList.remove(level); 222 | }catch (Exception e){ 223 | throw new RuntimeException(e); 224 | } 225 | } 226 | 227 | @Override 228 | public boolean unloadWorld(@NotNull World world, boolean save) { 229 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 230 | 231 | if (world == null) { 232 | return false; 233 | } 234 | 235 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 236 | final MinecraftServer console = craftServer.getServer(); 237 | ServerLevel handle = ((CraftWorld) world).getHandle(); 238 | 239 | if (console.getLevel(handle.dimension()) == null) { 240 | return false; 241 | } 242 | 243 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 244 | return false; 245 | } 246 | 247 | if (!handle.players().isEmpty()) { 248 | return false; 249 | } 250 | 251 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 252 | Bukkit.getPluginManager().callEvent(e); 253 | 254 | if (e.isCancelled()) { 255 | return false; 256 | } 257 | 258 | try { 259 | this.removeWorldFromRegionizedServer(handle); 260 | this.killAllThreadedRegionsOnce(handle); 261 | 262 | if (save) { 263 | this.save(handle, true, false); 264 | } 265 | 266 | this.closeChunkProvider(handle, save); 267 | handle.convertable.close(); 268 | } catch (Exception ex) { 269 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 270 | } 271 | 272 | final Map worlds; 273 | 274 | //Ugly reflection :( 275 | try { 276 | final Class craftServerClass = CraftServer.class; 277 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 278 | worldsField.setAccessible(true); 279 | worlds = ((Map) worldsField.get(craftServer)); 280 | }catch (Exception ex){ 281 | throw new RuntimeException(ex); 282 | } 283 | 284 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 285 | console.removeLevel(handle); 286 | return true; 287 | } 288 | 289 | @Override 290 | public boolean unloadWorld(@NotNull String name, boolean save) { 291 | return this.unloadWorld(Bukkit.getWorld(name),save); 292 | } 293 | 294 | @Override 295 | public World createWorld(@NotNull WorldCreator creator) { 296 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 297 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 298 | DedicatedServer console = craftServer.getServer(); 299 | 300 | String name = creator.name(); 301 | 302 | String levelName = console.getProperties().levelName; 303 | if (name.equals(levelName) 304 | || (console.isNetherEnabled() && name.equals(levelName + "_nether")) 305 | || (craftServer.getAllowEnd() && name.equals(levelName + "_the_end")) 306 | ) { 307 | return null; 308 | } 309 | 310 | ChunkGenerator generator = creator.generator(); 311 | BiomeProvider biomeProvider = creator.biomeProvider(); 312 | File folder = new File(craftServer.getWorldContainer(), name); 313 | org.bukkit.World world = craftServer.getWorld(name); 314 | 315 | CraftWorld worldByKey = (CraftWorld) craftServer.getWorld(creator.key()); 316 | if (world != null || worldByKey != null) { 317 | if (world != worldByKey) { 318 | return world; 319 | } 320 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 321 | } 322 | 323 | if (folder.exists()) { 324 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 325 | } 326 | 327 | if (generator == null) { 328 | generator = craftServer.getGenerator(name); 329 | } 330 | 331 | if (biomeProvider == null) { 332 | biomeProvider = craftServer.getBiomeProvider(name); 333 | } 334 | 335 | ResourceKey actualDimension = switch (creator.environment()) { 336 | case NORMAL -> LevelStem.OVERWORLD; 337 | case NETHER -> LevelStem.NETHER; 338 | case THE_END -> LevelStem.END; 339 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 340 | }; 341 | 342 | LevelStorageSource.LevelStorageAccess worldSession; 343 | try { 344 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).createAccess(name, actualDimension); 345 | } catch (IOException ex) { 346 | throw new RuntimeException(ex); 347 | } 348 | 349 | Dynamic dynamic; 350 | if (worldSession.hasWorldData()) { 351 | net.minecraft.world.level.storage.LevelSummary worldinfo; 352 | 353 | try { 354 | dynamic = worldSession.getDataTag(); 355 | worldinfo = worldSession.getSummary(dynamic); 356 | } catch (NbtException | ReportedNbtException | IOException ioexception) { 357 | LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory(); 358 | 359 | MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception); 360 | MinecraftServer.LOGGER.info("Attempting to use fallback"); 361 | 362 | try { 363 | dynamic = worldSession.getDataTagFallback(); 364 | worldinfo = worldSession.getSummary(dynamic); 365 | } catch (NbtException | ReportedNbtException | IOException ioexception1) { 366 | MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1); 367 | MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile()); 368 | return null; 369 | } 370 | 371 | worldSession.restoreLevelDataFromOld(); 372 | } 373 | 374 | if (worldinfo.requiresManualConversion()) { 375 | MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted"); 376 | return null; 377 | } 378 | 379 | if (!worldinfo.isCompatible()) { 380 | MinecraftServer.LOGGER.info("This world was created by an incompatible version."); 381 | return null; 382 | } 383 | } else { 384 | dynamic = null; 385 | } 386 | 387 | boolean hardcore = creator.hardcore(); 388 | 389 | PrimaryLevelData worlddata; 390 | WorldLoader.DataLoadContext worldloader_a = console.worldLoader; 391 | net.minecraft.core.Registry iregistry = worldloader_a.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); 392 | if (dynamic != null) { 393 | LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen()); 394 | 395 | worlddata = (PrimaryLevelData) leveldataanddimensions.worldData(); 396 | iregistry = leveldataanddimensions.dimensions().dimensions(); 397 | } else { 398 | LevelSettings worldsettings; 399 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 400 | WorldDimensions worlddimensions; 401 | 402 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 403 | 404 | worldsettings = new LevelSettings(name, GameType.byId(craftServer.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldloader_a.dataConfiguration()); 405 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 406 | 407 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 408 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 409 | 410 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 411 | iregistry = worlddimensions_b.dimensions(); 412 | } 413 | 414 | worlddata.customDimensions = iregistry; 415 | worlddata.checkName(name); 416 | worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); 417 | 418 | long j = BiomeManager.obfuscateSeed(creator.seed()); 419 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 420 | LevelStem worlddimension = iregistry.get(actualDimension); 421 | 422 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper 423 | if (biomeProvider == null && generator != null) { 424 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 425 | } 426 | 427 | if (console.options.has("forceUpgrade")) { 428 | net.minecraft.server.Main.convertWorldButItWorks( 429 | actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), console.options.has("eraseCache") 430 | ); 431 | } 432 | 433 | ResourceKey worldKey; 434 | worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper 435 | 436 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 437 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 438 | 439 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 440 | int loadRegionRadius = ((32) >> 4); 441 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 442 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 443 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 444 | internal.chunkSource.addTicketAtLevel( 445 | TicketType.UNKNOWN, pos, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, pos 446 | ); 447 | } 448 | } 449 | 450 | console.addLevel(internal); 451 | 452 | internal.setSpawnSettings(true, true); 453 | 454 | internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper 455 | 456 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 457 | 458 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 459 | 460 | return internal.getWorld(); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /nms/NMS_V1206/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.5" 3 | } 4 | 5 | dependencies { 6 | paperweight.foliaDevBundle("1.20.6-R0.1-SNAPSHOT") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1206/src/main/java/i/mrhua269/zutils/nms/v1_20_6/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_20_6.impl; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import com.mojang.serialization.Dynamic; 6 | import com.mojang.serialization.Lifecycle; 7 | import i.mrhua269.zutils.api.WorldManager; 8 | import io.papermc.paper.chunk.system.io.RegionFileIOThread; 9 | import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; 10 | import io.papermc.paper.chunk.system.scheduling.NewChunkHolder; 11 | import io.papermc.paper.threadedregions.RegionizedServer; 12 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 13 | import io.papermc.paper.util.TickThread; 14 | import net.minecraft.core.RegistryAccess; 15 | import net.minecraft.core.registries.Registries; 16 | import net.minecraft.nbt.NbtException; 17 | import net.minecraft.nbt.ReportedNbtException; 18 | import net.minecraft.resources.ResourceKey; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.server.WorldLoader; 21 | import net.minecraft.server.dedicated.DedicatedServer; 22 | import net.minecraft.server.dedicated.DedicatedServerProperties; 23 | import net.minecraft.server.level.ServerLevel; 24 | import net.minecraft.server.level.TicketType; 25 | import net.minecraft.util.GsonHelper; 26 | import net.minecraft.util.datafix.DataFixers; 27 | import net.minecraft.world.Difficulty; 28 | import net.minecraft.world.entity.ai.village.VillageSiege; 29 | import net.minecraft.world.entity.npc.CatSpawner; 30 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 31 | import net.minecraft.world.level.*; 32 | import net.minecraft.world.level.biome.BiomeManager; 33 | import net.minecraft.world.level.dimension.LevelStem; 34 | import net.minecraft.world.level.levelgen.PatrolSpawner; 35 | import net.minecraft.world.level.levelgen.PhantomSpawner; 36 | import net.minecraft.world.level.levelgen.WorldDimensions; 37 | import net.minecraft.world.level.levelgen.WorldOptions; 38 | import net.minecraft.world.level.storage.DimensionDataStorage; 39 | import net.minecraft.world.level.storage.LevelDataAndDimensions; 40 | import net.minecraft.world.level.storage.LevelStorageSource; 41 | import net.minecraft.world.level.storage.PrimaryLevelData; 42 | import org.bukkit.Bukkit; 43 | import org.bukkit.World; 44 | import org.bukkit.WorldCreator; 45 | import org.bukkit.event.world.WorldLoadEvent; 46 | import org.bukkit.event.world.WorldUnloadEvent; 47 | import org.bukkit.generator.BiomeProvider; 48 | import org.bukkit.generator.ChunkGenerator; 49 | import org.bukkit.generator.WorldInfo; 50 | import org.jetbrains.annotations.NotNull; 51 | 52 | import java.io.File; 53 | import java.io.IOException; 54 | import java.lang.reflect.Field; 55 | import java.lang.reflect.Method; 56 | import java.text.DecimalFormat; 57 | import java.util.List; 58 | import java.util.Locale; 59 | import java.util.Map; 60 | import java.util.concurrent.TimeUnit; 61 | 62 | import org.bukkit.craftbukkit.CraftServer; 63 | import org.bukkit.craftbukkit.CraftWorld; 64 | import org.bukkit.craftbukkit.generator.CraftWorldInfo; 65 | 66 | public class FoliaWorldManagerImpl implements WorldManager { 67 | 68 | //TODO Did we ACTUALLY kill the region? 69 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 70 | level.regioniser.computeForAllRegions(region -> { 71 | for (;;) { 72 | boolean result; 73 | 74 | try { 75 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 76 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 77 | tryKillMethod.setAccessible(true); 78 | result = (boolean) tryKillMethod.invoke(region); 79 | }catch (Exception e){ 80 | break; 81 | } 82 | 83 | if (result) { 84 | break; 85 | } 86 | } 87 | }); 88 | } 89 | 90 | private void saveAllChunksNoCheck(ServerLevel level, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 91 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 92 | 93 | final List holders = holderManager.getChunkHolders(); //We don't need current region because all regions are killed right now 94 | 95 | int saved = 0; 96 | 97 | boolean needsFlush = false; 98 | final int flushInterval = 50; 99 | 100 | for (int i = 0, len = holders.size(); i < len; ++i) { 101 | final NewChunkHolder holder = holders.get(i); 102 | if (!RegionizedServer.isGlobalTickThread()) { 103 | continue; 104 | } 105 | try { 106 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); 107 | if (saveStat != null) { 108 | ++saved; 109 | needsFlush = flush; 110 | if (saveStat.savedChunk()) { 111 | } 112 | if (saveStat.savedEntityChunk()) { 113 | } 114 | if (saveStat.savedPoiChunk()) { 115 | } 116 | } 117 | } catch (ThreadDeath killSignal) { 118 | throw killSignal; 119 | } catch (final Throwable ex) { 120 | MinecraftServer.LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + level.getWorld().getName() + "'", ex); 121 | } 122 | 123 | if (needsFlush && (saved % flushInterval) == 0) { 124 | needsFlush = false; 125 | RegionFileIOThread.partialFlush(flushInterval / 2); 126 | } 127 | } 128 | 129 | if (last && flush) { 130 | RegionFileIOThread.flush(); 131 | if (level.paperConfig().chunks.flushRegionsOnSave) { 132 | try { 133 | level.chunkSource.chunkMap.regionFileCache.flush(); 134 | } catch (IOException ex) { 135 | MinecraftServer.LOGGER.error("Exception when flushing regions in world {}", level.getWorld().getName(), ex); 136 | } 137 | } 138 | } 139 | } 140 | 141 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 142 | // Paper start - rewrite chunk system - add close param 143 | this.save(level, flush, savingDisabled, false); 144 | } 145 | 146 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 147 | if (!savingDisabled) { 148 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 149 | 150 | level.saveLevelData(!close); 151 | 152 | if (!close) this.saveAllChunksNoCheck(level, level.chunkTaskScheduler.chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 153 | if (close) this.closeChunkProvider(level, true); 154 | 155 | } else if (close) { 156 | this.closeChunkProvider(level, false); 157 | } 158 | } 159 | 160 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 161 | this.closeChunkHolderManager(handle, handle.chunkTaskScheduler.chunkHolderManager, save, true,true, true, false); 162 | try { 163 | handle.chunkSource.getDataStorage().close(); 164 | } catch (IOException exception) { 165 | MinecraftServer.LOGGER.error("Failed to close persistent world data", exception); 166 | } 167 | } 168 | 169 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 170 | if (first && halt) { 171 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 172 | if (!world.chunkTaskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { 173 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 174 | } else { 175 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 176 | } 177 | } 178 | 179 | if (save) { 180 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); // Folia - region threading 181 | } 182 | 183 | if (last) { 184 | if (world.chunkDataControllerNew.hasTasks() || world.entityDataControllerNew.hasTasks() || world.poiDataControllerNew.hasTasks()) { 185 | RegionFileIOThread.flush(); 186 | } 187 | 188 | try { 189 | world.chunkDataControllerNew.getCache().close(); 190 | } catch (final IOException ex) { 191 | MinecraftServer.LOGGER.error("Failed to close chunk regionfile cache for world '" + world.getWorld().getName() + "'", ex); 192 | } 193 | try { 194 | world.entityDataControllerNew.getCache().close(); 195 | } catch (final IOException ex) { 196 | MinecraftServer.LOGGER.error("Failed to close entity regionfile cache for world '" + world.getWorld().getName() + "'", ex); 197 | } 198 | try { 199 | world.poiDataControllerNew.getCache().close(); 200 | } catch (final IOException ex) { 201 | MinecraftServer.LOGGER.error("Failed to close poi regionfile cache for world '" + world.getWorld().getName() + "'", ex); 202 | } 203 | } 204 | } 205 | 206 | 207 | private void removeWorldFromRegionizedServer(ServerLevel level){ 208 | try { 209 | final Class targetClass = RegionizedServer.class; 210 | final Field worldListField = targetClass.getDeclaredField("worlds"); 211 | worldListField.setAccessible(true); 212 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 213 | 214 | worldList.remove(level); 215 | }catch (Exception e){ 216 | throw new RuntimeException(e); 217 | } 218 | } 219 | 220 | @Override 221 | public boolean unloadWorld(@NotNull World world, boolean save) { 222 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 223 | 224 | if (world == null) { 225 | return false; 226 | } 227 | 228 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 229 | final MinecraftServer console = craftServer.getServer(); 230 | ServerLevel handle = ((CraftWorld) world).getHandle(); 231 | 232 | if (console.getLevel(handle.dimension()) == null) { 233 | return false; 234 | } 235 | 236 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 237 | return false; 238 | } 239 | 240 | if (!handle.players().isEmpty()) { 241 | return false; 242 | } 243 | 244 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 245 | Bukkit.getPluginManager().callEvent(e); 246 | 247 | if (e.isCancelled()) { 248 | return false; 249 | } 250 | 251 | try { 252 | this.removeWorldFromRegionizedServer(handle); 253 | this.killAllThreadedRegionsOnce(handle); 254 | 255 | if (save) { 256 | this.save(handle, true, false); 257 | } 258 | 259 | this.closeChunkProvider(handle, save); 260 | handle.convertable.close(); 261 | } catch (Exception ex) { 262 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 263 | } 264 | 265 | final Map worlds; 266 | 267 | //Ugly reflection :( 268 | try { 269 | final Class craftServerClass = CraftServer.class; 270 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 271 | worldsField.setAccessible(true); 272 | worlds = ((Map) worldsField.get(craftServer)); 273 | }catch (Exception ex){ 274 | throw new RuntimeException(ex); 275 | } 276 | 277 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 278 | console.removeLevel(handle); 279 | return true; 280 | } 281 | 282 | @Override 283 | public boolean unloadWorld(@NotNull String name, boolean save) { 284 | return this.unloadWorld(Bukkit.getWorld(name),save); 285 | } 286 | 287 | @Override 288 | public World createWorld(@NotNull WorldCreator creator) { 289 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 290 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 291 | DedicatedServer console = craftServer.getServer(); 292 | 293 | String name = creator.name(); 294 | 295 | String levelName = console.getProperties().levelName; 296 | if (name.equals(levelName) 297 | || (console.isNetherEnabled() && name.equals(levelName + "_nether")) 298 | || (craftServer.getAllowEnd() && name.equals(levelName + "_the_end")) 299 | ) { 300 | return null; 301 | } 302 | 303 | ChunkGenerator generator = creator.generator(); 304 | BiomeProvider biomeProvider = creator.biomeProvider(); 305 | File folder = new File(craftServer.getWorldContainer(), name); 306 | org.bukkit.World world = craftServer.getWorld(name); 307 | 308 | CraftWorld worldByKey = (CraftWorld) craftServer.getWorld(creator.key()); 309 | if (world != null || worldByKey != null) { 310 | if (world != worldByKey) { 311 | return world; 312 | } 313 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 314 | } 315 | 316 | if (folder.exists()) { 317 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 318 | } 319 | 320 | if (generator == null) { 321 | generator = craftServer.getGenerator(name); 322 | } 323 | 324 | if (biomeProvider == null) { 325 | biomeProvider = craftServer.getBiomeProvider(name); 326 | } 327 | 328 | ResourceKey actualDimension = switch (creator.environment()) { 329 | case NORMAL -> LevelStem.OVERWORLD; 330 | case NETHER -> LevelStem.NETHER; 331 | case THE_END -> LevelStem.END; 332 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 333 | }; 334 | 335 | LevelStorageSource.LevelStorageAccess worldSession; 336 | try { 337 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).createAccess(name, actualDimension); 338 | } catch (IOException ex) { 339 | throw new RuntimeException(ex); 340 | } 341 | 342 | Dynamic dynamic; 343 | if (worldSession.hasWorldData()) { 344 | net.minecraft.world.level.storage.LevelSummary worldinfo; 345 | 346 | try { 347 | dynamic = worldSession.getDataTag(); 348 | worldinfo = worldSession.getSummary(dynamic); 349 | } catch (NbtException | ReportedNbtException | IOException ioexception) { 350 | LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory(); 351 | 352 | MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception); 353 | MinecraftServer.LOGGER.info("Attempting to use fallback"); 354 | 355 | try { 356 | dynamic = worldSession.getDataTagFallback(); 357 | worldinfo = worldSession.getSummary(dynamic); 358 | } catch (NbtException | ReportedNbtException | IOException ioexception1) { 359 | MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1); 360 | MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile()); 361 | return null; 362 | } 363 | 364 | worldSession.restoreLevelDataFromOld(); 365 | } 366 | 367 | if (worldinfo.requiresManualConversion()) { 368 | MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted"); 369 | return null; 370 | } 371 | 372 | if (!worldinfo.isCompatible()) { 373 | MinecraftServer.LOGGER.info("This world was created by an incompatible version."); 374 | return null; 375 | } 376 | } else { 377 | dynamic = null; 378 | } 379 | 380 | boolean hardcore = creator.hardcore(); 381 | 382 | PrimaryLevelData worlddata; 383 | WorldLoader.DataLoadContext worldloader_a = console.worldLoader; 384 | RegistryAccess.Frozen iregistrycustom_dimension = worldloader_a.datapackDimensions(); 385 | net.minecraft.core.Registry iregistry = iregistrycustom_dimension.registryOrThrow(Registries.LEVEL_STEM); 386 | if (dynamic != null) { 387 | LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen()); 388 | 389 | worlddata = (PrimaryLevelData) leveldataanddimensions.worldData(); 390 | iregistrycustom_dimension = leveldataanddimensions.dimensions().dimensionsRegistryAccess(); 391 | } else { 392 | LevelSettings worldsettings; 393 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 394 | WorldDimensions worlddimensions; 395 | 396 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 397 | 398 | worldsettings = new LevelSettings(name, GameType.byId(craftServer.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldloader_a.dataConfiguration()); 399 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 400 | 401 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 402 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 403 | 404 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 405 | iregistrycustom_dimension = worlddimensions_b.dimensionsRegistryAccess(); 406 | } 407 | iregistry = iregistrycustom_dimension.registryOrThrow(Registries.LEVEL_STEM); 408 | worlddata.customDimensions = iregistry; 409 | worlddata.checkName(name); 410 | worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); 411 | 412 | long j = BiomeManager.obfuscateSeed(creator.seed()); 413 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 414 | LevelStem worlddimension = iregistry.get(actualDimension); 415 | 416 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper 417 | if (biomeProvider == null && generator != null) { 418 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 419 | } 420 | 421 | ResourceKey worldKey; 422 | worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper 423 | 424 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 425 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 426 | 427 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 428 | int loadRegionRadius = ((1024) >> 4); 429 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 430 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 431 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 432 | internal.chunkSource.addTicketAtLevel( 433 | TicketType.UNKNOWN, pos, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, pos 434 | ); 435 | } 436 | } 437 | 438 | console.addLevel(internal); 439 | internal.setSpawnSettings(true, true); 440 | 441 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 442 | 443 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 444 | 445 | return internal.getWorld(); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /nms/NMS_V1211/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.5" 3 | } 4 | 5 | dependencies { 6 | paperweight.devBundle("me.earthme.luminol", "1.21.1-R0.1-20240830.020224-7") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1211/src/main/java/i/mrhua269/zutils/nms/v1_21_1/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_21_1.impl; 2 | 3 | import ca.spottedleaf.moonrise.common.util.CoordinateUtils; 4 | import ca.spottedleaf.moonrise.common.util.WorldUtil; 5 | import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; 6 | import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; 7 | import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; 8 | import com.google.common.base.Preconditions; 9 | import com.google.common.collect.ImmutableList; 10 | import com.mojang.serialization.Dynamic; 11 | import com.mojang.serialization.Lifecycle; 12 | import i.mrhua269.zutils.api.WorldManager; 13 | import io.papermc.paper.threadedregions.RegionizedServer; 14 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 15 | import it.unimi.dsi.fastutil.longs.LongIterator; 16 | import net.minecraft.core.RegistryAccess; 17 | import net.minecraft.core.registries.Registries; 18 | import net.minecraft.nbt.NbtException; 19 | import net.minecraft.nbt.ReportedNbtException; 20 | import net.minecraft.resources.ResourceKey; 21 | import net.minecraft.resources.ResourceLocation; 22 | import net.minecraft.server.MinecraftServer; 23 | import net.minecraft.server.WorldLoader; 24 | import net.minecraft.server.dedicated.DedicatedServer; 25 | import net.minecraft.server.dedicated.DedicatedServerProperties; 26 | import net.minecraft.server.level.ServerLevel; 27 | import net.minecraft.server.level.TicketType; 28 | import net.minecraft.util.GsonHelper; 29 | import net.minecraft.world.Difficulty; 30 | import net.minecraft.world.entity.ai.village.VillageSiege; 31 | import net.minecraft.world.entity.npc.CatSpawner; 32 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 33 | import net.minecraft.world.level.*; 34 | import net.minecraft.world.level.biome.BiomeManager; 35 | import net.minecraft.world.level.dimension.LevelStem; 36 | import net.minecraft.world.level.levelgen.PatrolSpawner; 37 | import net.minecraft.world.level.levelgen.PhantomSpawner; 38 | import net.minecraft.world.level.levelgen.WorldDimensions; 39 | import net.minecraft.world.level.levelgen.WorldOptions; 40 | import net.minecraft.world.level.storage.LevelDataAndDimensions; 41 | import net.minecraft.world.level.storage.LevelStorageSource; 42 | import net.minecraft.world.level.storage.PrimaryLevelData; 43 | import org.bukkit.Bukkit; 44 | import org.bukkit.World; 45 | import org.bukkit.WorldCreator; 46 | import org.bukkit.event.world.WorldLoadEvent; 47 | import org.bukkit.event.world.WorldUnloadEvent; 48 | import org.bukkit.generator.BiomeProvider; 49 | import org.bukkit.generator.ChunkGenerator; 50 | import org.bukkit.generator.WorldInfo; 51 | import org.jetbrains.annotations.NotNull; 52 | 53 | import java.io.File; 54 | import java.io.IOException; 55 | import java.lang.reflect.Field; 56 | import java.lang.reflect.Method; 57 | import java.text.DecimalFormat; 58 | import java.util.List; 59 | import java.util.Locale; 60 | import java.util.Map; 61 | import java.util.concurrent.TimeUnit; 62 | 63 | import org.bukkit.craftbukkit.CraftServer; 64 | import org.bukkit.craftbukkit.CraftWorld; 65 | import org.bukkit.craftbukkit.generator.CraftWorldInfo; 66 | 67 | public class FoliaWorldManagerImpl implements WorldManager { 68 | 69 | //TODO Did we ACTUALLY kill the region? 70 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 71 | level.regioniser.computeForAllRegions(region -> { 72 | for (;;) { 73 | boolean result; 74 | 75 | try { 76 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 77 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 78 | tryKillMethod.setAccessible(true); 79 | result = (boolean) tryKillMethod.invoke(region); 80 | }catch (Exception e){ 81 | break; 82 | } 83 | 84 | if (result) { 85 | break; 86 | } 87 | } 88 | }); 89 | } 90 | 91 | private void saveAllChunksNoCheck(ServerLevel world, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 92 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 93 | 94 | final List holders = new java.util.ArrayList<>(holderManager.getChunkHolders().size() / 10); 95 | // we could iterate through all chunk holders with thread checks, however for many regions the iteration cost alone 96 | // will multiply. to avoid this, we can simply iterate through all owned sections 97 | final int regionShift = world.moonrise$getRegionChunkShift(); 98 | world.regioniser.computeForAllRegions(region -> { 99 | for (final LongIterator iterator = region.getOwnedSectionsUnsynchronised(); iterator.hasNext();) { 100 | final long sectionKey = iterator.nextLong(); 101 | final int width = 1 << regionShift; 102 | final int offsetX = CoordinateUtils.getChunkX(sectionKey) << regionShift; 103 | final int offsetZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift; 104 | 105 | for (int dz = 0; dz < width; ++dz) { 106 | for (int dx = 0; dx < width; ++dx) { 107 | final NewChunkHolder holder = holderManager.getChunkHolder(offsetX | dx, offsetZ | dz); 108 | if (holder != null) { 109 | holders.add(holder); 110 | } 111 | } 112 | } 113 | } 114 | }); 115 | // Folia end - region threading 116 | 117 | if (first && logProgress) { // Folia - region threading 118 | MinecraftServer.LOGGER.info("Saving all chunkholders for world '{}'", WorldUtil.getWorldName(world)); 119 | } 120 | 121 | final DecimalFormat format = new DecimalFormat("#0.00"); 122 | 123 | int saved = 0; 124 | 125 | long start = System.nanoTime(); 126 | long lastLog = start; 127 | boolean needsFlush = false; 128 | final int flushInterval = 50; 129 | 130 | int savedChunk = 0; 131 | int savedEntity = 0; 132 | int savedPoi = 0; 133 | 134 | for (int i = 0, len = holders.size(); i < len; ++i) { 135 | final NewChunkHolder holder = holders.get(i); 136 | try { 137 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown); 138 | if (saveStat != null) { 139 | ++saved; 140 | needsFlush = flush; 141 | if (saveStat.savedChunk()) { 142 | ++savedChunk; 143 | } 144 | if (saveStat.savedEntityChunk()) { 145 | ++savedEntity; 146 | } 147 | if (saveStat.savedPoiChunk()) { 148 | ++savedPoi; 149 | } 150 | } 151 | } catch (final Throwable thr) { 152 | MinecraftServer.LOGGER.error("Failed to save chunk ({},{}) in world '{}'", holder.chunkX, holder.chunkZ, WorldUtil.getWorldName(world), thr); 153 | } 154 | 155 | if (needsFlush && (saved % flushInterval) == 0) { 156 | needsFlush = false; 157 | RegionFileIOThread.partialFlush(flushInterval / 2); 158 | } 159 | 160 | if (logProgress) { 161 | final long currTime = System.nanoTime(); 162 | if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { 163 | lastLog = currTime; 164 | MinecraftServer.LOGGER.info("Saved {} chunks ({}%) in world '{}'", saved, format.format((double) (i + 1) / (double) len * 100.0), WorldUtil.getWorldName(world)); 165 | } 166 | } 167 | } 168 | if (last && flush) { // Folia - region threading 169 | RegionFileIOThread.flush(); 170 | try { 171 | RegionFileIOThread.flushRegionStorages(world); 172 | } catch (final IOException ex) { 173 | MinecraftServer.LOGGER.error("Exception when flushing regions in world '{}'", WorldUtil.getWorldName(world), ex); 174 | } 175 | } 176 | if (logProgress) { 177 | MinecraftServer.LOGGER.info("Saved {} block chunks, {} entity chunks, {} poi chunks in world '{}' in {}s", savedChunk, savedEntity, savedPoi, WorldUtil.getWorldName(world), format.format(1.0E-9 * (System.nanoTime() - start))); 178 | } 179 | } 180 | 181 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 182 | // Paper start - rewrite chunk system - add close param 183 | this.save(level, flush, savingDisabled, false); 184 | } 185 | 186 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 187 | if (!savingDisabled) { 188 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 189 | 190 | level.saveLevelData(!close); 191 | 192 | if (!close) this.saveAllChunksNoCheck(level, level.moonrise$getChunkTaskScheduler().chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 193 | if (close) this.closeChunkProvider(level, true); 194 | 195 | } else if (close) { 196 | this.closeChunkProvider(level, false); 197 | } 198 | } 199 | 200 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 201 | this.closeChunkHolderManager(handle, handle.moonrise$getChunkTaskScheduler().chunkHolderManager, save, true,true, true, false); 202 | try { 203 | handle.chunkSource.getDataStorage().close(); 204 | } catch (IOException exception) { 205 | MinecraftServer.LOGGER.error("Failed to close persistent world data", exception); 206 | } 207 | } 208 | 209 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 210 | if (first && halt) { 211 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 212 | if (!world.moonrise$getChunkTaskScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) { 213 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 214 | } else { 215 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 216 | } 217 | } 218 | 219 | if (save) { 220 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); // Folia - region threading 221 | } 222 | 223 | if (last) { // Folia - region threading 224 | boolean hasTasks = false; 225 | 226 | for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { 227 | if (RegionFileIOThread.getControllerFor(world, type).hasTasks()) { 228 | hasTasks = true; 229 | break; 230 | } 231 | } 232 | 233 | if (hasTasks) { 234 | RegionFileIOThread.flush(); 235 | } 236 | 237 | // kill regionfile cache 238 | for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { 239 | try { 240 | RegionFileIOThread.getControllerFor(world, type).getCache().close(); 241 | } catch (final IOException ex) { 242 | MinecraftServer.LOGGER.error("Failed to close '" + type.name() + "' regionfile cache for world '" + WorldUtil.getWorldName(world) + "'", ex); 243 | } 244 | } 245 | } // Folia - region threading 246 | } 247 | 248 | 249 | private void removeWorldFromRegionizedServer(ServerLevel level){ 250 | try { 251 | final Class targetClass = RegionizedServer.class; 252 | final Field worldListField = targetClass.getDeclaredField("worlds"); 253 | worldListField.setAccessible(true); 254 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 255 | 256 | worldList.remove(level); 257 | }catch (Exception e){ 258 | throw new RuntimeException(e); 259 | } 260 | } 261 | 262 | @Override 263 | public boolean unloadWorld(@NotNull World world, boolean save) { 264 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 265 | 266 | if (world == null) { 267 | return false; 268 | } 269 | 270 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 271 | final MinecraftServer console = craftServer.getServer(); 272 | ServerLevel handle = ((CraftWorld) world).getHandle(); 273 | 274 | if (console.getLevel(handle.dimension()) == null) { 275 | return false; 276 | } 277 | 278 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 279 | return false; 280 | } 281 | 282 | if (!handle.players().isEmpty()) { 283 | return false; 284 | } 285 | 286 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 287 | Bukkit.getPluginManager().callEvent(e); 288 | 289 | if (e.isCancelled()) { 290 | return false; 291 | } 292 | 293 | try { 294 | this.removeWorldFromRegionizedServer(handle); 295 | this.killAllThreadedRegionsOnce(handle); 296 | 297 | if (save) { 298 | this.save(handle, true, false); 299 | } 300 | 301 | this.closeChunkProvider(handle, save); 302 | handle.convertable.close(); 303 | } catch (Exception ex) { 304 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 305 | } 306 | 307 | final Map worlds; 308 | 309 | //Ugly reflection :( 310 | try { 311 | final Class craftServerClass = CraftServer.class; 312 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 313 | worldsField.setAccessible(true); 314 | worlds = ((Map) worldsField.get(craftServer)); 315 | }catch (Exception ex){ 316 | throw new RuntimeException(ex); 317 | } 318 | 319 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 320 | console.removeLevel(handle); 321 | return true; 322 | } 323 | 324 | @Override 325 | public boolean unloadWorld(@NotNull String name, boolean save) { 326 | return this.unloadWorld(Bukkit.getWorld(name),save); 327 | } 328 | 329 | @Override 330 | public World createWorld(@NotNull WorldCreator creator) { 331 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 332 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 333 | DedicatedServer console = craftServer.getServer(); 334 | 335 | String name = creator.name(); 336 | ChunkGenerator generator = creator.generator(); 337 | BiomeProvider biomeProvider = creator.biomeProvider(); 338 | File folder = new File(craftServer.getWorldContainer(), name); 339 | World world = craftServer.getWorld(name); 340 | 341 | // Paper start 342 | World worldByKey = craftServer.getWorld(creator.key()); 343 | if (world != null || worldByKey != null) { 344 | if (world == worldByKey) { 345 | return world; 346 | } 347 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 348 | } 349 | // Paper end 350 | 351 | if (folder.exists()) { 352 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 353 | } 354 | 355 | if (generator == null) { 356 | generator = craftServer.getGenerator(name); 357 | } 358 | 359 | if (biomeProvider == null) { 360 | biomeProvider = craftServer.getBiomeProvider(name); 361 | } 362 | 363 | 364 | ResourceKey actualDimension = switch (creator.environment()) { 365 | case NORMAL -> LevelStem.OVERWORLD; 366 | case NETHER -> LevelStem.NETHER; 367 | case THE_END -> LevelStem.END; 368 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 369 | }; 370 | 371 | LevelStorageSource.LevelStorageAccess worldSession; 372 | try { 373 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).createAccess(name, actualDimension); 374 | } catch (IOException ex) { 375 | throw new RuntimeException(ex); 376 | } 377 | 378 | Dynamic dynamic; 379 | if (worldSession.hasWorldData()) { 380 | net.minecraft.world.level.storage.LevelSummary worldinfo; 381 | 382 | try { 383 | dynamic = worldSession.getDataTag(); 384 | worldinfo = worldSession.getSummary(dynamic); 385 | } catch (NbtException | ReportedNbtException | IOException ioexception) { 386 | LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory(); 387 | 388 | MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception); 389 | MinecraftServer.LOGGER.info("Attempting to use fallback"); 390 | 391 | try { 392 | dynamic = worldSession.getDataTagFallback(); 393 | worldinfo = worldSession.getSummary(dynamic); 394 | } catch (NbtException | ReportedNbtException | IOException ioexception1) { 395 | MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1); 396 | MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile()); 397 | return null; 398 | } 399 | 400 | worldSession.restoreLevelDataFromOld(); 401 | } 402 | 403 | if (worldinfo.requiresManualConversion()) { 404 | MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted"); 405 | return null; 406 | } 407 | 408 | if (!worldinfo.isCompatible()) { 409 | MinecraftServer.LOGGER.info("This world was created by an incompatible version."); 410 | return null; 411 | } 412 | } else { 413 | dynamic = null; 414 | } 415 | 416 | boolean hardcore = creator.hardcore(); 417 | 418 | PrimaryLevelData worlddata; 419 | WorldLoader.DataLoadContext worldloader_a = console.worldLoader; 420 | RegistryAccess.Frozen iregistrycustom_dimension = worldloader_a.datapackDimensions(); 421 | net.minecraft.core.Registry iregistry = iregistrycustom_dimension.registryOrThrow(Registries.LEVEL_STEM); 422 | if (dynamic != null) { 423 | LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen()); 424 | 425 | worlddata = (PrimaryLevelData) leveldataanddimensions.worldData(); 426 | iregistrycustom_dimension = leveldataanddimensions.dimensions().dimensionsRegistryAccess(); 427 | } else { 428 | LevelSettings worldsettings; 429 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 430 | WorldDimensions worlddimensions; 431 | 432 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 433 | 434 | worldsettings = new LevelSettings(name, GameType.byId(craftServer.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldloader_a.dataConfiguration()); 435 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 436 | 437 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 438 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 439 | 440 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 441 | iregistrycustom_dimension = worlddimensions_b.dimensionsRegistryAccess(); 442 | } 443 | iregistry = iregistrycustom_dimension.registryOrThrow(Registries.LEVEL_STEM); 444 | worlddata.customDimensions = iregistry; 445 | worlddata.checkName(name); 446 | worlddata.setModdedInfo(console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); 447 | 448 | long j = BiomeManager.obfuscateSeed(creator.seed()); 449 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 450 | LevelStem worlddimension = iregistry.get(actualDimension); 451 | 452 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper 453 | if (biomeProvider == null && generator != null) { 454 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 455 | } 456 | 457 | ResourceKey worldKey; 458 | worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper 459 | 460 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 461 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 462 | 463 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 464 | int loadRegionRadius = ((1024) >> 4); 465 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 466 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 467 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 468 | internal.chunkSource.addTicketAtLevel( 469 | TicketType.UNKNOWN, pos, ChunkHolderManager.MAX_TICKET_LEVEL, pos 470 | ); 471 | } 472 | } 473 | 474 | console.addLevel(internal); 475 | internal.setSpawnSettings(true, true); 476 | 477 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 478 | 479 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 480 | 481 | return internal.getWorld(); 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /nms/NMS_V1213/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.papermc.paperweight.userdev") version "1.7.5" 3 | } 4 | 5 | dependencies { 6 | paperweight.devBundle("me.earthme.luminol", "1.21.3-R0.1-20241201.004730-1") 7 | implementation(project(":zutils-based-api")) 8 | } 9 | 10 | configurations.reobf { 11 | outgoing.artifact(layout.buildDirectory.file("libs/${project.name}-${project.version}.jar")) 12 | } -------------------------------------------------------------------------------- /nms/NMS_V1213/src/main/java/i/mrhua269/zutils/nms/v1_21_3/impl/FoliaWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.nms.v1_21_3.impl; 2 | 3 | import ca.spottedleaf.moonrise.common.PlatformHooks; 4 | import ca.spottedleaf.moonrise.common.util.CoordinateUtils; 5 | import ca.spottedleaf.moonrise.common.util.TickThread; 6 | import ca.spottedleaf.moonrise.common.util.WorldUtil; 7 | import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; 8 | import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; 9 | import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; 10 | import com.google.common.base.Preconditions; 11 | import com.google.common.collect.ImmutableList; 12 | import com.mojang.serialization.Dynamic; 13 | import com.mojang.serialization.Lifecycle; 14 | import i.mrhua269.zutils.api.WorldManager; 15 | import io.papermc.paper.threadedregions.RegionizedServer; 16 | import io.papermc.paper.threadedregions.ThreadedRegionizer; 17 | import it.unimi.dsi.fastutil.longs.LongIterator; 18 | import net.minecraft.core.RegistryAccess; 19 | import net.minecraft.core.registries.Registries; 20 | import net.minecraft.nbt.NbtException; 21 | import net.minecraft.nbt.ReportedNbtException; 22 | import net.minecraft.resources.ResourceKey; 23 | import net.minecraft.resources.ResourceLocation; 24 | import net.minecraft.server.MinecraftServer; 25 | import net.minecraft.server.WorldLoader; 26 | import net.minecraft.server.dedicated.DedicatedServer; 27 | import net.minecraft.server.dedicated.DedicatedServerProperties; 28 | import net.minecraft.server.level.ServerLevel; 29 | import net.minecraft.server.level.TicketType; 30 | import net.minecraft.util.GsonHelper; 31 | import net.minecraft.util.datafix.DataFixers; 32 | import net.minecraft.world.Difficulty; 33 | import net.minecraft.world.entity.ai.village.VillageSiege; 34 | import net.minecraft.world.entity.npc.CatSpawner; 35 | import net.minecraft.world.entity.npc.WanderingTraderSpawner; 36 | import net.minecraft.world.level.*; 37 | import net.minecraft.world.level.biome.BiomeManager; 38 | import net.minecraft.world.level.chunk.LevelChunk; 39 | import net.minecraft.world.level.dimension.LevelStem; 40 | import net.minecraft.world.level.levelgen.PatrolSpawner; 41 | import net.minecraft.world.level.levelgen.PhantomSpawner; 42 | import net.minecraft.world.level.levelgen.WorldDimensions; 43 | import net.minecraft.world.level.levelgen.WorldOptions; 44 | import net.minecraft.world.level.storage.LevelDataAndDimensions; 45 | import net.minecraft.world.level.storage.LevelStorageSource; 46 | import net.minecraft.world.level.storage.PrimaryLevelData; 47 | import net.minecraft.world.level.validation.ContentValidationException; 48 | import org.bukkit.Bukkit; 49 | import org.bukkit.World; 50 | import org.bukkit.WorldCreator; 51 | import org.bukkit.event.world.WorldLoadEvent; 52 | import org.bukkit.event.world.WorldUnloadEvent; 53 | import org.bukkit.generator.BiomeProvider; 54 | import org.bukkit.generator.ChunkGenerator; 55 | import org.bukkit.generator.WorldInfo; 56 | import org.jetbrains.annotations.NotNull; 57 | 58 | import java.io.File; 59 | import java.io.IOException; 60 | import java.lang.reflect.Field; 61 | import java.lang.reflect.Method; 62 | import java.text.DecimalFormat; 63 | import java.util.List; 64 | import java.util.Locale; 65 | import java.util.Map; 66 | import java.util.concurrent.TimeUnit; 67 | 68 | import org.bukkit.craftbukkit.CraftServer; 69 | import org.bukkit.craftbukkit.CraftWorld; 70 | import org.bukkit.craftbukkit.generator.CraftWorldInfo; 71 | 72 | public class FoliaWorldManagerImpl implements WorldManager { 73 | 74 | //TODO Did we ACTUALLY kill the region? 75 | private void killAllThreadedRegionsOnce(@NotNull ServerLevel level){ 76 | level.regioniser.computeForAllRegions(region -> { 77 | for (;;) { 78 | boolean result; 79 | 80 | try { 81 | final Class threadedRegionClass = ThreadedRegionizer.ThreadedRegion.class; 82 | final Method tryKillMethod = threadedRegionClass.getDeclaredMethod("tryKill"); 83 | tryKillMethod.setAccessible(true); 84 | result = (boolean) tryKillMethod.invoke(region); 85 | }catch (Exception e){ 86 | break; 87 | } 88 | 89 | if (result) { 90 | break; 91 | } 92 | } 93 | }); 94 | } 95 | 96 | private void saveAllChunksNoCheck(ServerLevel world, @NotNull ChunkHolderManager holderManager, final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last) { 97 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Saving all chunks can be done only on global tick thread"); 98 | 99 | final List holders = new java.util.ArrayList<>(holderManager.getChunkHolders().size() / 10); 100 | // we could iterate through all chunk holders with thread checks, however for many regions the iteration cost alone 101 | // will multiply. to avoid this, we can simply iterate through all owned sections 102 | final int regionShift = world.moonrise$getRegionChunkShift(); 103 | final int width = 1 << regionShift; 104 | world.regioniser.computeForAllRegions(region -> { 105 | for (final LongIterator iterator = region.getOwnedSectionsUnsynchronised(); iterator.hasNext();) { 106 | final long sectionKey = iterator.nextLong(); 107 | final int offsetX = CoordinateUtils.getChunkX(sectionKey) << regionShift; 108 | final int offsetZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift; 109 | 110 | for (int dz = 0; dz < width; ++dz) { 111 | for (int dx = 0; dx < width; ++dx) { 112 | final NewChunkHolder holder = holderManager.getChunkHolder(offsetX | dx, offsetZ | dz); 113 | if (holder != null) { 114 | holders.add(holder); 115 | } 116 | } 117 | } 118 | } 119 | }); 120 | // Folia end - region threading 121 | 122 | if (first && logProgress) { // Folia - region threading 123 | MinecraftServer.LOGGER.info("Saving all chunkholders for world '" + WorldUtil.getWorldName(world) + "'"); 124 | } 125 | 126 | final DecimalFormat format = new DecimalFormat("#0.00"); 127 | 128 | int saved = 0; 129 | 130 | long start = System.nanoTime(); 131 | long lastLog = start; 132 | final int flushInterval = 200; 133 | int lastFlush = 0; 134 | 135 | int savedChunk = 0; 136 | int savedEntity = 0; 137 | int savedPoi = 0; 138 | 139 | if (shutdown) { 140 | // Normal unload process does not occur during shutdown: fire event manually 141 | // for mods that expect ChunkEvent.Unload to fire on shutdown (before LevelEvent.Unload) 142 | for (int i = 0, len = holders.size(); i < len; ++i) { 143 | final NewChunkHolder holder = holders.get(i); 144 | if (holder.getCurrentChunk() instanceof LevelChunk levelChunk) { 145 | PlatformHooks.get().chunkUnloadFromWorld(levelChunk); 146 | } 147 | } 148 | } 149 | for (int i = 0, len = holders.size(); i < len; ++i) { 150 | final NewChunkHolder holder = holders.get(i); 151 | try { 152 | final NewChunkHolder.SaveStat saveStat = holder.save(shutdown); 153 | if (saveStat != null) { 154 | if (saveStat.savedChunk()) { 155 | ++savedChunk; 156 | ++saved; 157 | } 158 | if (saveStat.savedEntityChunk()) { 159 | ++savedEntity; 160 | ++saved; 161 | } 162 | if (saveStat.savedPoiChunk()) { 163 | ++savedPoi; 164 | ++saved; 165 | } 166 | } 167 | } catch (final Throwable thr) { 168 | MinecraftServer.LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + WorldUtil.getWorldName(world) + "'", thr); 169 | } 170 | if (flush && (saved - lastFlush) > (flushInterval / 2)) { 171 | lastFlush = saved; 172 | MoonriseRegionFileIO.partialFlush(world, flushInterval / 2); 173 | } 174 | if (logProgress) { 175 | final long currTime = System.nanoTime(); 176 | if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { 177 | lastLog = currTime; 178 | MinecraftServer.LOGGER.info( 179 | "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi 180 | + " poi chunks in world '" + WorldUtil.getWorldName(world) + "', progress: " 181 | + format.format((double)(i+1)/(double)len * 100.0) 182 | ); 183 | } 184 | } 185 | } 186 | if (last && flush) { // Folia - region threading 187 | MoonriseRegionFileIO.flush(world); 188 | try { 189 | MoonriseRegionFileIO.flushRegionStorages(world); 190 | } catch (final IOException ex) { 191 | MinecraftServer.LOGGER.error("Exception when flushing regions in world '" + WorldUtil.getWorldName(world) + "'", ex); 192 | } 193 | } 194 | if (logProgress) { 195 | MinecraftServer.LOGGER.info( 196 | "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi 197 | + " poi chunks in world '" + WorldUtil.getWorldName(world) + "' in " 198 | + format.format(1.0E-9 * (System.nanoTime() - start)) + "s" 199 | ); 200 | } 201 | } 202 | 203 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled) { 204 | // Paper start - rewrite chunk system - add close param 205 | this.save(level, flush, savingDisabled, false); 206 | } 207 | 208 | public void save(@NotNull ServerLevel level, boolean flush, boolean savingDisabled, boolean close) { 209 | if (!savingDisabled) { 210 | org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(level.getWorld())); // CraftBukkit 211 | 212 | level.saveLevelData(!close); 213 | 214 | if (!close) this.saveAllChunksNoCheck(level, level.moonrise$getChunkTaskScheduler().chunkHolderManager, flush, false, false,true,true); // Paper - rewrite chunk system 215 | if (close) this.closeChunkProvider(level, true); 216 | 217 | } else if (close) { 218 | this.closeChunkProvider(level, false); 219 | } 220 | } 221 | 222 | private void closeChunkProvider(@NotNull ServerLevel handle, boolean save){ 223 | this.closeChunkHolderManager(handle, handle.moonrise$getChunkTaskScheduler().chunkHolderManager, save, true,true, true, false); 224 | try { 225 | handle.chunkSource.getDataStorage().close(); 226 | } catch (Exception exception) { 227 | MinecraftServer.LOGGER.error("Failed to close persistent world data"); 228 | exception.printStackTrace(); 229 | } 230 | } 231 | 232 | public void closeChunkHolderManager(final ServerLevel world, final ChunkHolderManager manager, final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) { 233 | if (first && halt) { 234 | MinecraftServer.LOGGER.info("Waiting 60s for chunk system to halt for world '" + world.getWorld().getName() + "'"); 235 | if (!world.moonrise$getChunkTaskScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) { 236 | MinecraftServer.LOGGER.warn("Failed to halt world generation/loading tasks for world '" + world.getWorld().getName() + "'"); 237 | } else { 238 | MinecraftServer.LOGGER.info("Halted chunk system for world '" + world.getWorld().getName() + "'"); 239 | } 240 | } 241 | 242 | if (save) { 243 | this.saveAllChunksNoCheck(world, manager, true, true, true, first, last); // Folia - region threading 244 | } 245 | 246 | if (last) { // Folia - region threading 247 | MoonriseRegionFileIO.flush(world); 248 | 249 | if (halt) { 250 | MinecraftServer.LOGGER.info("Waiting 60s for chunk I/O to halt for world '" + WorldUtil.getWorldName(world) + "'"); 251 | if (!world.moonrise$getChunkTaskScheduler().haltIO(true, TimeUnit.SECONDS.toNanos(60L))) { 252 | MinecraftServer.LOGGER.warn("Failed to halt I/O tasks for world '" + WorldUtil.getWorldName(world) + "'"); 253 | } else { 254 | MinecraftServer.LOGGER.info("Halted I/O scheduler for world '" + WorldUtil.getWorldName(world) + "'"); 255 | } 256 | } 257 | 258 | // kill regionfile cache 259 | for (final MoonriseRegionFileIO.RegionFileType type : MoonriseRegionFileIO.RegionFileType.values()) { 260 | try { 261 | MoonriseRegionFileIO.getControllerFor(world, type).getCache().close(); 262 | } catch (final IOException ex) { 263 | MinecraftServer.LOGGER.error("Failed to close '" + type.name() + "' regionfile cache for world '" + WorldUtil.getWorldName(world) + "'", ex); 264 | } 265 | } 266 | } // Folia - region threading 267 | } 268 | 269 | 270 | private void removeWorldFromRegionizedServer(ServerLevel level){ 271 | try { 272 | final Class targetClass = RegionizedServer.class; 273 | final Field worldListField = targetClass.getDeclaredField("worlds"); 274 | worldListField.setAccessible(true); 275 | final List worldList = (List) worldListField.get(RegionizedServer.getInstance()); 276 | 277 | worldList.remove(level); 278 | }catch (Exception e){ 279 | throw new RuntimeException(e); 280 | } 281 | } 282 | 283 | @Override 284 | public boolean unloadWorld(@NotNull World world, boolean save) { 285 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World unload can be done only on global tick thread"); 286 | 287 | if (world == null) { 288 | return false; 289 | } 290 | 291 | final CraftServer craftServer = ((CraftServer) Bukkit.getServer()); 292 | final MinecraftServer console = craftServer.getServer(); 293 | ServerLevel handle = ((CraftWorld) world).getHandle(); 294 | 295 | if (console.getLevel(handle.dimension()) == null) { 296 | return false; 297 | } 298 | 299 | if (handle.dimension() == net.minecraft.world.level.Level.OVERWORLD) { 300 | return false; 301 | } 302 | 303 | if (!handle.players().isEmpty()) { 304 | return false; 305 | } 306 | 307 | WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); 308 | Bukkit.getPluginManager().callEvent(e); 309 | 310 | if (e.isCancelled()) { 311 | return false; 312 | } 313 | 314 | try { 315 | this.removeWorldFromRegionizedServer(handle); 316 | this.killAllThreadedRegionsOnce(handle); 317 | 318 | if (save) { 319 | this.save(handle, true, false); 320 | } 321 | 322 | this.closeChunkProvider(handle, save); 323 | handle.convertable.close(); 324 | } catch (Exception ex) { 325 | Bukkit.getLogger().log(java.util.logging.Level.SEVERE, null, ex); 326 | } 327 | 328 | final Map worlds; 329 | 330 | //Ugly reflection :( 331 | try { 332 | final Class craftServerClass = CraftServer.class; 333 | final Field worldsField = craftServerClass.getDeclaredField("worlds"); 334 | worldsField.setAccessible(true); 335 | worlds = ((Map) worldsField.get(craftServer)); 336 | }catch (Exception ex){ 337 | throw new RuntimeException(ex); 338 | } 339 | 340 | worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); 341 | console.removeLevel(handle); 342 | return true; 343 | } 344 | 345 | @Override 346 | public boolean unloadWorld(@NotNull String name, boolean save) { 347 | return this.unloadWorld(Bukkit.getWorld(name),save); 348 | } 349 | 350 | @Override 351 | public World createWorld(@NotNull WorldCreator creator) { 352 | io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("World create can be done only on global tick thread"); 353 | CraftServer craftServer = (CraftServer) Bukkit.getServer(); 354 | DedicatedServer console = craftServer.getServer(); 355 | 356 | String name = creator.name(); 357 | ChunkGenerator generator = creator.generator(); 358 | BiomeProvider biomeProvider = creator.biomeProvider(); 359 | File folder = new File(craftServer.getWorldContainer(), name); 360 | World world = craftServer.getWorld(name); 361 | 362 | // Paper start 363 | World worldByKey = craftServer.getWorld(creator.key()); 364 | if (world != null || worldByKey != null) { 365 | if (world == worldByKey) { 366 | return world; 367 | } 368 | throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); 369 | } 370 | // Paper end 371 | 372 | if (folder.exists()) { 373 | Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); 374 | } 375 | 376 | if (generator == null) { 377 | generator = craftServer.getGenerator(name); 378 | } 379 | 380 | if (biomeProvider == null) { 381 | biomeProvider = craftServer.getBiomeProvider(name); 382 | } 383 | 384 | 385 | ResourceKey actualDimension = switch (creator.environment()) { 386 | case NORMAL -> LevelStem.OVERWORLD; 387 | case NETHER -> LevelStem.NETHER; 388 | case THE_END -> LevelStem.END; 389 | default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); 390 | }; 391 | 392 | LevelStorageSource.LevelStorageAccess worldSession; 393 | try { 394 | worldSession = LevelStorageSource.createDefault(craftServer.getWorldContainer().toPath()).validateAndCreateAccess(name, actualDimension); 395 | } catch (IOException | ContentValidationException ex) { 396 | throw new RuntimeException(ex); 397 | } 398 | 399 | Dynamic dynamic; 400 | if (worldSession.hasWorldData()) { 401 | net.minecraft.world.level.storage.LevelSummary worldinfo; 402 | 403 | try { 404 | dynamic = worldSession.getDataTag(); 405 | worldinfo = worldSession.getSummary(dynamic); 406 | } catch (NbtException | ReportedNbtException | IOException ioexception) { 407 | LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory(); 408 | 409 | MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception); 410 | MinecraftServer.LOGGER.info("Attempting to use fallback"); 411 | 412 | try { 413 | dynamic = worldSession.getDataTagFallback(); 414 | worldinfo = worldSession.getSummary(dynamic); 415 | } catch (NbtException | ReportedNbtException | IOException ioexception1) { 416 | MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1); 417 | MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile()); 418 | return null; 419 | } 420 | 421 | worldSession.restoreLevelDataFromOld(); 422 | } 423 | 424 | if (worldinfo.requiresManualConversion()) { 425 | MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted"); 426 | return null; 427 | } 428 | 429 | if (!worldinfo.isCompatible()) { 430 | MinecraftServer.LOGGER.info("This world was created by an incompatible version."); 431 | return null; 432 | } 433 | } else { 434 | dynamic = null; 435 | } 436 | 437 | boolean hardcore = creator.hardcore(); 438 | 439 | PrimaryLevelData worlddata; 440 | WorldLoader.DataLoadContext worldloader_a = MinecraftServer.getServer().worldLoader; 441 | RegistryAccess.Frozen iregistrycustom_dimension = worldloader_a.datapackDimensions(); 442 | net.minecraft.core.Registry iregistry = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM); 443 | if (dynamic != null) { 444 | LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen()); 445 | 446 | worlddata = (PrimaryLevelData) leveldataanddimensions.worldData(); 447 | iregistrycustom_dimension = leveldataanddimensions.dimensions().dimensionsRegistryAccess(); 448 | } else { 449 | LevelSettings worldsettings; 450 | WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); 451 | WorldDimensions worlddimensions; 452 | 453 | DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); 454 | 455 | worldsettings = new LevelSettings(name, GameType.byId(craftServer.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration()); 456 | worlddimensions = properties.create(worldloader_a.datapackWorldgen()); 457 | 458 | WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); 459 | Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); 460 | 461 | worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); 462 | iregistrycustom_dimension = worlddimensions_b.dimensionsRegistryAccess(); 463 | } 464 | iregistry = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM); 465 | worlddata.customDimensions = iregistry; 466 | worlddata.checkName(name); 467 | worlddata.setModdedInfo(MinecraftServer.getServer().getServerModName(), MinecraftServer.getServer().getModdedStatus().shouldReportAsModified()); 468 | 469 | if (MinecraftServer.getServer().options.has("forceUpgrade")) { 470 | net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), MinecraftServer.getServer().options.has("eraseCache"), () -> true, iregistrycustom_dimension, MinecraftServer.getServer().options.has("recreateRegionFiles")); 471 | } 472 | 473 | long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed 474 | List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); 475 | LevelStem worlddimension = iregistry.getValue(actualDimension); 476 | 477 | WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), craftServer.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo 478 | if (biomeProvider == null && generator != null) { 479 | biomeProvider = generator.getDefaultBiomeProvider(worldInfo); 480 | } 481 | 482 | ResourceKey worldKey; 483 | String levelName = craftServer.getServer().getProperties().levelName; 484 | if (name.equals(levelName + "_nether")) { 485 | worldKey = net.minecraft.world.level.Level.NETHER; 486 | } else if (name.equals(levelName + "_the_end")) { 487 | worldKey = net.minecraft.world.level.Level.END; 488 | } else { 489 | worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value())); 490 | } 491 | 492 | // If set to not keep spawn in memory (changed from default) then adjust rule accordingly 493 | if (creator.keepSpawnLoaded() == net.kyori.adventure.util.TriState.FALSE) { // Paper 494 | worlddata.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null); 495 | } 496 | 497 | ServerLevel internal = new ServerLevel(console, console.executor, worldSession, worlddata, worldKey, worlddimension, console.progressListenerFactory.create(11), 498 | worlddata.isDebugWorld(), j, creator.environment() == org.bukkit.World.Environment.NORMAL ? list : ImmutableList.of(), true, null, creator.environment(), generator, biomeProvider); 499 | 500 | internal.randomSpawnSelection = new ChunkPos(internal.getChunkSource().randomState().sampler().findSpawnPosition()); 501 | int loadRegionRadius = ((1024) >> 4); 502 | for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) { 503 | for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) { 504 | net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(currX, currZ); 505 | internal.chunkSource.addTicketAtLevel( 506 | TicketType.UNKNOWN, pos, ChunkHolderManager.MAX_TICKET_LEVEL, pos 507 | ); 508 | } 509 | } 510 | 511 | console.addLevel(internal); 512 | internal.setSpawnSettings(true); 513 | 514 | io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(internal); 515 | 516 | Bukkit.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); 517 | 518 | return internal.getWorld(); 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ZUtils" 2 | 3 | include(":zutils-plugin") 4 | include("zutils-based-api") 5 | 6 | /* 7 | include("nms:NMS_V1201") 8 | findProject(":nms:NMS_V1201")?.name = "NMS_V1201" 9 | 10 | include("nms:NMS_V1202") 11 | findProject(":nms:NMS_V1202")?.name = "NMS_V1202" 12 | 13 | include("nms:NMS_V1204") 14 | findProject(":nms:NMS_V1204")?.name = "NMS_V1204" 15 | */ 16 | 17 | include("nms:NMS_V1206") 18 | findProject(":nms:NMS_V1206")?.name = "NMS_V1206" 19 | 20 | include("nms:NMS_V1211") 21 | findProject(":nms:NMS_V1211")?.name = "NMS_V1211" 22 | 23 | include("nms:NMS_V1213") 24 | findProject(":nms:NMS_V1213")?.name = "NMS_V1213" 25 | -------------------------------------------------------------------------------- /zutils-based-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = rootProject.group 2 | version = rootProject.version 3 | 4 | dependencies { 5 | compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") 6 | } -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/api/WorldManager.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.api; 2 | 3 | import org.bukkit.World; 4 | import org.bukkit.WorldCreator; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface WorldManager { 9 | boolean unloadWorld(@NotNull World world, boolean save); 10 | 11 | boolean unloadWorld(@NotNull String name, boolean save); 12 | 13 | @Nullable 14 | World createWorld(@NotNull WorldCreator creator); 15 | } 16 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/api/ZAPIEntryPoint.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.api; 2 | 3 | import i.mrhua269.zutils.api.scheduler.SchedulerService; 4 | import i.mrhua269.zutils.api.teleporter.Teleporter; 5 | import i.mrhua269.zutils.shared.Utils; 6 | import org.jetbrains.annotations.Contract; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | public class ZAPIEntryPoint { 13 | private static final String BASE_PACKAGE_NAME = "i.mrhua269.zutils.nms."; 14 | private static final String API_PACKAGE_NAME = "i.mrhua269.zutils"; 15 | 16 | private static final String FOLIA_SCHEDULER_SERVICE_CLASS_NAME; 17 | private static final String BUKKIT_SCHEDULER_SERVICE_CLASS_NAME; 18 | 19 | private static final String FOLIA_TELEPORTER_CLASS_NAME; 20 | private static final String BUKKIT_TELEPORTER_CLASS_NAME; 21 | private static final String FOLIA_WORLD_MANAGER_CLASS_NAME; 22 | private static final String BUKKIT_WORLD_MANAGER_CLASS_NAME; 23 | 24 | private static final String NMS_VERSION; 25 | 26 | private static SchedulerService SCHEDULER_SERVICE; 27 | private static Teleporter TELEPORTER; 28 | private static WorldManager WORLDMANAGER; 29 | 30 | private static boolean isFolia = false; 31 | 32 | static { 33 | NMS_VERSION = Utils.getServerNMSVersion(); 34 | FOLIA_TELEPORTER_CLASS_NAME = getBaseAPIModuleName() + ".impl.teleporter.FoliaTeleporterImpl"; 35 | BUKKIT_TELEPORTER_CLASS_NAME = getBaseAPIModuleName() + ".impl.teleporter.BukkitTeleporterImpl"; 36 | FOLIA_WORLD_MANAGER_CLASS_NAME = getBaseNMSModuleName() + ".impl.FoliaWorldManagerImpl"; 37 | BUKKIT_WORLD_MANAGER_CLASS_NAME = getBaseAPIModuleName() + ".impl.BukkitWorldManagerImpl"; 38 | 39 | FOLIA_SCHEDULER_SERVICE_CLASS_NAME = getBaseAPIModuleName() + ".impl.scheduler.FoliaSchedulerServiceImpl"; 40 | BUKKIT_SCHEDULER_SERVICE_CLASS_NAME = getBaseAPIModuleName() + ".impl.scheduler.BukkitSchedulerServiceImpl"; 41 | 42 | try { 43 | Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); 44 | isFolia = true; 45 | }catch (Exception ignored){ 46 | 47 | } 48 | } 49 | 50 | public static boolean isFolia(){ 51 | return isFolia; 52 | } 53 | 54 | public static void init(){ 55 | try { 56 | initSchedulerService(); 57 | initTeleporter(); 58 | initWorldManager(); 59 | }catch (Exception e){ 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | 64 | @NotNull 65 | public static SchedulerService getSchedulerService(){ 66 | return SCHEDULER_SERVICE; 67 | } 68 | 69 | @NotNull 70 | public static Teleporter getTeleporter(){ 71 | return TELEPORTER; 72 | } 73 | 74 | @NotNull 75 | public static WorldManager getWorldManager(){ 76 | return WORLDMANAGER; 77 | } 78 | 79 | @Contract(pure = true) 80 | public static @NotNull String getBaseNMSModuleName(){ 81 | return BASE_PACKAGE_NAME + NMS_VERSION; 82 | } 83 | 84 | @NotNull 85 | public static String getBaseAPIModuleName(){ 86 | return API_PACKAGE_NAME; 87 | } 88 | 89 | private static void initSchedulerService() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { 90 | final Class schedulerServiceClass = isFolia() ? Class.forName(FOLIA_SCHEDULER_SERVICE_CLASS_NAME) : Class.forName(BUKKIT_SCHEDULER_SERVICE_CLASS_NAME); 91 | final Constructor constructor = schedulerServiceClass.getConstructor(); 92 | 93 | SCHEDULER_SERVICE = (SchedulerService) constructor.newInstance(); 94 | } 95 | 96 | private static void initTeleporter() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { 97 | final Class teleporterClass = isFolia() ? Class.forName(FOLIA_TELEPORTER_CLASS_NAME) : Class.forName(BUKKIT_TELEPORTER_CLASS_NAME); 98 | final Constructor constructor = teleporterClass.getConstructor(); 99 | 100 | TELEPORTER = (Teleporter) constructor.newInstance(); 101 | } 102 | 103 | private static void initWorldManager() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { 104 | final Class teleporterClass = isFolia() ? Class.forName(FOLIA_WORLD_MANAGER_CLASS_NAME) : Class.forName(BUKKIT_WORLD_MANAGER_CLASS_NAME); 105 | final Constructor constructor = teleporterClass.getConstructor(); 106 | 107 | WORLDMANAGER = (WorldManager) constructor.newInstance(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/api/scheduler/ScheduledTask.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.api.scheduler; 2 | 3 | public interface ScheduledTask { 4 | void cancel(); 5 | } 6 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/api/scheduler/SchedulerService.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.api.scheduler; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.plugin.Plugin; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | 9 | public interface SchedulerService { 10 | @NotNull 11 | ScheduledTask runTaskNow(Runnable task, @Nullable Location location, Plugin plugin); 12 | 13 | @NotNull 14 | ScheduledTask runAsyncTaskNow(Runnable task,Plugin plugin); 15 | 16 | @NotNull 17 | ScheduledTask runTaskLater(Runnable task,long delay,@Nullable Location location,Plugin plugin); 18 | 19 | @NotNull 20 | ScheduledTask runAsyncTaskLater(Runnable task,long delay,Plugin plugin); 21 | 22 | @NotNull 23 | ScheduledTask runTaskTimer(Runnable task,long delay,long period,@Nullable Location location,Plugin plugin); 24 | 25 | @NotNull 26 | ScheduledTask runAsyncTaskTimer(Runnable task,long delay,long period,Plugin plugin); 27 | 28 | void cancelTask(@NotNull Object taskId); 29 | } 30 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/api/teleporter/Teleporter.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.api.teleporter; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.Entity; 5 | import org.bukkit.event.player.PlayerTeleportEvent; 6 | import org.bukkit.plugin.Plugin; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | public interface Teleporter { 12 | @NotNull 13 | CompletableFuture teleportAsync(@NotNull Entity entity, final @NotNull Location loc, final @NotNull PlayerTeleportEvent.TeleportCause cause, @NotNull Plugin plugin); 14 | 15 | @NotNull 16 | CompletableFuture teleportAsync(@NotNull Entity entity, final @NotNull Location loc, @NotNull Plugin plugin); 17 | } 18 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/BukkitWorldManagerImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl; 2 | 3 | import i.mrhua269.zutils.api.WorldManager; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.World; 6 | import org.bukkit.WorldCreator; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class BukkitWorldManagerImpl implements WorldManager { 11 | @Override 12 | public boolean unloadWorld(@NotNull World world, boolean save) { 13 | return Bukkit.getServer().unloadWorld(world,save); 14 | } 15 | 16 | @Override 17 | public boolean unloadWorld(@NotNull String name, boolean save) { 18 | return Bukkit.getServer().unloadWorld(name,save); 19 | } 20 | 21 | @Override 22 | public @Nullable World createWorld(@NotNull WorldCreator creator) { 23 | return Bukkit.createWorld(creator); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/scheduler/BukkitSchedulerServiceImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl.scheduler; 2 | 3 | import i.mrhua269.zutils.api.scheduler.ScheduledTask; 4 | import i.mrhua269.zutils.api.scheduler.SchedulerService; 5 | import i.mrhua269.zutils.impl.scheduler.task.NormalScheduledTaskImpl; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Location; 8 | import org.bukkit.plugin.Plugin; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public class BukkitSchedulerServiceImpl implements SchedulerService { 13 | @Override 14 | public @NotNull ScheduledTask runTaskNow(Runnable task, @Nullable Location location, Plugin plugin) { 15 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 16 | wrapped.setTaskId(Bukkit.getScheduler().runTask(plugin,task).getTaskId()); 17 | return wrapped; 18 | } 19 | 20 | @Override 21 | public @NotNull ScheduledTask runAsyncTaskNow(Runnable task, Plugin plugin) { 22 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 23 | wrapped.setTaskId(Bukkit.getScheduler().runTaskAsynchronously(plugin,task).getTaskId()); 24 | return wrapped; 25 | } 26 | 27 | @Override 28 | public @NotNull ScheduledTask runTaskLater(Runnable task, long delay, @Nullable Location location, Plugin plugin) { 29 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 30 | wrapped.setTaskId(Bukkit.getScheduler().runTaskLater(plugin,task,delay).getTaskId()); 31 | return wrapped; 32 | } 33 | 34 | @Override 35 | public @NotNull ScheduledTask runAsyncTaskLater(Runnable task, long delay, Plugin plugin) { 36 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 37 | wrapped.setTaskId(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delay).getTaskId()); 38 | return wrapped; 39 | } 40 | 41 | @Override 42 | public @NotNull ScheduledTask runTaskTimer(Runnable task, long delay, long period, @Nullable Location location, Plugin plugin) { 43 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 44 | wrapped.setTaskId(Bukkit.getScheduler().runTaskTimer(plugin, task, delay, period).getTaskId()); 45 | return wrapped; 46 | } 47 | 48 | @Override 49 | public @NotNull ScheduledTask runAsyncTaskTimer(Runnable task, long delay, long period, Plugin plugin) { 50 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 51 | wrapped.setTaskId(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay, period).getTaskId()); 52 | return wrapped; 53 | } 54 | 55 | @Override 56 | public void cancelTask(@NotNull Object taskId) { 57 | Bukkit.getScheduler().cancelTask((Integer) taskId); 58 | } 59 | } -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/scheduler/FoliaSchedulerServiceImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl.scheduler; 2 | 3 | import i.mrhua269.zutils.api.scheduler.ScheduledTask; 4 | import i.mrhua269.zutils.api.scheduler.SchedulerService; 5 | import i.mrhua269.zutils.impl.scheduler.task.NormalScheduledTaskImpl; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Location; 8 | import org.bukkit.plugin.Plugin; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class FoliaSchedulerServiceImpl implements SchedulerService { 15 | 16 | @Override 17 | public @NotNull ScheduledTask runTaskNow(Runnable task, @Nullable Location location, Plugin plugin) { 18 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 19 | if (location != null){ 20 | wrapped.setTaskId(Bukkit.getRegionScheduler().run(plugin,location,scheduledTask -> task.run())); 21 | }else{ 22 | wrapped.setTaskId(Bukkit.getGlobalRegionScheduler().run(plugin,scheduledTask -> task.run())); 23 | } 24 | return wrapped; 25 | } 26 | 27 | @Override 28 | public @NotNull ScheduledTask runAsyncTaskNow(Runnable task, Plugin plugin) { 29 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 30 | wrapped.setTaskId(Bukkit.getAsyncScheduler().runNow(plugin,scheduledTask -> task.run())); 31 | return wrapped; 32 | } 33 | 34 | @Override 35 | public @NotNull ScheduledTask runTaskLater(Runnable task, long delay, @Nullable Location location, Plugin plugin) { 36 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 37 | if (location != null){ 38 | wrapped.setTaskId(Bukkit.getRegionScheduler().runDelayed(plugin,location,scheduledTask -> task.run(),delay)); 39 | }else{ 40 | wrapped.setTaskId(Bukkit.getGlobalRegionScheduler().runDelayed(plugin,scheduledTask -> task.run(),delay)); 41 | } 42 | return wrapped; 43 | } 44 | 45 | @Override 46 | public @NotNull ScheduledTask runAsyncTaskLater(Runnable task, long delay, Plugin plugin) { 47 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 48 | wrapped.setTaskId(Bukkit.getAsyncScheduler().runDelayed(plugin,scheduledTask -> task.run(),delay * 50, TimeUnit.MILLISECONDS)); 49 | return null; 50 | } 51 | 52 | @Override 53 | public @NotNull ScheduledTask runTaskTimer(Runnable task, long delay, long period, @Nullable Location location, Plugin plugin) { 54 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 55 | if (location != null){ 56 | wrapped.setTaskId(Bukkit.getRegionScheduler().runAtFixedRate(plugin,location,scheduledTask -> task.run(),delay,period)); 57 | }else { 58 | wrapped.setTaskId(Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin,scheduledTask -> task.run(),delay,period)); 59 | } 60 | return wrapped; 61 | } 62 | 63 | @Override 64 | public @NotNull ScheduledTask runAsyncTaskTimer(Runnable task, long delay, long period, Plugin plugin) { 65 | final NormalScheduledTaskImpl wrapped = new NormalScheduledTaskImpl(this); 66 | wrapped.setTaskId(Bukkit.getAsyncScheduler().runAtFixedRate(plugin,scheduledTask -> task.run(),delay * 50,period * 50, TimeUnit.MILLISECONDS)); 67 | return wrapped; 68 | } 69 | 70 | @Override 71 | public void cancelTask(@NotNull Object taskId) { 72 | ((io.papermc.paper.threadedregions.scheduler.ScheduledTask) taskId).cancel(); 73 | } 74 | } -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/scheduler/task/NormalScheduledTaskImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl.scheduler.task; 2 | 3 | import i.mrhua269.zutils.api.scheduler.ScheduledTask; 4 | import i.mrhua269.zutils.api.scheduler.SchedulerService; 5 | 6 | public class NormalScheduledTaskImpl implements ScheduledTask { 7 | private final SchedulerService parentService; 8 | private Object taskId = null; 9 | 10 | public NormalScheduledTaskImpl(SchedulerService parentService) { 11 | this.parentService = parentService; 12 | } 13 | 14 | public void setTaskId(Object taskId){ 15 | this.taskId = taskId; 16 | } 17 | 18 | @Override 19 | public void cancel() { 20 | this.parentService.cancelTask(this.taskId); 21 | } 22 | } -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/teleporter/BukkitTeleporterImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl.teleporter; 2 | 3 | import i.mrhua269.zutils.api.teleporter.Teleporter; 4 | import org.bukkit.Location; 5 | import org.bukkit.entity.Entity; 6 | import org.bukkit.event.player.PlayerTeleportEvent; 7 | import org.bukkit.plugin.Plugin; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | public class BukkitTeleporterImpl implements Teleporter { 13 | @Override 14 | public @NotNull CompletableFuture teleportAsync(@NotNull Entity entity, @NotNull Location loc, PlayerTeleportEvent.@NotNull TeleportCause cause, @NotNull Plugin plugin) { 15 | return entity.teleportAsync(loc, cause); //Call directly 16 | } 17 | 18 | @Override 19 | public @NotNull CompletableFuture teleportAsync(@NotNull Entity entity, @NotNull Location loc, @NotNull Plugin plugin) { 20 | return entity.teleportAsync(loc); //Call directly 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/impl/teleporter/FoliaTeleporterImpl.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.impl.teleporter; 2 | 3 | import i.mrhua269.zutils.api.teleporter.Teleporter; 4 | import org.bukkit.Location; 5 | import org.bukkit.entity.Entity; 6 | import org.bukkit.event.player.PlayerTeleportEvent; 7 | import org.bukkit.plugin.Plugin; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | public class FoliaTeleporterImpl implements Teleporter { 13 | @Override 14 | public @NotNull CompletableFuture teleportAsync(@NotNull Entity entity, @NotNull Location loc, PlayerTeleportEvent.@NotNull TeleportCause cause, @NotNull Plugin plugin) { 15 | final CompletableFuture result = new CompletableFuture<>(); 16 | 17 | entity.getScheduler().execute(plugin, () -> { 18 | entity.teleportAsync(loc, cause).thenAccept(result::complete); //Force run on the scheduler of entity 19 | },() -> result.complete(Boolean.FALSE),1L); 20 | 21 | return result; 22 | } 23 | 24 | @Override 25 | public @NotNull CompletableFuture teleportAsync(@NotNull Entity entity, @NotNull Location loc, @NotNull Plugin plugin) { 26 | final CompletableFuture result = new CompletableFuture<>(); 27 | 28 | entity.getScheduler().execute(plugin, () -> { 29 | entity.teleportAsync(loc).thenAccept(result::complete); //Force run on the scheduler of entity 30 | },() -> result.complete(Boolean.FALSE),1L); 31 | 32 | return result; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /zutils-based-api/src/main/java/i/mrhua269/zutils/shared/Utils.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutils.shared; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class Utils { 7 | @NotNull 8 | public static String getServerNMSVersion(){ 9 | return "v" + Bukkit.getServer().getMinecraftVersion().replace(".","_"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zutils-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = rootProject.group 2 | version = rootProject.version 3 | 4 | dependencies { 5 | compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") 6 | implementation(project(":zutils-based-api")) 7 | /*implementation(project(":nms:NMS_V1201","reobf")) 8 | implementation(project(":nms:NMS_V1202","reobf")) 9 | implementation(project(":nms:NMS_V1204","reobf"))*/ 10 | implementation(project(":nms:NMS_V1206","reobf")) 11 | implementation(project(":nms:NMS_V1211","reobf")) 12 | implementation(project(":nms:NMS_V1213","reobf")) 13 | } -------------------------------------------------------------------------------- /zutils-plugin/src/main/java/i/mrhua269/zutilsplugin/ZUtils.java: -------------------------------------------------------------------------------- 1 | package i.mrhua269.zutilsplugin; 2 | 3 | import i.mrhua269.zutils.api.ZAPIEntryPoint; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | public final class ZUtils extends JavaPlugin { 7 | 8 | @Override 9 | public void onEnable() { 10 | ZAPIEntryPoint.init(); 11 | } 12 | 13 | @Override 14 | public void onDisable() { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /zutils-plugin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: zutils-plugin 2 | version: '${version}' 3 | main: i.mrhua269.zutilsplugin.ZUtils 4 | api-version: '1.19' 5 | folia-supported: true --------------------------------------------------------------------------------