├── .gitignore ├── LICENSE ├── README.MD ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── settings.gradle └── src └── main ├── java └── me │ └── contaria │ └── seedqueue │ ├── SeedQueue.java │ ├── SeedQueueConfig.java │ ├── SeedQueueEntry.java │ ├── SeedQueueException.java │ ├── SeedQueueExecutorWrapper.java │ ├── SeedQueueMixinConfigPlugin.java │ ├── SeedQueueThread.java │ ├── compat │ ├── FastResetCompat.java │ ├── ModCompat.java │ ├── SeedQueuePreviewFrameBuffer.java │ ├── SeedQueuePreviewProperties.java │ ├── SeedQueueSettingsCache.java │ ├── SodiumCompat.java │ ├── StandardSettingsCompat.java │ ├── StateOutputCompat.java │ └── WorldPreviewCompat.java │ ├── customization │ ├── AnimatedTexture.java │ ├── Layout.java │ └── LockTexture.java │ ├── debug │ ├── SeedQueueProfiler.java │ ├── SeedQueueSystemInfo.java │ └── SeedQueueWatchdog.java │ ├── gui │ ├── SeedQueueCrashToast.java │ ├── config │ │ ├── SeedQueueKeyButtonWidget.java │ │ ├── SeedQueueKeybindingsListWidget.java │ │ ├── SeedQueueKeybindingsScreen.java │ │ └── SeedQueueWindowSizeWidget.java │ └── wall │ │ ├── SeedQueueBenchmarkToast.java │ │ ├── SeedQueuePreview.java │ │ └── SeedQueueWallScreen.java │ ├── interfaces │ ├── SQMinecraftServer.java │ ├── SQSoundManager.java │ ├── SQSoundSystem.java │ ├── SQWorldGenerationProgressLogger.java │ ├── SQWorldGenerationProgressTracker.java │ ├── sodium │ │ └── SQChunkBuilder$WorkerRunnable.java │ └── worldpreview │ │ └── SQWorldRenderer.java │ ├── keybindings │ ├── SeedQueueKeyBindings.java │ └── SeedQueueMultiKeyBinding.java │ ├── mixin │ ├── accessor │ │ ├── CameraAccessor.java │ │ ├── DebugHudAccessor.java │ │ ├── EntityAccessor.java │ │ ├── MinecraftClientAccessor.java │ │ ├── MinecraftServerAccessor.java │ │ ├── PlayerEntityAccessor.java │ │ ├── UtilAccessor.java │ │ ├── WorldGenerationProgressTrackerAccessor.java │ │ └── WorldRendererAccessor.java │ ├── client │ │ ├── CreateWorldScreenMixin.java │ │ ├── MinecraftClientMixin.java │ │ ├── WorldGenerationProgressLoggerMixin.java │ │ ├── WorldGenerationProgressTrackerMixin.java │ │ ├── debug │ │ │ └── DebugHudMixin.java │ │ ├── levellist │ │ │ └── LevelStorageMixin.java │ │ ├── profiling │ │ │ └── WorldRendererMixin.java │ │ ├── render │ │ │ ├── LevelLoadingScreenMixin.java │ │ │ └── MinecraftClientMixin.java │ │ └── sounds │ │ │ ├── SoundManagerMixin.java │ │ │ └── SoundSystemMixin.java │ ├── compat │ │ ├── atum │ │ │ ├── AttemptTrackerMixin.java │ │ │ ├── AtumMixin.java │ │ │ ├── CreateWorldScreenMixin.java │ │ │ └── KeyboardMixin.java │ │ ├── sodium │ │ │ ├── ChunkBuilder$WorkerRunnableMixin.java │ │ │ ├── ChunkBuilder$WorkerRunnableMixin2.java │ │ │ ├── ChunkBuilderMixin.java │ │ │ ├── ChunkRenderManagerMixin.java │ │ │ ├── ChunkRenderShaderBackendMixin.java │ │ │ └── profiling │ │ │ │ ├── ChunkBuilderMixin.java │ │ │ │ ├── ChunkRenderCacheLocalMixin.java │ │ │ │ ├── ChunkRenderManagerMixin.java │ │ │ │ ├── MultidrawChunkRenderBackendMixin.java │ │ │ │ └── SodiumWorldRendererMixin.java │ │ ├── standardsettings │ │ │ └── MinecraftClientMixin.java │ │ └── worldpreview │ │ │ ├── ThreadedAnvilChunkStorageMixin.java │ │ │ ├── WorldPreviewMixin.java │ │ │ └── render │ │ │ ├── ClientWorldMixin.java │ │ │ ├── InGameHudMixin.java │ │ │ ├── WindowMixin.java │ │ │ └── WorldRendererMixin.java │ └── server │ │ ├── IntegratedServerMixin.java │ │ ├── MinecraftServerMixin.java │ │ ├── optimization │ │ └── ThreadedAnvilChunkStorageMixin.java │ │ ├── parity │ │ └── EntityMixin.java │ │ └── synchronization │ │ ├── BiomeMixin.java │ │ ├── DirectionTransformationMixin.java │ │ ├── ScheduledTickMixin.java │ │ └── WeightedBlockStateProviderMixin.java │ └── sounds │ └── SeedQueueSounds.java └── resources ├── assets └── seedqueue │ ├── icon.png │ ├── lang │ ├── cs_cz.json │ ├── en_us.json │ ├── es_es.json │ ├── he_il.json │ ├── pl_pl.json │ └── zh_cn.json │ ├── sounds.json │ ├── sounds │ ├── lock_instance.ogg │ └── reset_instance.ogg │ └── textures │ └── gui │ └── wall │ └── lock.png ├── fabric.mod.json └── seedqueue.mixins.json /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | libs/ 35 | 36 | # java 37 | 38 | hs_err_*.log 39 | replay_*.log 40 | *.hprof 41 | *.jfr 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 contaria 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # SeedQueue 2 | ## Description 3 | SeedQueue is a mod that is meant to replace multi-instancing. Instead of having multiple Minecrafts generating worlds open at the same time, it does it all in just one Minecraft instance as it has a built-in wall screen that can be configured through the SpeedrunAPI menu and further customized through resource packs. This greatly improves performance, especially for lower end hardware, and is also aimed to make speedrunning more accessible. 4 | ## Dependencies 5 | To use [SeedQueue](https://github.com/KingContaria/seedqueue/releases/latest) you will also need Atum and SpeedrunAPI, you can get them from [here](https://mods.tildejustin.dev/). 6 | ## Latest Release 7 | Get the latest release of SeedQueue from [here](https://github.com/KingContaria/seedqueue/releases/latest). 8 | ## Tutorial to get started with SeedQueue 9 | If you are new and want to get started with SeedQueue watch this [tutorial](https://www.youtube.com/watch?v=fGu2MYZxh_c). 10 | ## SeedQueue Wiki 11 | You can learn more about the config settings for SeedQueue and get guidance for customization of your Wall Screen on the [SeedQueue Wiki](https://github.com/KingContaria/seedqueue/wiki). 12 | ## Discord Server 13 | If you are looking forward to getting in touch with the SeedQueue Community, join the SeedQueue discord server by clicking [here](https://discord.gg/9P6PJkHCdU). 14 | 15 | ## Acknowledgements 16 | 17 | 18 | 19 | I use the [YourKit Java Profiler](https://www.yourkit.com/java/profiler/) for profiling when developing SeedQueue. 20 | Thanks to YourKit for providing a free license to their product. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.7-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | version = project.mod_version + "+" + project.target_version 7 | group = project.maven_group 8 | 9 | base { 10 | archivesName = project.archives_base_name 11 | } 12 | 13 | repositories { 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | // To change the versions see the gradle.properties file 19 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 20 | mappings "dev.tildejustin:yarn:${project.yarn_mappings}:v2" 21 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 22 | 23 | // SpeedrunAPI 24 | // check for the latest versions at https://jitpack.io/#kingcontaria/speedrunapi 25 | modImplementation "com.github.KingContaria:SpeedrunAPI:8497ae7479" 26 | annotationProcessor "com.github.bawnorton.mixinsquared:mixinsquared-fabric:0.1.1" 27 | 28 | // check for the latest versions at https://jitpack.io/#kingcontaria/atum-rewrite 29 | modImplementation ("com.github.KingContaria:atum-rewrite:d0ac2178f8") { 30 | transitive = false 31 | } 32 | 33 | // check for the latest versions at https://jitpack.io/#kingcontaria/mcsr-worldpreview-1.16.1 34 | modCompileOnly ("com.github.KingContaria:mcsr-worldpreview-1.16.1:649212d145") { 35 | transitive = false 36 | } 37 | 38 | // check for the latest versions at https://jitpack.io/#Minecraft-Java-Edition-Speedrunning/sodium 39 | modCompileOnly "com.github.Minecraft-Java-Edition-Speedrunning:sodium:d488d3a782" 40 | 41 | // check for the latest versions at https://jitpack.io/#kingcontaria/fastreset 42 | modCompileOnly ("com.github.KingContaria:fastreset:7da3236482") { 43 | transitive = false 44 | } 45 | 46 | // check for the latest versions at https://jitpack.io/#kingcontaria/standardsettings 47 | modCompileOnly ("com.github.KingContaria:standardsettings:a22107e94e") { 48 | transitive = false 49 | } 50 | 51 | // check for latest versions at https://jitpack.io/#dev.tildejustin/state-output 52 | modCompileOnly "dev.tildejustin.state-output:state-output-common:v1.2.0" 53 | } 54 | 55 | processResources { 56 | inputs.property "version", version 57 | 58 | filesMatching("fabric.mod.json") { 59 | expand "version": version 60 | } 61 | } 62 | 63 | tasks.withType(JavaCompile).configureEach { 64 | it.options.encoding = "UTF-8" 65 | 66 | def targetVersion = 8 67 | if (JavaVersion.current().isJava9Compatible()) { 68 | it.options.release = targetVersion 69 | } 70 | } 71 | 72 | java { 73 | sourceCompatibility = JavaVersion.VERSION_1_8 74 | targetCompatibility = JavaVersion.VERSION_1_8 75 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 76 | // if it is present. 77 | // If you remove this line, sources will not be generated. 78 | withSourcesJar() 79 | } 80 | 81 | jar { 82 | from("LICENSE") { 83 | rename { "${it}_${project.base.archivesName.get()}"} 84 | } 85 | } 86 | 87 | // configure the maven publication 88 | publishing { 89 | publications { 90 | mavenJava(MavenPublication) { 91 | from components.java 92 | } 93 | } 94 | 95 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 96 | repositories { 97 | // Add repositories to publish to here. 98 | // Notice: This block does NOT have the same function as the block in the top level. 99 | // The repositories here will be used for publishing your artifact, not for 100 | // retrieving dependencies. 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | org.gradle.parallel=true 4 | 5 | # Fabric Properties 6 | # check these on https://fabricmc.net/develop 7 | minecraft_version=1.16.1 8 | yarn_mappings=1.16.1-build.24 9 | loader_version=0.16.10 10 | 11 | # Mod Properties 12 | mod_version=1.4 13 | target_version=1.16.1 14 | maven_group=me.contaria 15 | archives_base_name=seedqueue 16 | 17 | # Dependencies -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | before_install: 4 | - sdk install java 17.0.1-open 5 | - sdk use java 17.0.1-open 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/SeedQueueException.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue; 2 | 3 | /** 4 | * Exception thrown when something goes wrong on the {@link SeedQueueThread}. 5 | */ 6 | public class SeedQueueException extends RuntimeException { 7 | public SeedQueueException(String message) { 8 | super(message); 9 | } 10 | 11 | public SeedQueueException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/SeedQueueExecutorWrapper.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue; 2 | 3 | import me.contaria.seedqueue.mixin.accessor.UtilAccessor; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.concurrent.Executor; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.ForkJoinPool; 9 | import java.util.concurrent.ForkJoinWorkerThread; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | /** 13 | * Wraps the server executor to allow for using seperate executors while a server is generating in queue. 14 | * This allows for modifying thread priorities and parallelism of executors used while in queue to help manage performance. 15 | *

16 | * The backing {@link ExecutorService}'s are created lazily and should be shut down after a SeedQueue session. 17 | * This should happen AFTER all servers have been shut down! 18 | * 19 | * @see SeedQueueConfig#backgroundExecutorThreadPriority 20 | * @see SeedQueueConfig#backgroundExecutorThreads 21 | * @see SeedQueueConfig#wallExecutorThreadPriority 22 | * @see SeedQueueConfig#wallExecutorThreads 23 | */ 24 | public class SeedQueueExecutorWrapper implements Executor { 25 | /** 26 | * Executor used by servers while they are in queue. 27 | * Redirects to the backing executors depending on the current state. 28 | */ 29 | public static final Executor SEEDQUEUE_EXECUTOR = command -> getSeedqueueExecutor().execute(command); 30 | 31 | private static ExecutorService SEEDQUEUE_BACKGROUND_EXECUTOR; 32 | private static ExecutorService SEEDQUEUE_WALL_EXECUTOR; 33 | 34 | private final Executor originalExecutor; 35 | private Executor executor; 36 | 37 | public SeedQueueExecutorWrapper(Executor originalExecutor) { 38 | this.executor = this.originalExecutor = originalExecutor; 39 | } 40 | 41 | @Override 42 | public void execute(@NotNull Runnable command) { 43 | this.executor.execute(command); 44 | } 45 | 46 | public void setExecutor(Executor executor) { 47 | this.executor = executor; 48 | } 49 | 50 | public void resetExecutor() { 51 | this.setExecutor(this.originalExecutor); 52 | } 53 | 54 | private static Executor getSeedqueueExecutor() { 55 | // if Max Generating Seeds is set to 0 while not on wall, 56 | // this will ensure the background executor is never created 57 | if (SeedQueue.isOnWall() || SeedQueue.config.maxConcurrently == 0) { 58 | return getOrCreateWallExecutor(); 59 | } 60 | return getOrCreateBackgroundExecutor(); 61 | } 62 | 63 | private synchronized static Executor getOrCreateBackgroundExecutor() { 64 | if (SEEDQUEUE_BACKGROUND_EXECUTOR == null) { 65 | SEEDQUEUE_BACKGROUND_EXECUTOR = createExecutor("SeedQueue", SeedQueue.config.getBackgroundExecutorThreads(), SeedQueue.config.backgroundExecutorThreadPriority); 66 | } 67 | return SEEDQUEUE_BACKGROUND_EXECUTOR; 68 | } 69 | 70 | private synchronized static Executor getOrCreateWallExecutor() { 71 | if (SEEDQUEUE_WALL_EXECUTOR == null) { 72 | SEEDQUEUE_WALL_EXECUTOR = createExecutor("SeedQueue Wall", SeedQueue.config.getWallExecutorThreads(), SeedQueue.config.wallExecutorThreadPriority); 73 | } 74 | return SEEDQUEUE_WALL_EXECUTOR; 75 | } 76 | 77 | // see Util#createWorker 78 | private static ExecutorService createExecutor(String name, int threads, int priority) { 79 | AtomicInteger threadCount = new AtomicInteger(); 80 | return new ForkJoinPool(threads, pool -> { 81 | ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); 82 | thread.setName("Worker-" + name + "-" + threadCount.getAndIncrement()); 83 | thread.setPriority(priority); 84 | return thread; 85 | }, UtilAccessor::seedQueue$uncaughtExceptionHandler, true); 86 | } 87 | 88 | /** 89 | * Shuts down and removes the SeedQueue specific {@link ExecutorService}s. 90 | */ 91 | public synchronized static void shutdownExecutors() { 92 | if (SEEDQUEUE_BACKGROUND_EXECUTOR != null) { 93 | UtilAccessor.seedQueue$attemptShutdown(SEEDQUEUE_BACKGROUND_EXECUTOR); 94 | SEEDQUEUE_BACKGROUND_EXECUTOR = null; 95 | } 96 | if (SEEDQUEUE_WALL_EXECUTOR != null) { 97 | UtilAccessor.seedQueue$attemptShutdown(SEEDQUEUE_WALL_EXECUTOR); 98 | SEEDQUEUE_WALL_EXECUTOR = null; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/SeedQueueMixinConfigPlugin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue; 2 | 3 | import com.bawnorton.mixinsquared.api.MixinCanceller; 4 | import me.contaria.speedrunapi.mixin_plugin.SpeedrunMixinConfigPlugin; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * SeedQueues Mixin Config Plugin, extends SpeedrunAPI's plugin to inherit its functionality. 11 | * Only used for compatibility with Sodium Mac, will be redundant once Sodium is updated to not need the seperate mac version anymore. 12 | */ 13 | public class SeedQueueMixinConfigPlugin extends SpeedrunMixinConfigPlugin implements MixinCanceller { 14 | private static final boolean MAC_SODIUM = FabricLoader.getInstance().isModLoaded("sodiummac"); 15 | 16 | @Override 17 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 18 | // Sodium Mac "Compat" 19 | // Create RenderCache Async doesn't apply on Sodium Mac 20 | if (MAC_SODIUM) { 21 | if (mixinClassName.startsWith(this.mixinPackage + ".compat.sodium.profiling")) { 22 | return false; 23 | } 24 | if (mixinClassName.equals(this.mixinPackage + ".compat.sodium.ClientChunkManagerMixin") || mixinClassName.equals(this.mixinPackage + ".compat.sodium.ChunkBuilder$WorkerRunnableMixin2")) { 25 | return false; 26 | } 27 | } 28 | return super.shouldApplyMixin(targetClassName, mixinClassName); 29 | } 30 | 31 | @Override 32 | public boolean shouldCancel(List targetClassNames, String mixinClassName) { 33 | // SleepBackgrounds Thread Executor mixin has been observed to hurt performance when SeedQueue is active 34 | // since SeedQueue spawns many more executors 35 | return mixinClassName.equals("com.redlimerl.sleepbackground.mixin.MixinThreadExecutor"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/SeedQueueThread.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue; 2 | 3 | import me.contaria.seedqueue.gui.SeedQueueCrashToast; 4 | import me.contaria.speedrunapi.util.TextUtil; 5 | import me.voidxwalker.autoreset.AtumCreateWorldScreen; 6 | import net.minecraft.client.MinecraftClient; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | 12 | /** 13 | * The thread responsible for launching new server threads and creating and queueing the corresponding {@link SeedQueueEntry}'s. 14 | *

15 | * This thread is to be launched at the start of a SeedQueue session and to be closed by calling {@link SeedQueueThread#stopQueue}. 16 | */ 17 | public class SeedQueueThread extends Thread { 18 | public static final Object WORLD_CREATION_LOCK = new Object(); 19 | 20 | private final Object lock = new Object(); 21 | private final AtomicBoolean pinged = new AtomicBoolean(); 22 | private volatile boolean running = true; 23 | 24 | SeedQueueThread() { 25 | super("SeedQueue Thread"); 26 | this.setPriority(SeedQueue.config.seedQueueThreadPriority); 27 | } 28 | 29 | @Override 30 | public void run() { 31 | while (this.running) { 32 | try { 33 | // clear pinged state when starting a new check 34 | this.pinged.set(false); 35 | if (SeedQueue.shouldPauseGenerating()) { 36 | this.pauseSeedQueueEntry(); 37 | continue; 38 | } 39 | boolean shouldResumeAfterQueueFull = SeedQueue.shouldResumeAfterQueueFull(); 40 | if (!SeedQueue.shouldGenerate() || shouldResumeAfterQueueFull) { 41 | boolean shouldResumeGenerating = SeedQueue.shouldResumeGenerating(); 42 | if (!shouldResumeGenerating && !SeedQueue.noLockedRemaining() && shouldResumeAfterQueueFull) { 43 | this.pauseSeedQueueEntry(); 44 | continue; 45 | } 46 | if (shouldResumeGenerating && this.unpauseSeedQueueEntry()) { 47 | continue; 48 | } 49 | synchronized (this.lock) { 50 | // don't freeze thread if it's been pinged at any point during the check 51 | if (this.pinged.get()) { 52 | continue; 53 | } 54 | this.lock.wait(); 55 | } 56 | continue; 57 | } 58 | 59 | if (this.unpauseSeedQueueEntry()) { 60 | continue; 61 | } 62 | 63 | this.createSeedQueueEntry(); 64 | } catch (Exception e) { 65 | SeedQueue.LOGGER.error("Shutting down SeedQueue Thread...", e); 66 | SeedQueue.scheduleTaskOnClientThread(() -> MinecraftClient.getInstance().getToastManager().add(new SeedQueueCrashToast( 67 | TextUtil.translatable("seedqueue.menu.crash.title"), 68 | TextUtil.translatable("seedqueue.menu.crash.description", e.getClass().getSimpleName()) 69 | ))); 70 | this.stopQueue(); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Tries to find a currently unpaused {@link SeedQueueEntry} and schedules it to be paused. 77 | */ 78 | private void pauseSeedQueueEntry() { 79 | // try to pause not locked entries first 80 | Optional entry = SeedQueue.getEntryMatching(e -> !e.isLocked() && e.canPause()); 81 | if (!entry.isPresent()) { 82 | entry = SeedQueue.getEntryMatching(SeedQueueEntry::canPause); 83 | } 84 | entry.ifPresent(SeedQueueEntry::schedulePause); 85 | } 86 | 87 | /** 88 | * Tries to find a currently paused {@link SeedQueueEntry} and schedules it to be unpaused. 89 | * 90 | * @return False if there is no entries to unpause. 91 | */ 92 | private boolean unpauseSeedQueueEntry() { 93 | List entries = SeedQueue.getEntries(); 94 | // try to unpause locked entries first 95 | for (SeedQueueEntry entry : entries) { 96 | if (entry.isLocked() && entry.tryToUnpause()) { 97 | return true; 98 | } 99 | } 100 | for (SeedQueueEntry entry : entries) { 101 | if (entry.tryToUnpause()) { 102 | return true; 103 | } 104 | } 105 | return false; 106 | } 107 | 108 | /** 109 | * Creates a new {@link SeedQueueEntry} and adds it to the queue. 110 | */ 111 | private void createSeedQueueEntry() { 112 | synchronized (WORLD_CREATION_LOCK) { 113 | new AtumCreateWorldScreen(null).init(MinecraftClient.getInstance(), 0, 0); 114 | } 115 | } 116 | 117 | public void ping() { 118 | synchronized (this.lock) { 119 | this.pinged.set(true); 120 | this.lock.notify(); 121 | } 122 | } 123 | 124 | /** 125 | * Stops this thread. 126 | *

127 | * If this method is called from another thread, {@link SeedQueueThread#ping} has to be called AFTER. 128 | */ 129 | public void stopQueue() { 130 | this.running = false; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/FastResetCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import fast_reset.client.interfaces.FRMinecraftServer; 4 | import net.minecraft.server.MinecraftServer; 5 | 6 | class FastResetCompat { 7 | 8 | static void fastReset(MinecraftServer server) { 9 | ((FRMinecraftServer) server).fastReset$fastReset(); 10 | } 11 | 12 | static boolean shouldSave(MinecraftServer server) { 13 | return ((FRMinecraftServer) server).fastReset$shouldSave(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/ModCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import me.contaria.standardsettings.StandardSettings; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | import net.minecraft.server.MinecraftServer; 6 | 7 | /** 8 | * Intermediate class that allows safe access to other mods methods/fields from code paths that may be run without the mod present. 9 | * This class provides wrapper methods for compat classes which should only be classloaded if the mod in question is loaded. 10 | */ 11 | public class ModCompat { 12 | public static final boolean HAS_FASTRESET = FabricLoader.getInstance().isModLoaded("fast_reset"); 13 | public static final boolean HAS_SODIUM = FabricLoader.getInstance().isModLoaded("sodium"); 14 | public static final boolean HAS_STANDARDSETTINGS = FabricLoader.getInstance().isModLoaded("standardsettings"); 15 | public static final boolean HAS_WORLDPREVIEW = FabricLoader.getInstance().isModLoaded("worldpreview"); 16 | public static final boolean HAS_STATEOUTPUT = FabricLoader.getInstance().isModLoaded("state-output"); 17 | 18 | public static void fastReset$fastReset(MinecraftServer server) { 19 | if (HAS_FASTRESET) { 20 | FastResetCompat.fastReset(server); 21 | } 22 | } 23 | 24 | public static boolean fastReset$shouldSave(MinecraftServer server) { 25 | if (HAS_FASTRESET) { 26 | return FastResetCompat.shouldSave(server); 27 | } 28 | return false; 29 | } 30 | 31 | public static void stateoutput$setWallState() { 32 | if (HAS_STATEOUTPUT) { 33 | StateOutputCompat.setWallState(); 34 | } 35 | } 36 | 37 | public static void sodium$clearShaderCache() { 38 | if (HAS_SODIUM) { 39 | SodiumCompat.clearShaderCache(); 40 | } 41 | } 42 | 43 | public static void sodium$clearBuildBufferPool() { 44 | if (HAS_SODIUM) { 45 | SodiumCompat.clearBuildBufferPool(); 46 | } 47 | } 48 | 49 | public static void standardsettings$cache() { 50 | if (HAS_STANDARDSETTINGS) { 51 | StandardSettingsCompat.createCache(); 52 | } 53 | } 54 | 55 | public static void standardsettings$reset() { 56 | if (HAS_STANDARDSETTINGS) { 57 | StandardSettingsCompat.resetPendingActions(); 58 | if (StandardSettings.isEnabled()) { 59 | StandardSettingsCompat.reset(); 60 | } 61 | } 62 | } 63 | 64 | public static void standardsettings$loadCache() { 65 | if (HAS_STANDARDSETTINGS) { 66 | StandardSettingsCompat.loadCache(); 67 | } 68 | } 69 | 70 | public static boolean worldpreview$inPreview() { 71 | if (HAS_WORLDPREVIEW) { 72 | return WorldPreviewCompat.inPreview(); 73 | } 74 | return false; 75 | } 76 | 77 | public static boolean worldpreview$kill(MinecraftServer server) { 78 | if (HAS_WORLDPREVIEW) { 79 | return WorldPreviewCompat.kill(server); 80 | } 81 | return false; 82 | } 83 | 84 | public static void worldpreview$clearFramebufferPool() { 85 | if (HAS_WORLDPREVIEW) { 86 | SeedQueuePreviewFrameBuffer.clearFramebufferPool(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/SeedQueuePreviewFrameBuffer.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.gl.Framebuffer; 7 | import net.minecraft.client.render.BufferBuilder; 8 | import net.minecraft.client.render.Tessellator; 9 | import net.minecraft.client.render.VertexFormats; 10 | import net.minecraft.client.render.WorldRenderer; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Objects; 16 | 17 | /** 18 | * Wrapper for Minecrafts {@link Framebuffer} storing a previews last drawn image. 19 | *

20 | * Stores {@link SeedQueuePreviewFrameBuffer#lastRenderData} as to only redraw the preview if it has changed. 21 | */ 22 | public class SeedQueuePreviewFrameBuffer { 23 | private static final List FRAMEBUFFER_POOL = new ArrayList<>(); 24 | 25 | private final Framebuffer framebuffer; 26 | 27 | // stores a string unique to the current state of world rendering when writing to the framebuffer 28 | @Nullable 29 | private String lastRenderData; 30 | 31 | public SeedQueuePreviewFrameBuffer() { 32 | if (FRAMEBUFFER_POOL.isEmpty()) { 33 | this.framebuffer = new Framebuffer( 34 | SeedQueue.config.simulatedWindowSize.width(), 35 | SeedQueue.config.simulatedWindowSize.height(), 36 | true, 37 | MinecraftClient.IS_SYSTEM_MAC 38 | ); 39 | } else { 40 | this.framebuffer = FRAMEBUFFER_POOL.remove(0); 41 | } 42 | } 43 | 44 | public void beginWrite() { 45 | this.framebuffer.beginWrite(true); 46 | } 47 | 48 | public void endWrite() { 49 | this.framebuffer.endWrite(); 50 | } 51 | 52 | public boolean updateRenderData(WorldRenderer worldRenderer) { 53 | return !Objects.equals(this.lastRenderData, this.lastRenderData = worldRenderer.getChunksDebugString() + "\n" + worldRenderer.getEntitiesDebugString()); 54 | } 55 | 56 | /** 57 | * Draws the internal {@link Framebuffer} without setting {@link RenderSystem#ortho} and {@link RenderSystem#viewport}. 58 | */ 59 | @SuppressWarnings("deprecation") 60 | public void draw(int width, int height) { 61 | RenderSystem.assertThread(RenderSystem::isOnRenderThread); 62 | 63 | RenderSystem.colorMask(true, true, true, false); 64 | RenderSystem.disableDepthTest(); 65 | RenderSystem.depthMask(false); 66 | RenderSystem.enableTexture(); 67 | RenderSystem.disableLighting(); 68 | RenderSystem.disableAlphaTest(); 69 | RenderSystem.disableBlend(); 70 | RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f); 71 | 72 | this.framebuffer.beginRead(); 73 | Tessellator tessellator = RenderSystem.renderThreadTesselator(); 74 | BufferBuilder bufferBuilder = tessellator.getBuffer(); 75 | bufferBuilder.begin(7, VertexFormats.POSITION_TEXTURE_COLOR); 76 | bufferBuilder.vertex(0.0, height, 0.0).texture(0.0f, 0.0f).color(255, 255, 255, 255).next(); 77 | bufferBuilder.vertex(width, height, 0.0).texture(1.0f, 0.0f).color(255, 255, 255, 255).next(); 78 | bufferBuilder.vertex(width, 0.0, 0.0).texture(1.0f, 1.0f).color(255, 255, 255, 255).next(); 79 | bufferBuilder.vertex(0.0, 0.0, 0.0).texture(0.0f, 1.0f).color(255, 255, 255, 255).next(); 80 | tessellator.draw(); 81 | this.framebuffer.endRead(); 82 | 83 | RenderSystem.depthMask(true); 84 | RenderSystem.colorMask(true, true, true, true); 85 | } 86 | 87 | public void discard() { 88 | FRAMEBUFFER_POOL.add(this.framebuffer); 89 | } 90 | 91 | static void clearFramebufferPool() { 92 | for (Framebuffer framebuffer : FRAMEBUFFER_POOL) { 93 | framebuffer.delete(); 94 | } 95 | FRAMEBUFFER_POOL.clear(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/SeedQueuePreviewProperties.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import me.contaria.seedqueue.debug.SeedQueueProfiler; 5 | import me.contaria.seedqueue.interfaces.worldpreview.SQWorldRenderer; 6 | import me.contaria.seedqueue.mixin.accessor.CameraAccessor; 7 | import me.contaria.seedqueue.mixin.accessor.PlayerEntityAccessor; 8 | import me.voidxwalker.worldpreview.WorldPreview; 9 | import me.voidxwalker.worldpreview.WorldPreviewProperties; 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.network.ClientPlayerEntity; 12 | import net.minecraft.client.network.ClientPlayerInteractionManager; 13 | import net.minecraft.client.render.Camera; 14 | import net.minecraft.client.render.DiffuseLighting; 15 | import net.minecraft.client.render.entity.PlayerModelPart; 16 | import net.minecraft.client.util.Window; 17 | import net.minecraft.client.util.math.MatrixStack; 18 | import net.minecraft.client.util.math.Vector3f; 19 | import net.minecraft.client.world.ClientWorld; 20 | import net.minecraft.network.Packet; 21 | import net.minecraft.util.math.Matrix4f; 22 | 23 | import java.util.Queue; 24 | 25 | public class SeedQueuePreviewProperties extends WorldPreviewProperties { 26 | public SeedQueuePreviewProperties(ClientWorld world, ClientPlayerEntity player, ClientPlayerInteractionManager interactionManager, Camera camera, Queue> packetQueue) { 27 | super(world, player, interactionManager, camera, packetQueue); 28 | } 29 | 30 | public int getPerspective() { 31 | return this.camera.isThirdPerson() ? ((CameraAccessor) this.camera).seedQueue$isInverseView() ? 2 : 1 : 0; 32 | } 33 | 34 | /** 35 | * Builds chunks for the current {@link WorldPreview#worldRenderer} without rendering the preview. 36 | * 37 | * @see WorldPreviewProperties#render 38 | */ 39 | @SuppressWarnings("deprecation") 40 | public void buildChunks() { 41 | MinecraftClient client = MinecraftClient.getInstance(); 42 | Window window = client.getWindow(); 43 | Camera camera = client.gameRenderer.getCamera(); 44 | 45 | SeedQueueProfiler.swap("build_preview"); 46 | 47 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC); 48 | RenderSystem.loadIdentity(); 49 | RenderSystem.ortho(0.0, window.getFramebufferWidth(), window.getFramebufferHeight(), 0.0, 1000.0, 3000.0); 50 | RenderSystem.loadIdentity(); 51 | RenderSystem.translatef(0.0F, 0.0F, 0.0F); 52 | DiffuseLighting.disableGuiDepthLighting(); 53 | 54 | SeedQueueProfiler.push("matrix"); 55 | // see GameRenderer#renderWorld 56 | MatrixStack rotationMatrix = new MatrixStack(); 57 | rotationMatrix.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(camera.getPitch())); 58 | rotationMatrix.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(camera.getYaw() + 180.0f)); 59 | 60 | Matrix4f projectionMatrix = new Matrix4f(); 61 | client.gameRenderer.loadProjectionMatrix(projectionMatrix); 62 | 63 | SeedQueueProfiler.swap("camera_update"); 64 | camera.update(client.world, client.player, camera.isThirdPerson(), ((CameraAccessor) camera).seedQueue$isInverseView(), 1.0f); 65 | SeedQueueProfiler.swap("build_chunks"); 66 | ((SQWorldRenderer) client.worldRenderer).seedQueue$buildChunks(rotationMatrix, camera, projectionMatrix); 67 | SeedQueueProfiler.pop(); 68 | 69 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC); 70 | RenderSystem.matrixMode(5889); 71 | RenderSystem.loadIdentity(); 72 | RenderSystem.ortho(0.0D, (double) window.getFramebufferWidth() / window.getScaleFactor(), (double) window.getFramebufferHeight() / window.getScaleFactor(), 0.0D, 1000.0D, 3000.0D); 73 | RenderSystem.matrixMode(5888); 74 | RenderSystem.loadIdentity(); 75 | RenderSystem.translatef(0.0F, 0.0F, -2000.0F); 76 | DiffuseLighting.enableGuiDepthLighting(); 77 | RenderSystem.defaultAlphaFunc(); 78 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC); 79 | } 80 | 81 | public void loadPlayerModelParts() { 82 | // see WorldPreview#configure 83 | int playerModelPartsBitMask = 0; 84 | for (PlayerModelPart playerModelPart : MinecraftClient.getInstance().options.getEnabledPlayerModelParts()) { 85 | playerModelPartsBitMask |= playerModelPart.getBitFlag(); 86 | } 87 | this.player.getDataTracker().set(PlayerEntityAccessor.seedQueue$getPLAYER_MODEL_PARTS(), (byte) playerModelPartsBitMask); 88 | } 89 | 90 | public void load() { 91 | WorldPreview.set(this.world, this.player, this.interactionManager, this.camera, this.packetQueue); 92 | } 93 | 94 | @Override 95 | protected int getDataLimit() { 96 | // Don't allow Unlimited (=100) on wall 97 | return Math.min(99, super.getDataLimit()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/SeedQueueSettingsCache.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.seedqueue.SeedQueueEntry; 5 | import me.contaria.seedqueue.mixin.accessor.PlayerEntityAccessor; 6 | import me.contaria.standardsettings.StandardSettingsCache; 7 | import me.contaria.standardsettings.options.PlayerModelPartStandardSetting; 8 | import me.contaria.standardsettings.options.StandardSetting; 9 | import net.minecraft.client.network.ClientPlayerEntity; 10 | 11 | import java.util.*; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Saves settings when a preview is first rendered to load them when the corresponding {@link SeedQueueEntry} is entered. 16 | *

17 | * This is to achieve parity with multi instance settings changing. 18 | * If settings wouldn't get cached it would allow people to view preview A, then enter preview B, 19 | * change settings (for example the piechart) to values best suited for preview A, leave preview B and then enter preview A with optimal settings. 20 | */ 21 | public class SeedQueueSettingsCache extends StandardSettingsCache { 22 | // a set of all setting id's that affect preview rendering on wall 23 | // while language and forceUnicodeFont also affect previews, they require reloading of some resources which is not feasible 24 | // does not include perspective since it is special cased 25 | // see SeedQueuePreviewProperties#getPerspective 26 | private static final Set PREVIEW_SETTINGS = new HashSet<>(Arrays.asList( 27 | "biomeBlendRadius", 28 | "graphicsMode", 29 | "renderDistance", 30 | "ao", 31 | "guiScale", 32 | "attackIndicator", 33 | "gamma", 34 | "renderClouds", 35 | "particles", 36 | "mipmapLevels", 37 | "entityShadows", 38 | "entityDistanceScaling", 39 | "textBackgroundOpacity", 40 | "backgroundForChatOnly", 41 | "hitboxes", 42 | "chunkborders", 43 | "f1" 44 | )); 45 | 46 | // ignored settings dont get saved in seedqueue settings caches 47 | // fullscreen is ignored since it can also be changed by macros at any point and is quite annoying when it is changed during a session 48 | private static final Set IGNORED_SETTINGS = new HashSet<>(Collections.singletonList( 49 | "fullscreen" 50 | )); 51 | 52 | private final Set> previewSettings; 53 | 54 | private SeedQueueSettingsCache() { 55 | super(null); 56 | 57 | this.cache.removeIf(entry -> IGNORED_SETTINGS.contains(entry.setting.getID())); 58 | 59 | this.previewSettings = new HashSet<>(PREVIEW_SETTINGS.size()); 60 | for (Entry entry : this.cache) { 61 | if (PREVIEW_SETTINGS.contains(entry.setting.getID())) { 62 | this.previewSettings.add(entry); 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Loads only settings that affect preview rendering. 69 | * 70 | * @see SeedQueueSettingsCache#PREVIEW_SETTINGS 71 | */ 72 | public void loadPreview() { 73 | this.previewSettings.forEach(Entry::load); 74 | } 75 | 76 | /** 77 | * Loads player model part settings onto the given {@link ClientPlayerEntity}. 78 | */ 79 | public void loadPlayerModelParts(ClientPlayerEntity player) { 80 | int playerModelPartsBitMask = 0; 81 | for (Entry entry : this.cache) { 82 | if (entry.setting instanceof PlayerModelPartStandardSetting && Boolean.TRUE.equals(entry.value)) { 83 | playerModelPartsBitMask |= ((PlayerModelPartStandardSetting) entry.setting).playerModelPart.getBitFlag(); 84 | } 85 | } 86 | player.getDataTracker().set(PlayerEntityAccessor.seedQueue$getPLAYER_MODEL_PARTS(), (byte) playerModelPartsBitMask); 87 | } 88 | 89 | /** 90 | * @param option The options ID according to {@link StandardSetting#getID}. 91 | * @return The cached value for the given option. 92 | */ 93 | public Object getValue(String option) { 94 | for (Entry entry : this.cache) { 95 | if (option.equals(entry.setting.getID())) { 96 | return entry.value; 97 | } 98 | } 99 | return null; 100 | } 101 | 102 | /** 103 | * @return True if the cached settings match the current settings. 104 | */ 105 | public boolean isCurrentSettings() { 106 | for (Entry entry : this.cache) { 107 | if (!Objects.equals(entry.value, entry.setting.getOption())) { 108 | return false; 109 | } 110 | } 111 | return true; 112 | } 113 | 114 | /** 115 | * Searches all {@link SeedQueueEntry}s and returns the first {@link SeedQueueSettingsCache} matching the current settings or creates a new cache if it doesn't find one. 116 | * 117 | * @return A {@link SeedQueueSettingsCache} matching the current settings. 118 | * @see SeedQueueSettingsCache#isCurrentSettings 119 | */ 120 | public static SeedQueueSettingsCache create() { 121 | for (SeedQueueSettingsCache settingsCache : SeedQueue.getEntries().stream().map(SeedQueueEntry::getSettingsCache).filter(Objects::nonNull).collect(Collectors.toSet())) { 122 | if (settingsCache.isCurrentSettings()) { 123 | return settingsCache; 124 | } 125 | } 126 | return new SeedQueueSettingsCache(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/SodiumCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; 4 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkFogMode; 5 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkProgram; 6 | 7 | import java.util.*; 8 | 9 | public class SodiumCompat { 10 | public static final Map WALL_SHADER_CACHE = new EnumMap<>(ChunkFogMode.class); 11 | public static final List WALL_BUILD_BUFFERS_POOL = Collections.synchronizedList(new ArrayList<>()); 12 | 13 | static void clearShaderCache() { 14 | for (ChunkProgram program : WALL_SHADER_CACHE.values()) { 15 | program.delete(); 16 | } 17 | WALL_SHADER_CACHE.clear(); 18 | } 19 | 20 | static void clearBuildBufferPool() { 21 | WALL_BUILD_BUFFERS_POOL.clear(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/StandardSettingsCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import me.contaria.standardsettings.StandardSettings; 4 | 5 | class StandardSettingsCompat { 6 | 7 | static void reset() { 8 | StandardSettings.reset(); 9 | } 10 | 11 | static void createCache() { 12 | StandardSettings.createCache(); 13 | } 14 | 15 | static void resetPendingActions() { 16 | StandardSettings.resetPendingActions(); 17 | } 18 | 19 | static void loadCache() { 20 | StandardSettings.loadCache(StandardSettings.lastWorld); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/StateOutputCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import dev.tildejustin.stateoutput.State; 4 | import dev.tildejustin.stateoutput.StateOutputHelper; 5 | 6 | public class StateOutputCompat { 7 | static void setWallState() { 8 | StateOutputHelper.outputState(State.WALL); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/compat/WorldPreviewCompat.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.compat; 2 | 3 | import me.voidxwalker.worldpreview.WorldPreview; 4 | import me.voidxwalker.worldpreview.interfaces.WPMinecraftServer; 5 | import net.minecraft.server.MinecraftServer; 6 | 7 | public class WorldPreviewCompat { 8 | 9 | static boolean inPreview() { 10 | return WorldPreview.inPreview(); 11 | } 12 | 13 | static boolean kill(MinecraftServer server) { 14 | return ((WPMinecraftServer) server).worldpreview$kill(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.customization; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import net.minecraft.client.MinecraftClient; 5 | import net.minecraft.client.resource.metadata.AnimationResourceMetadata; 6 | import net.minecraft.util.Identifier; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.io.IOException; 10 | 11 | public class AnimatedTexture { 12 | private final Identifier id; 13 | @Nullable 14 | protected final AnimationResourceMetadata animation; 15 | 16 | protected AnimatedTexture(Identifier id) { 17 | this.id = id; 18 | AnimationResourceMetadata animation = null; 19 | try { 20 | animation = MinecraftClient.getInstance().getResourceManager().getResource(id).getMetadata(AnimationResourceMetadata.READER); 21 | } catch (IOException e) { 22 | SeedQueue.LOGGER.warn("Failed to read animation data for {}!", id, e); 23 | } 24 | this.animation = animation; 25 | } 26 | 27 | public Identifier getId() { 28 | return this.id; 29 | } 30 | 31 | public int getFrameIndex(int tick) { 32 | // does not currently support setting frametime for individual frames 33 | // see AnimationFrameResourceMetadata#usesDefaultFrameTime 34 | return this.animation != null ? this.animation.getFrameIndex((tick / this.animation.getDefaultFrameTime()) % this.animation.getFrameCount()) : 0; 35 | } 36 | 37 | public int getIndividualFrameCount() { 38 | return this.animation != null ? this.animation.getFrameIndexSet().size() : 1; 39 | } 40 | 41 | public static @Nullable AnimatedTexture of(Identifier id) { 42 | if (MinecraftClient.getInstance().getResourceManager().containsResource(id)) { 43 | return new AnimatedTexture(id); 44 | } 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/customization/LockTexture.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.customization; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.speedrunapi.util.IdentifierUtil; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.texture.NativeImage; 7 | import net.minecraft.util.Identifier; 8 | 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class LockTexture extends AnimatedTexture { 14 | private final int width; 15 | private final int height; 16 | 17 | public LockTexture(Identifier id) throws IOException { 18 | super(id); 19 | try (NativeImage image = NativeImage.read(MinecraftClient.getInstance().getResourceManager().getResource(id).getInputStream())) { 20 | this.width = image.getWidth(); 21 | this.height = image.getHeight() / (this.animation != null ? this.animation.getFrameIndexSet().size() : 1); 22 | } 23 | } 24 | 25 | public double getAspectRatio() { 26 | return (double) this.width / this.height; 27 | } 28 | 29 | public static List createLockTextures() { 30 | List lockTextures = new ArrayList<>(); 31 | Identifier lock = IdentifierUtil.of("seedqueue", "textures/gui/wall/lock.png"); 32 | do { 33 | try { 34 | lockTextures.add(new LockTexture(lock)); 35 | } catch (IOException e) { 36 | SeedQueue.LOGGER.warn("Failed to read lock image texture: {}", lock, e); 37 | } 38 | } while (MinecraftClient.getInstance().getResourceManager().containsResource(lock = IdentifierUtil.of("seedqueue", "textures/gui/wall/lock-" + lockTextures.size() + ".png"))); 39 | return lockTextures; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/debug/SeedQueueProfiler.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.debug; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.seedqueue.SeedQueueConfig; 5 | import net.minecraft.client.MinecraftClient; 6 | 7 | /** 8 | * Provides methods to profile rendering on the Wall Screen without affecting the profiler during vanilla gameplay. 9 | * It will only profile when called from the Render Thread while being on the Wall Screen with {@link SeedQueueConfig#showDebugMenu} enabled. 10 | */ 11 | public class SeedQueueProfiler { 12 | public static void push(String location) { 13 | if (shouldProfile()) { 14 | MinecraftClient.getInstance().getProfiler().push(location); 15 | } 16 | } 17 | 18 | public static void swap(String location) { 19 | if (shouldProfile()) { 20 | MinecraftClient.getInstance().getProfiler().swap(location); 21 | } 22 | } 23 | 24 | public static void pop() { 25 | if (shouldProfile()) { 26 | MinecraftClient.getInstance().getProfiler().pop(); 27 | } 28 | } 29 | 30 | private static boolean shouldProfile() { 31 | return MinecraftClient.getInstance().isOnThread() && SeedQueue.isOnWall() && SeedQueue.config.showDebugMenu; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/debug/SeedQueueSystemInfo.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.debug; 2 | 3 | import com.google.gson.GsonBuilder; 4 | import com.sun.management.OperatingSystemMXBean; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import org.lwjgl.opengl.GL11; 7 | 8 | import java.lang.management.ManagementFactory; 9 | 10 | public class SeedQueueSystemInfo { 11 | public static void logSystemInformation() { 12 | if (Boolean.parseBoolean(System.getProperty("seedqueue.logSystemInfo", "true"))) { 13 | SeedQueue.LOGGER.info("System Information (Logged by SeedQueue):"); 14 | SeedQueue.LOGGER.info("Operating System: {}", System.getProperty("os.name")); 15 | SeedQueue.LOGGER.info("OS Version: {}", System.getProperty("os.version")); 16 | SeedQueue.LOGGER.info("CPU: {}", getCpuInfo()); 17 | SeedQueue.LOGGER.info("GPU: {}", getGpuInfo()); 18 | SeedQueue.LOGGER.info("Java Version: {}", System.getProperty("java.version")); 19 | SeedQueue.LOGGER.info("JVM Arguments: {}", getJavaArguments()); 20 | SeedQueue.LOGGER.info("Total Physical Memory (MB): {}", getTotalPhysicalMemory()); 21 | SeedQueue.LOGGER.info("Max Memory (MB): {}", getMaxAllocatedMemory()); 22 | SeedQueue.LOGGER.info("Total Processors: {}", getTotalPhysicalProcessors()); 23 | SeedQueue.LOGGER.info("Available Processors: {}", getAvailableProcessors()); 24 | } 25 | } 26 | 27 | private static String getCpuInfo() { 28 | // see GLX#_init 29 | oshi.hardware.Processor[] processors = new oshi.SystemInfo().getHardware().getProcessors(); 30 | return String.format("%dx %s", processors.length, processors[0]).replaceAll("\\s+", " "); 31 | } 32 | 33 | private static String getGpuInfo() { 34 | // see GlDebugInfo#getRenderer 35 | return GL11.glGetString(GL11.GL_RENDERER); 36 | } 37 | 38 | private static String getJavaArguments() { 39 | // Logs the java arguments being used by the JVM 40 | return String.join(" ", ManagementFactory.getRuntimeMXBean().getInputArguments()); 41 | } 42 | 43 | private static long getTotalPhysicalMemory() { 44 | // Logs the total RAM on the system 45 | return ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class).getTotalPhysicalMemorySize() / (1024 * 1024); 46 | } 47 | 48 | private static long getMaxAllocatedMemory() { 49 | // Logs the max RAM the JVM will try to use 50 | return Runtime.getRuntime().maxMemory() / (1024 * 1024); 51 | } 52 | 53 | private static int getTotalPhysicalProcessors() { 54 | // Logs the total number of processors on the system 55 | // also includes the ones which are affected by affinity 56 | return ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); 57 | } 58 | 59 | private static int getAvailableProcessors() { 60 | // Logs the available number of processors 61 | // excludes the ones which are affected by affinity 62 | return Runtime.getRuntime().availableProcessors(); 63 | } 64 | 65 | public static void logConfigSettings() { 66 | if (Boolean.parseBoolean(System.getProperty("seedqueue.logConfigSettings", "true"))) { 67 | SeedQueue.LOGGER.info("SeedQueue Config settings: {}", new GsonBuilder().setPrettyPrinting().create().toJson(SeedQueue.config.container.toJson())); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/debug/SeedQueueWatchdog.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.debug; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.seedqueue.mixin.accessor.MinecraftClientAccessor; 5 | import net.minecraft.client.MinecraftClient; 6 | 7 | import java.util.Arrays; 8 | 9 | public class SeedQueueWatchdog { 10 | private static final Object LOCK = new Object(); 11 | private static volatile Thread watchdog = null; 12 | 13 | public static void start() { 14 | if (!SeedQueue.isActive()) { 15 | throw new IllegalStateException("Tried to stop SeedQueue Watchdog while SeedQueue isn't running!"); 16 | } 17 | if (SeedQueue.config.useWatchdog && watchdog == null) { 18 | watchdog = createWatchdog(); 19 | } 20 | } 21 | 22 | public static void stop() { 23 | if (SeedQueue.isActive()) { 24 | throw new IllegalStateException("Tried to stop SeedQueue Watchdog while SeedQueue is still running!"); 25 | } 26 | if (watchdog == null) { 27 | return; 28 | } 29 | synchronized (LOCK) { 30 | LOCK.notify(); 31 | } 32 | try { 33 | watchdog.join(); 34 | } catch (InterruptedException e) { 35 | SeedQueue.LOGGER.warn("SeedQueue Watchdog was interrupted while stopping!"); 36 | } 37 | watchdog = null; 38 | } 39 | 40 | private static Thread createWatchdog() { 41 | Thread thread = new Thread(() -> { 42 | try { 43 | while (SeedQueue.isActive()) { 44 | synchronized (LOCK) { 45 | Thread sqThread = SeedQueue.getThread(); 46 | if (sqThread != null) { 47 | SeedQueue.LOGGER.info("WATCHDOG | Main: {}", Arrays.toString(((MinecraftClientAccessor) MinecraftClient.getInstance()).seedQueue$getThread().getStackTrace())); 48 | SeedQueue.LOGGER.info("WATCHDOG | SeedQueue: {}", Arrays.toString(sqThread.getStackTrace())); 49 | } 50 | LOCK.wait(10000); 51 | } 52 | } 53 | } catch (InterruptedException e) { 54 | SeedQueue.LOGGER.warn("SeedQueue Watchdog was interrupted while running!"); 55 | } 56 | }); 57 | thread.setDaemon(true); 58 | thread.setPriority(3); 59 | thread.setName("SeedQueue Watchdog"); 60 | thread.start(); 61 | return thread; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/gui/SeedQueueCrashToast.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.gui; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.toast.Toast; 5 | import net.minecraft.client.toast.ToastManager; 6 | import net.minecraft.client.util.math.MatrixStack; 7 | import net.minecraft.text.StringRenderable; 8 | import net.minecraft.text.Text; 9 | 10 | import java.util.List; 11 | 12 | public class SeedQueueCrashToast implements Toast { 13 | private final Text title; 14 | private final List description; 15 | private boolean hasStarted; 16 | private long startTime = Long.MAX_VALUE; 17 | 18 | public SeedQueueCrashToast(Text title, Text description) { 19 | this.title = title; 20 | this.description = MinecraftClient.getInstance().textRenderer.wrapLines(description, this.getWidth() - 7); 21 | } 22 | 23 | @Override 24 | public Visibility draw(MatrixStack matrices, ToastManager manager, long startTime) { 25 | if (!this.hasStarted && manager.getGame().isWindowFocused()) { 26 | this.startTime = startTime; 27 | this.hasStarted = true; 28 | } 29 | 30 | manager.getGame().getTextureManager().bindTexture(TOASTS_TEX); 31 | if (this.description.size() < 2) { 32 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), this.getHeight()); 33 | } else { 34 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), 11); 35 | int y = 8; 36 | for (int i = 0; i < this.description.size(); i++) { 37 | manager.drawTexture(matrices, 0, y, 0, 11, this.getWidth(), 10); 38 | y += 10; 39 | } 40 | manager.drawTexture(matrices, 0, y, 0, 21, this.getWidth(), 11); 41 | } 42 | 43 | manager.getGame().textRenderer.draw(matrices, this.title, 7.0f, 7.0f, 0xFFFF00 | 0xFF000000); 44 | 45 | float y = 18.0f; 46 | for (StringRenderable description : this.description) { 47 | manager.getGame().textRenderer.draw(matrices, description, 7.0f, y, -1); 48 | y += 10.0f; 49 | } 50 | 51 | return startTime - this.startTime < 5000L ? Toast.Visibility.SHOW : Toast.Visibility.HIDE; 52 | } 53 | 54 | @Override 55 | public int getHeight() { 56 | return Toast.super.getHeight() + Math.max(1, this.description.size() - 1) * 10; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/gui/config/SeedQueueKeyButtonWidget.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.gui.config; 2 | 3 | import me.contaria.speedrunapi.util.TextUtil; 4 | import net.minecraft.client.gui.widget.AbstractPressableButtonWidget; 5 | import net.minecraft.client.util.InputUtil; 6 | import net.minecraft.text.MutableText; 7 | import net.minecraft.text.Text; 8 | 9 | public class SeedQueueKeyButtonWidget extends AbstractPressableButtonWidget { 10 | private static final Text UNKNOWN_KEY = InputUtil.UNKNOWN_KEY.getLocalizedText(); 11 | 12 | private final SeedQueueKeybindingsListWidget.KeyEntry entry; 13 | 14 | public SeedQueueKeyButtonWidget(SeedQueueKeybindingsListWidget.KeyEntry entry) { 15 | this(entry, UNKNOWN_KEY); 16 | } 17 | 18 | public SeedQueueKeyButtonWidget(SeedQueueKeybindingsListWidget.KeyEntry entry, Text message) { 19 | super(0, 0, 75, 20, message); 20 | this.entry = entry; 21 | } 22 | 23 | @Override 24 | public void onPress() { 25 | this.entry.selectButton(this); 26 | } 27 | 28 | @Override 29 | protected MutableText getNarrationMessage() { 30 | if (UNKNOWN_KEY.equals(this.getMessage())) { 31 | return TextUtil.translatable("narrator.controls.unbound", this.entry.title); 32 | } 33 | return TextUtil.translatable("narrator.controls.bound", this.entry.title, this.getMessage()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/gui/config/SeedQueueKeybindingsScreen.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.gui.config; 2 | 3 | import me.contaria.seedqueue.keybindings.SeedQueueMultiKeyBinding; 4 | import me.contaria.speedrunapi.util.TextUtil; 5 | import net.minecraft.client.gui.screen.Screen; 6 | import net.minecraft.client.gui.screen.ScreenTexts; 7 | import net.minecraft.client.gui.widget.ButtonWidget; 8 | import net.minecraft.client.util.InputUtil; 9 | import net.minecraft.client.util.math.MatrixStack; 10 | import org.lwjgl.glfw.GLFW; 11 | 12 | public class SeedQueueKeybindingsScreen extends Screen { 13 | private final Screen parent; 14 | protected final SeedQueueMultiKeyBinding[] keyBindings; 15 | protected SeedQueueKeybindingsListWidget.KeyEntry focusedBinding; 16 | private SeedQueueKeybindingsListWidget keyBindingListWidget; 17 | 18 | public SeedQueueKeybindingsScreen(Screen parent, SeedQueueMultiKeyBinding... keyBindings) { 19 | super(TextUtil.translatable("seedqueue.menu.keys")); 20 | this.parent = parent; 21 | this.keyBindings = keyBindings; 22 | } 23 | 24 | @Override 25 | protected void init() { 26 | this.keyBindingListWidget = new SeedQueueKeybindingsListWidget(this, this.client); 27 | this.children.add(this.keyBindingListWidget); 28 | this.addButton(new ButtonWidget(this.width / 2 - 100, this.height - 27, 200, 20, ScreenTexts.DONE, button -> this.onClose())); 29 | } 30 | 31 | @Override 32 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 33 | if (this.focusedBinding != null) { 34 | this.focusedBinding.pressKey(InputUtil.Type.MOUSE.createFromCode(button)); 35 | this.focusedBinding = null; 36 | return true; 37 | } 38 | return super.mouseClicked(mouseX, mouseY, button); 39 | } 40 | 41 | @Override 42 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 43 | if (this.focusedBinding != null) { 44 | this.focusedBinding.pressKey(keyCode == GLFW.GLFW_KEY_ESCAPE ? InputUtil.UNKNOWN_KEY : InputUtil.fromKeyCode(keyCode, scanCode)); 45 | this.focusedBinding = null; 46 | return true; 47 | } 48 | return super.keyPressed(keyCode, scanCode, modifiers); 49 | } 50 | 51 | @Override 52 | public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { 53 | this.renderBackground(matrices); 54 | this.keyBindingListWidget.render(matrices, mouseX, mouseY, delta); 55 | this.drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 10, 0xFFFFFF); 56 | super.render(matrices, mouseX, mouseY, delta); 57 | } 58 | 59 | @Override 60 | public void onClose() { 61 | assert this.client != null; 62 | this.client.openScreen(this.parent); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/gui/config/SeedQueueWindowSizeWidget.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.gui.config; 2 | 3 | import me.contaria.seedqueue.SeedQueueConfig; 4 | import me.contaria.speedrunapi.util.TextUtil; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.gui.Element; 7 | import net.minecraft.client.gui.ParentElement; 8 | import net.minecraft.client.gui.widget.AbstractButtonWidget; 9 | import net.minecraft.client.gui.widget.TextFieldWidget; 10 | import net.minecraft.client.util.math.MatrixStack; 11 | import net.minecraft.text.StringRenderable; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | public class SeedQueueWindowSizeWidget extends AbstractButtonWidget implements ParentElement { 19 | private static final StringRenderable X = StringRenderable.plain("X"); 20 | 21 | private final SeedQueueConfig.WindowSize windowSize; 22 | private final TextFieldWidget widthWidget; 23 | private final TextFieldWidget heightWidget; 24 | 25 | @Nullable 26 | private Element focused; 27 | private boolean isDragging; 28 | 29 | public SeedQueueWindowSizeWidget(SeedQueueConfig.WindowSize windowSize) { 30 | super(0, 0, 150, 20, TextUtil.empty()); 31 | this.windowSize = windowSize; 32 | this.widthWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 65, 20, TextUtil.empty()); 33 | this.widthWidget.setText(String.valueOf(this.windowSize.width())); 34 | this.widthWidget.setChangedListener(text -> { 35 | if (text.isEmpty()) { 36 | // set width to 0 without updating the text 37 | this.windowSize.setWidth(0); 38 | return; 39 | } 40 | this.windowSize.setWidth(Integer.parseUnsignedInt(text)); 41 | String newText = String.valueOf(this.windowSize.width()); 42 | if (!text.equals(newText)) { 43 | this.widthWidget.setText(newText); 44 | } 45 | }); 46 | this.widthWidget.setTextPredicate(text -> { 47 | try { 48 | return text.isEmpty() || Integer.parseUnsignedInt(text) >= 0; 49 | } catch (NumberFormatException e) { 50 | return false; 51 | } 52 | }); 53 | this.heightWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 65, 20, TextUtil.empty()); 54 | this.heightWidget.setText(String.valueOf(this.windowSize.height())); 55 | this.heightWidget.setChangedListener(text -> { 56 | if (text.isEmpty()) { 57 | this.windowSize.setHeight(0); 58 | return; 59 | } 60 | this.windowSize.setHeight(Integer.parseUnsignedInt(text)); 61 | String newText = String.valueOf(this.windowSize.height()); 62 | if (!text.equals(newText)) { 63 | this.heightWidget.setText(newText); 64 | } 65 | }); 66 | this.heightWidget.setTextPredicate(text -> { 67 | try { 68 | return text.isEmpty() || Integer.parseUnsignedInt(text) >= 0; 69 | } catch (NumberFormatException e) { 70 | return false; 71 | } 72 | }); 73 | } 74 | 75 | @Override 76 | public Optional hoveredElement(double mouseX, double mouseY) { 77 | return ParentElement.super.hoveredElement(mouseX, mouseY); 78 | } 79 | 80 | @Override 81 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 82 | return ParentElement.super.mouseClicked(mouseX, mouseY, button); 83 | } 84 | 85 | @Override 86 | public boolean mouseReleased(double mouseX, double mouseY, int button) { 87 | return ParentElement.super.mouseReleased(mouseX, mouseY, button); 88 | } 89 | 90 | @Override 91 | public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { 92 | return ParentElement.super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); 93 | } 94 | 95 | @Override 96 | public boolean mouseScrolled(double mouseX, double mouseY, double amount) { 97 | return ParentElement.super.mouseScrolled(mouseX, mouseY, amount); 98 | } 99 | 100 | @Override 101 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 102 | return ParentElement.super.keyPressed(keyCode, scanCode, modifiers); 103 | } 104 | 105 | @Override 106 | public boolean keyReleased(int keyCode, int scanCode, int modifiers) { 107 | return ParentElement.super.keyReleased(keyCode, scanCode, modifiers); 108 | } 109 | 110 | @Override 111 | public boolean charTyped(char chr, int keyCode) { 112 | return ParentElement.super.charTyped(chr, keyCode); 113 | } 114 | 115 | @Override 116 | public void setInitialFocus(@Nullable Element element) { 117 | ParentElement.super.setInitialFocus(element); 118 | } 119 | 120 | @Override 121 | public void focusOn(@Nullable Element element) { 122 | ParentElement.super.focusOn(element); 123 | } 124 | 125 | @Override 126 | public boolean changeFocus(boolean lookForwards) { 127 | return ParentElement.super.changeFocus(lookForwards); 128 | } 129 | 130 | @Override 131 | public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { 132 | this.widthWidget.x = this.x; 133 | this.widthWidget.y = this.y; 134 | this.widthWidget.render(matrices, mouseX, mouseY, delta); 135 | this.drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, X, this.x + this.width / 2, this.y + (this.height - MinecraftClient.getInstance().textRenderer.fontHeight) / 2, 0xFFFFFF); 136 | this.heightWidget.x = this.x + 85; 137 | this.heightWidget.y = this.y; 138 | this.heightWidget.render(matrices, mouseX, mouseY, delta); 139 | } 140 | 141 | @Override 142 | public List children() { 143 | List children = new ArrayList<>(); 144 | children.add(this.widthWidget); 145 | children.add(this.heightWidget); 146 | return children; 147 | } 148 | 149 | @Override 150 | public final boolean isDragging() { 151 | return this.isDragging; 152 | } 153 | 154 | @Override 155 | public final void setDragging(boolean dragging) { 156 | this.isDragging = dragging; 157 | } 158 | 159 | @Override 160 | @Nullable 161 | public Element getFocused() { 162 | return this.focused; 163 | } 164 | 165 | @Override 166 | public void setFocused(@Nullable Element focused) { 167 | this.focused = focused; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueBenchmarkToast.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.gui.wall; 2 | 3 | import me.contaria.speedrunapi.util.TextUtil; 4 | import net.minecraft.client.toast.Toast; 5 | import net.minecraft.client.toast.ToastManager; 6 | import net.minecraft.client.util.math.MatrixStack; 7 | import net.minecraft.text.Text; 8 | import net.minecraft.util.Util; 9 | 10 | public class SeedQueueBenchmarkToast implements Toast { 11 | private final SeedQueueWallScreen wall; 12 | private final Text title; 13 | 14 | private boolean finished; 15 | private boolean fadeOut; 16 | 17 | public SeedQueueBenchmarkToast(SeedQueueWallScreen wall) { 18 | this.wall = wall; 19 | this.title = TextUtil.translatable("seedqueue.menu.benchmark.title"); 20 | } 21 | 22 | @Override 23 | public Visibility draw(MatrixStack matrices, ToastManager manager, long startTime) { 24 | manager.getGame().getTextureManager().bindTexture(TOASTS_TEX); 25 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), this.getHeight()); 26 | manager.getGame().textRenderer.draw(matrices, this.title, 7.0f, 7.0f, 0xFFFF00 | 0xFF000000); 27 | 28 | this.finished |= !this.wall.isBenchmarking(); 29 | 30 | if (this.finished && !this.fadeOut && !this.wall.showFinishedBenchmarkResults) { 31 | this.fadeOut = true; 32 | } 33 | 34 | double time = (this.finished ? this.wall.benchmarkFinish : Util.getMeasuringTimeMs()) - this.wall.benchmarkStart; 35 | double rps = Math.round(this.wall.benchmarkedSeeds / (time / 10000.0)) / 10.0; 36 | manager.getGame().textRenderer.draw(matrices, TextUtil.translatable("seedqueue.menu.benchmark.result", this.wall.benchmarkedSeeds, Math.round(time / 1000.0), rps), 7.0f, 18.0f, -1); 37 | 38 | return this.fadeOut ? Visibility.HIDE : Visibility.SHOW; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/SQMinecraftServer.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces; 2 | 3 | import me.contaria.seedqueue.SeedQueueEntry; 4 | 5 | import java.util.Optional; 6 | import java.util.concurrent.Executor; 7 | 8 | public interface SQMinecraftServer { 9 | 10 | Optional seedQueue$getEntry(); 11 | 12 | boolean seedQueue$inQueue(); 13 | 14 | void seedQueue$setEntry(SeedQueueEntry entry); 15 | 16 | void seedQueue$tryPausingServer(); 17 | 18 | boolean seedQueue$shouldPause(); 19 | 20 | boolean seedQueue$isPaused(); 21 | 22 | boolean seedQueue$isScheduledToPause(); 23 | 24 | void seedQueue$schedulePause(); 25 | 26 | void seedQueue$unpause(); 27 | 28 | boolean seedQueue$isDiscarded(); 29 | 30 | void seedQueue$setExecutor(Executor executor); 31 | 32 | void seedQueue$resetExecutor(); 33 | 34 | int seedQueue$incrementAndGetEntityID(); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/SQSoundManager.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces; 2 | 3 | public interface SQSoundManager { 4 | void seedQueue$stopAllExceptSeedQueueSounds(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/SQSoundSystem.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces; 2 | 3 | public interface SQSoundSystem { 4 | void seedQueue$stopAllExceptSeedQueueSounds(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/SQWorldGenerationProgressLogger.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces; 2 | 3 | public interface SQWorldGenerationProgressLogger { 4 | 5 | void seedQueue$mute(); 6 | 7 | void seedQueue$unmute(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/SQWorldGenerationProgressTracker.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces; 2 | 3 | import net.minecraft.client.gui.WorldGenerationProgressTracker; 4 | 5 | import java.util.Optional; 6 | 7 | public interface SQWorldGenerationProgressTracker { 8 | /** 9 | * @return a copy of the WorldGenerationProgressTracker which was made after the milliseconds provided by seedQueue$makeCopyAfter 10 | */ 11 | boolean seedQueue$shouldFreeze(); 12 | /** 13 | * @return a copy of the WorldGenerationProgressTracker which was made after the milliseconds provided by seedQueue$makeCopyAfter 14 | */ 15 | Optional seedQueue$getFrozenCopy(); 16 | 17 | /** 18 | * Calculates a more accurate progress percentage by counting chunks in rings and only including outer layers if inner layers are complete. This tries to address the effect ocean spawns have on inflating the progress percentage in vanilla's {@link net.minecraft.server.WorldGenerationProgressLogger} 19 | * 20 | * @return The percent of spawn chunks that are generated, in the range of 0 to 100. 21 | */ 22 | int seedQueue$getProgressPercentage(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/sodium/SQChunkBuilder$WorkerRunnable.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces.sodium; 2 | 3 | import net.minecraft.world.World; 4 | 5 | public interface SQChunkBuilder$WorkerRunnable { 6 | 7 | void seedQueue$setWorldForRenderCache(World world); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/interfaces/worldpreview/SQWorldRenderer.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.interfaces.worldpreview; 2 | 3 | import net.minecraft.client.render.Camera; 4 | import net.minecraft.client.util.math.MatrixStack; 5 | import net.minecraft.util.math.Matrix4f; 6 | 7 | public interface SQWorldRenderer { 8 | 9 | void seedQueue$buildChunks(MatrixStack matrices, Camera camera, Matrix4f projectionMatrix); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/keybindings/SeedQueueKeyBindings.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.keybindings; 2 | 3 | import org.lwjgl.glfw.GLFW; 4 | 5 | public class SeedQueueKeyBindings { 6 | public static final SeedQueueMultiKeyBinding play = new SeedQueueMultiKeyBinding("seedqueue.key.play", GLFW.GLFW_KEY_R); 7 | public static final SeedQueueMultiKeyBinding lock = new SeedQueueMultiKeyBinding("seedqueue.key.lock", GLFW.GLFW_KEY_L); 8 | public static final SeedQueueMultiKeyBinding reset = new SeedQueueMultiKeyBinding("seedqueue.key.reset", GLFW.GLFW_KEY_E); 9 | public static final SeedQueueMultiKeyBinding resetAll = new SeedQueueMultiKeyBinding("seedqueue.key.resetAll", GLFW.GLFW_KEY_T); 10 | public static final SeedQueueMultiKeyBinding focusReset = new SeedQueueMultiKeyBinding("seedqueue.key.focusReset", GLFW.GLFW_KEY_F); 11 | public static final SeedQueueMultiKeyBinding resetColumn = new SeedQueueMultiKeyBinding("seedqueue.key.resetColumn"); 12 | public static final SeedQueueMultiKeyBinding resetRow = new SeedQueueMultiKeyBinding("seedqueue.key.resetRow"); 13 | public static final SeedQueueMultiKeyBinding playNextLock = new SeedQueueMultiKeyBinding("seedqueue.key.playNextLock"); 14 | public static final SeedQueueMultiKeyBinding scheduleJoin = new SeedQueueMultiKeyBinding("seedqueue.key.scheduleJoin"); 15 | public static final SeedQueueMultiKeyBinding scheduleAll = new SeedQueueMultiKeyBinding("seedqueue.key.scheduleAll"); 16 | public static final SeedQueueMultiKeyBinding startBenchmark = new SeedQueueMultiKeyBinding("seedqueue.key.startBenchmark"); 17 | public static final SeedQueueMultiKeyBinding cancelBenchmark = new SeedQueueMultiKeyBinding("seedqueue.key.cancelBenchmark"); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/keybindings/SeedQueueMultiKeyBinding.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.keybindings; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonPrimitive; 7 | import net.minecraft.client.MinecraftClient; 8 | import net.minecraft.client.util.InputUtil; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class SeedQueueMultiKeyBinding { 15 | private final String translationKey; 16 | private final String category; 17 | 18 | private InputUtil.Key primaryKey; 19 | private final List secondaryKeys; 20 | private final List blockingKeys; 21 | 22 | public SeedQueueMultiKeyBinding(String translationKey) { 23 | this(translationKey, "seedqueue.key.categories.builtin"); 24 | } 25 | 26 | public SeedQueueMultiKeyBinding(String translationKey, String category) { 27 | this(translationKey, category, InputUtil.UNKNOWN_KEY); 28 | } 29 | 30 | public SeedQueueMultiKeyBinding(String translationKey, int code) { 31 | this(translationKey, "seedqueue.key.categories.builtin", code); 32 | } 33 | 34 | public SeedQueueMultiKeyBinding(String translationKey, String category, int code) { 35 | this(translationKey, category, InputUtil.Type.KEYSYM, code); 36 | } 37 | 38 | public SeedQueueMultiKeyBinding(String translationKey, String category, InputUtil.Type type, int code) { 39 | this(translationKey, category, type.createFromCode(code)); 40 | } 41 | 42 | protected SeedQueueMultiKeyBinding(String translationKey, String category, InputUtil.Key primaryKey) { 43 | this.translationKey = translationKey; 44 | this.category = category; 45 | this.primaryKey = primaryKey; 46 | this.secondaryKeys = new ArrayList<>(); 47 | this.blockingKeys = new ArrayList<>(); 48 | } 49 | 50 | public boolean matchesKey(int keyCode, int scanCode) { 51 | return keyCode == InputUtil.UNKNOWN_KEY.getCode() ? this.matchesPrimary(InputUtil.Type.SCANCODE, scanCode) : this.matchesPrimary(InputUtil.Type.KEYSYM, keyCode) && this.areSecondaryKeysDown() && this.areBlockingKeysNotDown(); 52 | } 53 | 54 | public boolean matchesMouse(int code) { 55 | return this.matchesPrimary(InputUtil.Type.MOUSE, code) && this.areSecondaryKeysDown() && this.areBlockingKeysNotDown(); 56 | } 57 | 58 | private boolean matchesPrimary(InputUtil.Type type, int code) { 59 | return this.primaryKey.getCategory() == type && this.primaryKey.getCode() == code; 60 | } 61 | 62 | private boolean areSecondaryKeysDown() { 63 | for (InputUtil.Key key : this.secondaryKeys) { 64 | if (!InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), key.getCode())) { 65 | return false; 66 | } 67 | } 68 | return true; 69 | } 70 | 71 | private boolean areBlockingKeysNotDown() { 72 | for (InputUtil.Key key : this.blockingKeys) { 73 | if (InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), key.getCode())) { 74 | return false; 75 | } 76 | } 77 | return true; 78 | } 79 | 80 | public String getTranslationKey() { 81 | return this.translationKey; 82 | } 83 | 84 | public String getCategory() { 85 | return this.category; 86 | } 87 | 88 | public InputUtil.Key getPrimaryKey() { 89 | return this.primaryKey; 90 | } 91 | 92 | public void setPrimaryKey(InputUtil.Key key) { 93 | this.primaryKey = key; 94 | } 95 | 96 | public void setSecondaryKey(int index, InputUtil.Key key) { 97 | this.secondaryKeys.set(index, key); 98 | } 99 | 100 | public void addSecondaryKey(InputUtil.Key key) { 101 | this.secondaryKeys.add(key); 102 | } 103 | 104 | public void removeSecondaryKey(int index) { 105 | 106 | this.secondaryKeys.remove(index); 107 | } 108 | 109 | public List getSecondaryKeys() { 110 | return this.secondaryKeys; 111 | } 112 | 113 | public void setBlockingKey(int index, InputUtil.Key key) { 114 | this.blockingKeys.set(index, key); 115 | } 116 | 117 | public void addBlockingKey(InputUtil.Key key) { 118 | this.blockingKeys.add(key); 119 | } 120 | 121 | public void removeBlockingKey(int index) { 122 | this.blockingKeys.remove(index); 123 | } 124 | 125 | public List getBlockingKeys() { 126 | return this.blockingKeys; 127 | } 128 | 129 | public JsonElement toJson() { 130 | if (this.secondaryKeys.isEmpty() && this.blockingKeys.isEmpty()) { 131 | return new JsonPrimitive(this.primaryKey.getTranslationKey()); 132 | } 133 | 134 | JsonObject jsonObject = new JsonObject(); 135 | jsonObject.add("primary", new JsonPrimitive(this.primaryKey.getTranslationKey())); 136 | 137 | JsonArray secondary = new JsonArray(); 138 | for (InputUtil.Key key : this.secondaryKeys) { 139 | secondary.add(new JsonPrimitive(key.getTranslationKey())); 140 | } 141 | jsonObject.add("secondary", secondary); 142 | 143 | JsonArray blocking = new JsonArray(); 144 | for (InputUtil.Key key : this.blockingKeys) { 145 | blocking.add(new JsonPrimitive(key.getTranslationKey())); 146 | } 147 | jsonObject.add("blocking", blocking); 148 | 149 | return jsonObject; 150 | } 151 | 152 | public void fromJson(@Nullable JsonElement jsonElement) { 153 | if (jsonElement == null) { 154 | return; 155 | } 156 | 157 | this.secondaryKeys.clear(); 158 | this.blockingKeys.clear(); 159 | 160 | if (!jsonElement.isJsonObject()) { 161 | this.setPrimaryKey(InputUtil.fromTranslationKey(jsonElement.getAsString())); 162 | return; 163 | } 164 | 165 | JsonObject jsonObject = jsonElement.getAsJsonObject(); 166 | 167 | this.setPrimaryKey(InputUtil.fromTranslationKey(jsonObject.get("primary").getAsString())); 168 | for (JsonElement key : jsonObject.getAsJsonArray("secondary")) { 169 | this.addSecondaryKey(InputUtil.fromTranslationKey(key.getAsString())); 170 | } 171 | for (JsonElement key : jsonObject.getAsJsonArray("blocking")) { 172 | this.addBlockingKey(InputUtil.fromTranslationKey(key.getAsString())); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/CameraAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.client.render.Camera; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(Camera.class) 8 | public interface CameraAccessor { 9 | @Accessor("inverseView") 10 | boolean seedQueue$isInverseView(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/DebugHudAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.client.gui.hud.DebugHud; 4 | import net.minecraft.client.util.math.MatrixStack; 5 | import net.minecraft.util.MetricsData; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | 9 | @Mixin(DebugHud.class) 10 | public interface DebugHudAccessor { 11 | @Invoker("drawMetricsData") 12 | void seedQueue$drawMetricsData(MatrixStack matrixStack, MetricsData metricsData, int i, int j, boolean bl); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/EntityAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.entity.Entity; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | @Mixin(Entity.class) 10 | public interface EntityAccessor { 11 | @Accessor("MAX_ENTITY_ID") 12 | static AtomicInteger seedQueue$getMAX_ENTITY_ID() { 13 | throw new UnsupportedOperationException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/MinecraftClientAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(MinecraftClient.class) 9 | public interface MinecraftClientAccessor { 10 | @Invoker("render") 11 | void seedQueue$render(boolean tick); 12 | 13 | @Accessor("thread") 14 | Thread seedQueue$getThread(); 15 | 16 | @Invoker("handleProfilerKeyPress") 17 | void seedQueue$handleProfilerKeyPress(int digit); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/MinecraftServerAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import com.mojang.authlib.GameProfileRepository; 4 | import com.mojang.authlib.minecraft.MinecraftSessionService; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.util.UserCache; 7 | import net.minecraft.util.registry.RegistryTracker; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | @Mixin(MinecraftServer.class) 13 | public interface MinecraftServerAccessor { 14 | @Accessor("running") 15 | void seedQueue$setRunning(boolean running); 16 | 17 | @Mutable 18 | @Accessor("userCache") 19 | void seedQueue$setUserCache(UserCache userCache); 20 | 21 | @Mutable 22 | @Accessor("gameProfileRepo") 23 | void seedQueue$setGameProfileRepo(GameProfileRepository gameProfileRepo); 24 | 25 | @Mutable 26 | @Accessor("sessionService") 27 | void seedQueue$setSessionService(MinecraftSessionService sessionService); 28 | 29 | @Accessor("dimensionTracker") 30 | RegistryTracker.Modifiable seedQueue$getDimensionTracker(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/PlayerEntityAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.entity.data.TrackedData; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(PlayerEntity.class) 9 | public interface PlayerEntityAccessor { 10 | @Accessor("PLAYER_MODEL_PARTS") 11 | static TrackedData seedQueue$getPLAYER_MODEL_PARTS() { 12 | throw new UnsupportedOperationException(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/UtilAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.util.Util; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | 9 | @Mixin(Util.class) 10 | public interface UtilAccessor { 11 | @Invoker("attemptShutdown") 12 | static void seedQueue$attemptShutdown(ExecutorService executorService) { 13 | throw new UnsupportedOperationException(); 14 | } 15 | 16 | @Invoker("uncaughtExceptionHandler") 17 | static void seedQueue$uncaughtExceptionHandler(Thread thread, Throwable throwable) { 18 | throw new UnsupportedOperationException(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/WorldGenerationProgressTrackerAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.client.gui.WorldGenerationProgressTracker; 4 | import net.minecraft.server.WorldGenerationProgressLogger; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(WorldGenerationProgressTracker.class) 9 | public interface WorldGenerationProgressTrackerAccessor { 10 | @Accessor 11 | WorldGenerationProgressLogger getProgressLogger(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/accessor/WorldRendererAccessor.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.accessor; 2 | 3 | import net.minecraft.client.render.WorldRenderer; 4 | import net.minecraft.client.world.ClientWorld; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | 9 | @Mixin(WorldRenderer.class) 10 | public interface WorldRendererAccessor { 11 | @Accessor("world") 12 | ClientWorld seedQueue$getWorld(); 13 | 14 | @Invoker("getCompletedChunkCount") 15 | int seedQueue$getCompletedChunkCount(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/CreateWorldScreenMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 6 | import me.contaria.seedqueue.SeedQueue; 7 | import me.contaria.seedqueue.SeedQueueException; 8 | import net.minecraft.client.Keyboard; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.client.gui.screen.Screen; 11 | import net.minecraft.client.gui.screen.world.CreateWorldScreen; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(CreateWorldScreen.class) 18 | public abstract class CreateWorldScreenMixin { 19 | 20 | @WrapWithCondition( 21 | method = "createLevel", 22 | at = @At( 23 | value = "INVOKE", 24 | target = "Lnet/minecraft/client/MinecraftClient;setScreenAndRender(Lnet/minecraft/client/gui/screen/Screen;)V" 25 | ) 26 | ) 27 | private boolean cancelRenderingScreen(MinecraftClient client, Screen screen) { 28 | return !SeedQueue.inQueue(); 29 | } 30 | 31 | @WrapOperation( 32 | method = "createLevel", 33 | at = @At( 34 | value = "INVOKE", 35 | target = "Lnet/minecraft/client/MinecraftClient;setScreenAndRender(Lnet/minecraft/client/gui/screen/Screen;)V" 36 | ) 37 | ) 38 | private void skipIntermissionScreen(MinecraftClient client, Screen screen, Operation original) { 39 | if (SeedQueue.currentEntry != null) { 40 | client.openScreen(screen); 41 | } else { 42 | original.call(client, screen); 43 | } 44 | } 45 | 46 | @Inject( 47 | method = "createLevel", 48 | at = @At( 49 | value = "RETURN", 50 | ordinal = 0 51 | ) 52 | ) 53 | private void throwSeedQueueException(CallbackInfo ci) throws SeedQueueException { 54 | if (SeedQueue.inQueue()) { 55 | throw new SeedQueueException("Failed to copy datapacks to world!"); 56 | } 57 | } 58 | 59 | @WrapWithCondition( 60 | method = "init", 61 | at = @At( 62 | value = "INVOKE", 63 | target = "Lnet/minecraft/client/Keyboard;enableRepeatEvents(Z)V" 64 | ) 65 | ) 66 | private boolean doNotEnableRepeatEventsInQueue(Keyboard keyboard, boolean repeatEvents) { 67 | return !SeedQueue.inQueue(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/WorldGenerationProgressLoggerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import me.contaria.seedqueue.interfaces.SQWorldGenerationProgressLogger; 5 | import net.minecraft.server.WorldGenerationProgressLogger; 6 | import org.apache.logging.log4j.Logger; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | 11 | @Mixin(WorldGenerationProgressLogger.class) 12 | public abstract class WorldGenerationProgressLoggerMixin implements SQWorldGenerationProgressLogger { 13 | 14 | @Unique 15 | private boolean muted; 16 | 17 | @WrapWithCondition( 18 | method = "setChunkStatus", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V", 22 | remap = false 23 | ) 24 | ) 25 | private boolean muteWorldGenProgress_inQueue(Logger logger, String s) { 26 | return !this.muted; 27 | } 28 | 29 | @WrapWithCondition( 30 | method = "stop", 31 | at = @At( 32 | value = "INVOKE", 33 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V", 34 | remap = false 35 | ) 36 | ) 37 | private boolean muteWorldGenProgress_inQueue(Logger logger, String s, Object o) { 38 | return !this.muted; 39 | } 40 | 41 | @Override 42 | public void seedQueue$mute() { 43 | this.muted = true; 44 | } 45 | 46 | @Override 47 | public void seedQueue$unmute() { 48 | this.muted = false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/WorldGenerationProgressTrackerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.contaria.seedqueue.interfaces.SQWorldGenerationProgressTracker; 6 | import net.minecraft.client.gui.WorldGenerationProgressTracker; 7 | import net.minecraft.util.Util; 8 | import net.minecraft.util.math.ChunkPos; 9 | import net.minecraft.util.math.MathHelper; 10 | import net.minecraft.world.chunk.ChunkStatus; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | import java.util.Optional; 21 | 22 | @Mixin(WorldGenerationProgressTracker.class) 23 | public abstract class WorldGenerationProgressTrackerMixin implements SQWorldGenerationProgressTracker { 24 | @Shadow 25 | @Final 26 | private Long2ObjectOpenHashMap chunkStatuses; 27 | 28 | @Shadow 29 | private ChunkPos spawnPos; 30 | 31 | @Shadow 32 | @Final 33 | private int radius; 34 | 35 | @Shadow 36 | public abstract @Nullable ChunkStatus getChunkStatus(int x, int z); 37 | 38 | @Shadow 39 | @Final 40 | private int centerSize; 41 | 42 | @Unique 43 | private long freezeTime = -1; 44 | 45 | @Unique 46 | @Nullable 47 | private WorldGenerationProgressTracker frozenCopy; 48 | 49 | @Unique 50 | private volatile int progressLevel; 51 | @Unique 52 | private volatile int progressPercentage; 53 | 54 | @Inject( 55 | method = "", 56 | at = @At("TAIL") 57 | ) 58 | private void markFreezeTimeStart(CallbackInfo ci) { 59 | long chunkMapFreezingTime = SeedQueue.config.chunkMapFreezing; 60 | if (chunkMapFreezingTime != -1 && this.freezeTime == -1) { 61 | this.makeFrozenCopyAfter(chunkMapFreezingTime); 62 | } 63 | } 64 | 65 | @Inject( 66 | method = "setChunkStatus", 67 | at = @At( 68 | value = "INVOKE", 69 | target = "Lnet/minecraft/server/WorldGenerationProgressLogger;setChunkStatus(Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/ChunkStatus;)V" 70 | ) 71 | ) 72 | private void onSetChunkStatus(CallbackInfo ci) { 73 | if (this.frozenCopy == null && this.seedQueue$shouldFreeze()) { 74 | this.makeFrozenCopy(); 75 | } 76 | } 77 | 78 | @Inject( 79 | method = "start(Lnet/minecraft/util/math/ChunkPos;)V", 80 | at = @At("TAIL") 81 | ) 82 | private void recalculateProgressCount(CallbackInfo ci) { 83 | this.progressLevel = 0; 84 | this.calculateProgressCount(); 85 | } 86 | 87 | @Inject( 88 | method = "setChunkStatus", 89 | at = @At("TAIL") 90 | ) 91 | private void recalculateProgressCount(ChunkPos pos, ChunkStatus status, CallbackInfo ci) { 92 | if (status == ChunkStatus.FULL) { 93 | this.calculateProgressCount(); 94 | } 95 | } 96 | 97 | @Unique 98 | private void makeFrozenCopy() { 99 | WorldGenerationProgressTracker frozenCopy = new WorldGenerationProgressTracker(this.radius - ChunkStatus.getMaxTargetGenerationRadius()); // This will trigger a makeFrozenCopyAfter inside the frozen copy itself which could lead to further recursion, but as long as setChunkStatus isn't called, this will never be an issue. 100 | ((WorldGenerationProgressTrackerMixin) (Object) frozenCopy).setAsFrozenCopy(this.chunkStatuses, this.spawnPos); 101 | this.frozenCopy = frozenCopy; 102 | } 103 | 104 | @Unique 105 | private void makeFrozenCopyAfter(long millis) { 106 | this.freezeTime = Util.getMeasuringTimeMs() + millis; 107 | } 108 | 109 | @Unique 110 | private void setAsFrozenCopy(Long2ObjectOpenHashMap chunkStatuses, ChunkPos spawnPos) { 111 | this.chunkStatuses.putAll(chunkStatuses); 112 | this.spawnPos = spawnPos; 113 | } 114 | 115 | @Override 116 | public boolean seedQueue$shouldFreeze() { 117 | return this.freezeTime != -1 && Util.getMeasuringTimeMs() >= this.freezeTime; 118 | } 119 | 120 | @Override 121 | public Optional seedQueue$getFrozenCopy() { 122 | return Optional.ofNullable(this.frozenCopy); 123 | } 124 | 125 | @Unique 126 | private void calculateProgressCount() { 127 | // load cached level to avoid checking levels we already know are full 128 | int level = this.progressLevel; 129 | int count = (level * 2 - 1) * (level * 2 - 1); 130 | boolean end = false; 131 | 132 | for (; level < this.radius; level++) { 133 | // travel left to right on the x-axis 134 | // when on the right and leftmost bounds of the current level, 135 | // check all the y values of that ring, 136 | // otherwise, just check the top and bottom of the ring. 137 | // if any chunks are missing in the ring, 138 | // no future rings are searched but the current ring is completed. 139 | // 140 | // 141 | // ↑ then -> 142 | // 143 | // - - - - - 144 | // - + + + - 145 | // - + = + - 146 | // - + + + - 147 | // - - - - - 148 | 149 | // adding radius as WorldGenerationProgressTracker#getChunkStatus subtracts it 150 | int leftX = -level + this.radius; 151 | int rightX = level + this.radius; 152 | int bottomZ = -level + this.radius; 153 | int topZ = level + this.radius; 154 | 155 | for (int x = leftX; x <= rightX; ++x) { 156 | boolean onBounds = x == leftX || x == rightX; 157 | for (int z = bottomZ; z <= topZ; z += onBounds ? 1 : level * 2) { 158 | if (this.getChunkStatus(x, z) == ChunkStatus.FULL) { 159 | count++; 160 | } else { 161 | end = true; 162 | } 163 | } 164 | } 165 | 166 | if (end) { 167 | break; 168 | } 169 | } 170 | 171 | this.progressLevel = level; 172 | this.progressPercentage = MathHelper.clamp(count * 100 / (this.centerSize * this.centerSize), 0, 100); 173 | } 174 | 175 | @Override 176 | public int seedQueue$getProgressPercentage() { 177 | return this.progressPercentage; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/debug/DebugHudMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.debug; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import net.minecraft.client.gui.hud.DebugHud; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | 9 | import java.util.List; 10 | 11 | @Mixin(DebugHud.class) 12 | public abstract class DebugHudMixin { 13 | 14 | @ModifyReturnValue( 15 | method = "getRightText", 16 | at = @At("RETURN") 17 | ) 18 | private List addSeedQueueDebugText(List debugText) { 19 | if (SeedQueue.isActive()) { 20 | debugText.addAll(SeedQueue.getDebugText()); 21 | } 22 | return debugText; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/levellist/LevelStorageMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.levellist; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import net.minecraft.world.level.storage.LevelStorage; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | 10 | import java.io.File; 11 | 12 | @Mixin(LevelStorage.class) 13 | public abstract class LevelStorageMixin { 14 | 15 | @WrapOperation( 16 | method = "getLevelList", 17 | at = @At( 18 | value = "INVOKE", 19 | target = "Ljava/io/File;isDirectory()Z", 20 | remap = false 21 | ) 22 | ) 23 | private boolean reduceLevelList(File file, Operation original) { 24 | String name = file.getName(); 25 | if (SeedQueue.config.reduceLevelList && (name.startsWith("Benchmark Reset #") || (name.startsWith("Random Speedrun #") || name.startsWith("Set Speedrun #")) && !new File(file, "level.dat_old").exists())) { 26 | return false; 27 | } 28 | return original.call(file); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/profiling/WorldRendererMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.profiling; 2 | 3 | import me.contaria.seedqueue.debug.SeedQueueProfiler; 4 | import net.minecraft.client.render.WorldRenderer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | /** 11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering. 12 | * These Mixins will be removed in later versions of SeedQueue. 13 | */ 14 | @Mixin(value = WorldRenderer.class, priority = 500) 15 | public abstract class WorldRendererMixin { 16 | 17 | @Inject( 18 | method = "setWorld", 19 | at = @At("HEAD") 20 | ) 21 | private void profileVanilla_setWorld(CallbackInfo ci) { 22 | SeedQueueProfiler.push("vanilla"); 23 | } 24 | 25 | @Inject( 26 | method = "setWorld", 27 | at = @At( 28 | value = "INVOKE", 29 | target = "Lnet/minecraft/client/render/WorldRenderer;reload()V" 30 | ) 31 | ) 32 | private void profileReload(CallbackInfo ci) { 33 | SeedQueueProfiler.push("reload"); 34 | } 35 | 36 | @Inject( 37 | method = "setWorld", 38 | at = @At( 39 | value = "INVOKE", 40 | target = "Ljava/util/Set;clear()V", 41 | ordinal = 0 42 | ) 43 | ) 44 | private void profileClearChunks(CallbackInfo ci) { 45 | SeedQueueProfiler.push("clear_chunks"); 46 | } 47 | 48 | @Inject( 49 | method = "setWorld", 50 | at = @At( 51 | value = "INVOKE", 52 | target = "Lnet/minecraft/client/render/chunk/ChunkBuilder;stop()V" 53 | ) 54 | ) 55 | private void profileStopBuilder(CallbackInfo ci) { 56 | SeedQueueProfiler.swap("stop_builder"); 57 | } 58 | 59 | @Inject( 60 | method = "setWorld", 61 | at = @At( 62 | value = "FIELD", 63 | target = "Lnet/minecraft/client/render/WorldRenderer;noCullingBlockEntities:Ljava/util/Set;" 64 | ) 65 | ) 66 | private void profileClearBlockEntities(CallbackInfo ci) { 67 | SeedQueueProfiler.swap("clear_block_entities"); 68 | } 69 | 70 | @Inject( 71 | method = "setWorld", 72 | at = @At("TAIL") 73 | ) 74 | private void profilePop_setWorld(CallbackInfo ci) { 75 | SeedQueueProfiler.pop(); 76 | SeedQueueProfiler.pop(); 77 | } 78 | 79 | @Inject( 80 | method = "reload", 81 | at = @At("HEAD") 82 | ) 83 | private void profileTransparencyShader(CallbackInfo ci) { 84 | SeedQueueProfiler.push("transparency_shader"); 85 | } 86 | 87 | @Inject( 88 | method = "reload", 89 | at = @At( 90 | value = "INVOKE", 91 | target = "Lnet/minecraft/client/world/ClientWorld;reloadColor()V" 92 | ) 93 | ) 94 | private void profileReloadColor(CallbackInfo ci) { 95 | SeedQueueProfiler.swap("reload_color"); 96 | } 97 | 98 | @Inject( 99 | method = "reload", 100 | at = @At( 101 | value = "FIELD", 102 | target = "Lnet/minecraft/client/render/WorldRenderer;chunkBuilder:Lnet/minecraft/client/render/chunk/ChunkBuilder;", 103 | ordinal = 0 104 | ) 105 | ) 106 | private void profileChunkBuilder(CallbackInfo ci) { 107 | SeedQueueProfiler.swap("chunk_builder"); 108 | } 109 | 110 | @Inject( 111 | method = "reload", 112 | at = @At( 113 | value = "INVOKE", 114 | target = "Lnet/minecraft/client/render/BuiltChunkStorage;clear()V" 115 | ) 116 | ) 117 | private void profileClearChunks_reload(CallbackInfo ci) { 118 | SeedQueueProfiler.swap("clear_chunks"); 119 | } 120 | 121 | @Inject( 122 | method = "reload", 123 | at = @At( 124 | value = "INVOKE", 125 | target = "Lnet/minecraft/client/render/WorldRenderer;clearChunkRenderers()V" 126 | ) 127 | ) 128 | private void profileClearChunkRenderers_reload(CallbackInfo ci) { 129 | SeedQueueProfiler.swap("clear_chunk_renderers"); 130 | } 131 | 132 | @Inject( 133 | method = "reload", 134 | at = @At( 135 | value = "FIELD", 136 | target = "Lnet/minecraft/client/render/WorldRenderer;noCullingBlockEntities:Ljava/util/Set;", 137 | ordinal = 0 138 | ) 139 | ) 140 | private void profileClearBlockEntities_reload(CallbackInfo ci) { 141 | SeedQueueProfiler.swap("clear_block_entities"); 142 | } 143 | 144 | @Inject( 145 | method = "reload", 146 | at = @At( 147 | value = "NEW", 148 | target = "(Lnet/minecraft/client/render/chunk/ChunkBuilder;Lnet/minecraft/world/World;ILnet/minecraft/client/render/WorldRenderer;)Lnet/minecraft/client/render/BuiltChunkStorage;" 149 | ) 150 | ) 151 | private void profileBuiltChunkStorage(CallbackInfo ci) { 152 | SeedQueueProfiler.swap("built_chunk_storage"); 153 | } 154 | 155 | @Inject( 156 | method = "reload", 157 | at = @At( 158 | value = "INVOKE", 159 | target = "Lnet/minecraft/client/render/BuiltChunkStorage;updateCameraPosition(DD)V" 160 | ) 161 | ) 162 | private void profileUpdateCameraPos(CallbackInfo ci) { 163 | SeedQueueProfiler.swap("update_camera_pos"); 164 | } 165 | 166 | @Inject( 167 | method = "reload", 168 | at = @At("RETURN") 169 | ) 170 | private void profilePop_reload(CallbackInfo ci) { 171 | SeedQueueProfiler.pop(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/render/LevelLoadingScreenMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.render; 2 | 3 | import com.llamalad7.mixinextras.sugar.Share; 4 | import com.llamalad7.mixinextras.sugar.ref.LocalIntRef; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import net.minecraft.client.gui.WorldGenerationProgressTracker; 7 | import net.minecraft.client.gui.screen.LevelLoadingScreen; 8 | import net.minecraft.client.util.math.MatrixStack; 9 | import org.spongepowered.asm.mixin.Dynamic; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Unique; 12 | import org.spongepowered.asm.mixin.injection.*; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.awt.*; 16 | 17 | @Mixin(value = LevelLoadingScreen.class, priority = 1500) 18 | public abstract class LevelLoadingScreenMixin { 19 | 20 | @Unique 21 | private static final int NO_MODIFIER = new Color(255, 255, 255, 255).getRGB(); 22 | 23 | @Unique 24 | private static final int TRANSPARENT_MODIFIER = new Color(255, 255, 255, 150).getRGB(); 25 | 26 | @Inject( 27 | method = "drawChunkMap", 28 | at = @At("HEAD") 29 | ) 30 | private static void setColorModifier(MatrixStack matrixStack, WorldGenerationProgressTracker tracker, int i, int j, int k, int l, CallbackInfo ci, @Share("colorModifier") LocalIntRef colorModifier) { 31 | if (!SeedQueue.isOnWall() && SeedQueue.hasEntryMatching(entry -> tracker == entry.getWorldGenerationProgressTracker())) { 32 | colorModifier.set(TRANSPARENT_MODIFIER); 33 | } else { 34 | colorModifier.set(NO_MODIFIER); 35 | } 36 | } 37 | 38 | // This group is here for compatibility with sodium's MixinLevelLoadingScreen @Overwrite of drawChunkMap. 39 | @Group(name = "transparent") 40 | @ModifyArg( 41 | method = "drawChunkMap", 42 | at = @At( 43 | value = "INVOKE", 44 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;fill(Lnet/minecraft/client/util/math/MatrixStack;IIIII)V" 45 | ), 46 | slice = @Slice( 47 | from = @At( 48 | value = "FIELD", 49 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;STATUS_TO_COLOR:Lit/unimi/dsi/fastutil/objects/Object2IntMap;" 50 | ) 51 | ), 52 | index = 5 53 | ) 54 | private static int transparentSeedQueueChunkMap(int color, @Share("colorModifier") LocalIntRef colorModifier) { 55 | return color & colorModifier.get(); 56 | } 57 | 58 | @Dynamic 59 | @Group(name = "transparent") 60 | @ModifyArg( 61 | method = "drawChunkMap", 62 | at = @At( 63 | value = "INVOKE", 64 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;addRect(Lnet/minecraft/util/math/Matrix4f;Lme/jellysquid/mods/sodium/client/model/vertex/formats/screen_quad/BasicScreenQuadVertexSink;IIIII)V" 65 | ), 66 | index = 6 67 | ) 68 | private static int transparentSeedQueueChunkMap_sodium(int color, @Share("colorModifier") LocalIntRef colorModifier) { 69 | return color & colorModifier.get(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/render/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.render; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.seedqueue.SeedQueueEntry; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.gui.WorldGenerationProgressTracker; 7 | import net.minecraft.client.gui.screen.LevelLoadingScreen; 8 | import net.minecraft.client.util.Window; 9 | import net.minecraft.client.util.math.MatrixStack; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.Slice; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(MinecraftClient.class) 19 | public abstract class MinecraftClientMixin { 20 | 21 | @Shadow 22 | @Final 23 | private Window window; 24 | 25 | @Inject( 26 | method = "render", 27 | at = @At( 28 | value = "INVOKE", 29 | target = "Lnet/minecraft/util/profiler/Profiler;pop()V", 30 | ordinal = 0 31 | ), 32 | slice = @Slice( 33 | from = @At( 34 | value = "INVOKE", 35 | target = "Lnet/minecraft/client/render/GameRenderer;render(FJZ)V" 36 | ) 37 | ) 38 | ) 39 | private void drawSeedQueueChunkMaps(CallbackInfo ci) { 40 | if (SeedQueue.isOnWall() || !SeedQueue.config.showChunkMaps) { 41 | return; 42 | } 43 | 44 | int x = 3; 45 | int y = 3; 46 | for (SeedQueueEntry seedQueueEntry : SeedQueue.getEntries()) { 47 | if (seedQueueEntry.isPaused()) { 48 | continue; 49 | } 50 | WorldGenerationProgressTracker tracker = seedQueueEntry.getWorldGenerationProgressTracker(); 51 | if (tracker == null) { 52 | continue; 53 | } 54 | if (x + tracker.getSize() > this.window.getScaledWidth() - 3) { 55 | x = 3; 56 | y += tracker.getSize() + 3; 57 | } 58 | LevelLoadingScreen.drawChunkMap(new MatrixStack(), tracker, x + tracker.getSize() / 2, y + tracker.getSize() / 2, 1, 0); 59 | x += tracker.getSize() + 3; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/sounds/SoundManagerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.sounds; 2 | 3 | import me.contaria.seedqueue.interfaces.SQSoundManager; 4 | import me.contaria.seedqueue.interfaces.SQSoundSystem; 5 | import net.minecraft.client.sound.SoundManager; 6 | import net.minecraft.client.sound.SoundSystem; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | 11 | @Mixin(SoundManager.class) 12 | public abstract class SoundManagerMixin implements SQSoundManager { 13 | @Shadow 14 | @Final 15 | private SoundSystem soundSystem; 16 | 17 | @Override 18 | public void seedQueue$stopAllExceptSeedQueueSounds() { 19 | ((SQSoundSystem) this.soundSystem).seedQueue$stopAllExceptSeedQueueSounds(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/client/sounds/SoundSystemMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.client.sounds; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import me.contaria.seedqueue.interfaces.SQSoundSystem; 5 | import net.minecraft.client.sound.AudioStream; 6 | import net.minecraft.client.sound.Channel; 7 | import net.minecraft.client.sound.SoundInstance; 8 | import net.minecraft.client.sound.SoundSystem; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | 14 | import java.util.Map; 15 | import java.util.concurrent.CompletableFuture; 16 | 17 | @Mixin(SoundSystem.class) 18 | public abstract class SoundSystemMixin implements SQSoundSystem { 19 | @Shadow 20 | @Final 21 | private Map sources; 22 | 23 | @Shadow 24 | public abstract void stop(SoundInstance soundInstance); 25 | 26 | @ModifyExpressionValue( 27 | method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", 28 | at = { 29 | @At( 30 | value = "INVOKE", 31 | target = "Lnet/minecraft/client/sound/SoundLoader;loadStatic(Lnet/minecraft/util/Identifier;)Ljava/util/concurrent/CompletableFuture;" 32 | ), 33 | @At( 34 | value = "INVOKE", 35 | target = "Lnet/minecraft/client/sound/SoundLoader;loadStreamed(Lnet/minecraft/util/Identifier;Z)Ljava/util/concurrent/CompletableFuture;" 36 | ) 37 | } 38 | ) 39 | private CompletableFuture crashBrokenSeedQueueSoundFiles(CompletableFuture audio, SoundInstance soundInstance) { 40 | if (soundInstance.getId().getNamespace().equals("seedqueue") && audio.isCompletedExceptionally()) { 41 | throw new RuntimeException("Tried to play a broken sound file from a SeedQueue customization pack (\"" + soundInstance.getId() + "\")! If you are using empty sound files to mute sounds on the wall screen, use short silent ones instead."); 42 | } 43 | return audio; 44 | } 45 | 46 | // see SoundSystem#closeSounds 47 | @Override 48 | public void seedQueue$stopAllExceptSeedQueueSounds() { 49 | for (SoundInstance sound : this.sources.keySet()) { 50 | if (!sound.getId().getNamespace().equals("seedqueue")) { 51 | this.stop(sound); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/atum/AttemptTrackerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.atum; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.voidxwalker.autoreset.AttemptTracker; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import java.io.IOException; 12 | 13 | @Mixin(value = AttemptTracker.class, remap = false) 14 | public abstract class AttemptTrackerMixin { 15 | 16 | @Shadow 17 | public abstract void register(AttemptTracker.Type type) throws IOException; 18 | 19 | @Inject( 20 | method = "", 21 | at = @At("TAIL") 22 | ) 23 | private void registerBenchmarkResetCounter(CallbackInfo ci) throws IOException { 24 | this.register(SeedQueue.BENCHMARK_RESETS); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/atum/AtumMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.atum; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import me.contaria.seedqueue.SeedQueueEntry; 7 | import me.contaria.seedqueue.compat.ModCompat; 8 | import me.contaria.seedqueue.gui.wall.SeedQueueWallScreen; 9 | import me.contaria.seedqueue.sounds.SeedQueueSounds; 10 | import me.voidxwalker.autoreset.Atum; 11 | import net.minecraft.client.MinecraftClient; 12 | import net.minecraft.client.gui.screen.ProgressScreen; 13 | import net.minecraft.client.gui.screen.Screen; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.Optional; 20 | 21 | @Mixin(value = Atum.class, remap = false) 22 | public abstract class AtumMixin { 23 | 24 | @WrapOperation( 25 | method = "createNewWorld", 26 | at = @At( 27 | value = "INVOKE", 28 | target = "Lnet/minecraft/client/MinecraftClient;openScreen(Lnet/minecraft/client/gui/screen/Screen;)V", 29 | remap = true 30 | ) 31 | ) 32 | private static void openSeedQueueWallScreen(MinecraftClient client, Screen screen, Operation original) { 33 | if (!SeedQueue.isActive()) { 34 | original.call(client, screen); 35 | return; 36 | } 37 | if (SeedQueue.config.shouldUseWall()) { 38 | if (SeedQueue.config.bypassWall) { 39 | Optional nextSeedQueueEntry = SeedQueue.getEntryMatching(entry -> entry.isReady() && entry.isLocked()); 40 | if (nextSeedQueueEntry.isPresent()) { 41 | SeedQueueSounds.play(SeedQueueSounds.BYPASS_WALL); 42 | SeedQueue.playEntry(nextSeedQueueEntry.get()); 43 | return; 44 | } 45 | } 46 | // standardsettings can cause the current screen to be re-initialized, 47 | // so we open an intermission screen to avoid atum reset logic being called twice 48 | client.openScreen(new ProgressScreen()); 49 | ModCompat.standardsettings$cache(); 50 | ModCompat.standardsettings$reset(); 51 | ModCompat.stateoutput$setWallState(); 52 | SeedQueueSounds.play(SeedQueueSounds.OPEN_WALL); 53 | client.openScreen(new SeedQueueWallScreen()); 54 | return; 55 | } 56 | if (!SeedQueue.playEntry()) { 57 | original.call(client, screen); 58 | } 59 | } 60 | 61 | @Inject( 62 | method = "stopRunning", 63 | at = @At("TAIL") 64 | ) 65 | private static void stopSeedQueueOnAtumStop(CallbackInfo ci) { 66 | SeedQueue.stop(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/atum/CreateWorldScreenMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.atum; 2 | 3 | import com.bawnorton.mixinsquared.TargetHandler; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.voidxwalker.autoreset.AttemptTracker; 6 | import net.minecraft.client.gui.screen.world.CreateWorldScreen; 7 | import org.spongepowered.asm.mixin.Dynamic; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.ModifyArg; 11 | 12 | @Mixin(value = CreateWorldScreen.class, priority = 1500) 13 | public abstract class CreateWorldScreenMixin { 14 | 15 | @Dynamic 16 | @TargetHandler( 17 | mixin = "me.voidxwalker.autoreset.mixin.config.CreateWorldScreenMixin", 18 | name = "createWorld" 19 | ) 20 | @ModifyArg( 21 | method = "@MixinSquared:Handler", 22 | at = @At( 23 | value = "INVOKE", 24 | target = "Lme/voidxwalker/autoreset/AttemptTracker;incrementAndGetWorldName(Lme/voidxwalker/autoreset/AttemptTracker$Type;)Ljava/lang/String;", 25 | remap = false 26 | ) 27 | ) 28 | private AttemptTracker.Type useBenchmarkResetCounter(AttemptTracker.Type type) { 29 | if (SeedQueue.isBenchmarking()) { 30 | return SeedQueue.BENCHMARK_RESETS; 31 | } 32 | return type; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/atum/KeyboardMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.atum; 2 | 3 | import com.bawnorton.mixinsquared.TargetHandler; 4 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import me.contaria.seedqueue.gui.config.SeedQueueKeybindingsScreen; 7 | import net.minecraft.client.Keyboard; 8 | import net.minecraft.client.MinecraftClient; 9 | import org.spongepowered.asm.mixin.Dynamic; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | 15 | @Mixin(value = Keyboard.class, priority = 1500) 16 | public abstract class KeyboardMixin { 17 | 18 | @Shadow 19 | @Final 20 | private MinecraftClient client; 21 | 22 | @Dynamic 23 | @TargetHandler( 24 | mixin = "me.voidxwalker.autoreset.mixin.hotkey.KeyboardMixin", 25 | name = "onKey" 26 | ) 27 | @ModifyExpressionValue( 28 | method = "@MixinSquared:Handler", 29 | at = @At( 30 | value = "INVOKE", 31 | target = "Lnet/minecraft/client/options/KeyBinding;matchesKey(II)Z" 32 | ) 33 | ) 34 | private boolean doNotActivateAtumHotKey_onWall(boolean matchesKey) { 35 | return matchesKey && !(SeedQueue.isOnWall() || this.client.currentScreen instanceof SeedQueueKeybindingsScreen); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilder$WorkerRunnableMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium; 2 | 3 | import me.contaria.seedqueue.SeedQueue; 4 | import me.contaria.seedqueue.compat.SodiumCompat; 5 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(targets = "me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable", remap = false) 14 | public abstract class ChunkBuilder$WorkerRunnableMixin { 15 | 16 | @Shadow 17 | @Final 18 | private ChunkBuildBuffers bufferCache; 19 | 20 | @Inject( 21 | method = "run", 22 | at = @At("RETURN") 23 | ) 24 | private void cacheBuildBuffersOnWall(CallbackInfo ci) { 25 | if (SeedQueue.isOnWall()) { 26 | SodiumCompat.WALL_BUILD_BUFFERS_POOL.add(this.bufferCache); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilder$WorkerRunnableMixin2.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium; 2 | 3 | import me.contaria.seedqueue.interfaces.sodium.SQChunkBuilder$WorkerRunnable; 4 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.world.World; 7 | import org.spongepowered.asm.mixin.*; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(targets = "me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable", remap = false) 13 | public abstract class ChunkBuilder$WorkerRunnableMixin2 implements SQChunkBuilder$WorkerRunnable { 14 | 15 | @Mutable 16 | @Shadow 17 | @Final 18 | private ChunkRenderCacheLocal cache; 19 | 20 | @Unique 21 | private World world; 22 | 23 | @Inject( 24 | method = "run", 25 | at = @At("HEAD") 26 | ) 27 | private void createRenderCacheOnWorkerThread(CallbackInfo ci) { 28 | if (this.cache == null) { 29 | this.cache = new ChunkRenderCacheLocal(MinecraftClient.getInstance(), this.world); 30 | } 31 | this.world = null; 32 | } 33 | 34 | @Override 35 | public void seedQueue$setWorldForRenderCache(World world) { 36 | if (this.cache == null) { 37 | this.world = world; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilderMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 5 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 6 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 7 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 8 | import me.contaria.seedqueue.SeedQueue; 9 | import me.contaria.seedqueue.compat.SodiumCompat; 10 | import me.contaria.seedqueue.interfaces.sodium.SQChunkBuilder$WorkerRunnable; 11 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; 12 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder; 13 | import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPassManager; 14 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal; 15 | import net.minecraft.client.MinecraftClient; 16 | import net.minecraft.world.World; 17 | import org.apache.logging.log4j.Logger; 18 | import org.spongepowered.asm.mixin.*; 19 | import org.spongepowered.asm.mixin.injection.*; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | import java.util.Objects; 23 | import java.util.concurrent.atomic.AtomicBoolean; 24 | 25 | @Mixin(value = ChunkBuilder.class, remap = false) 26 | public abstract class ChunkBuilderMixin { 27 | 28 | @Shadow 29 | private World world; 30 | 31 | @Mutable 32 | @Shadow 33 | @Final 34 | private AtomicBoolean running; 35 | 36 | @ModifyReturnValue( 37 | method = "getMaxThreadCount", 38 | at = @At("RETURN") 39 | ) 40 | private static int modifyMaxThreads(int maxThreads) { 41 | if (SeedQueue.isOnWall()) { 42 | return SeedQueue.config.getChunkUpdateThreads(); 43 | } 44 | return maxThreads; 45 | } 46 | 47 | @ModifyArg( 48 | method = "createWorker", 49 | at = @At( 50 | value = "INVOKE", 51 | target = "Ljava/lang/Thread;setPriority(I)V" 52 | ) 53 | ) 54 | private int modifyChunkUpdateThreadPriority(int priority) { 55 | if (SeedQueue.isOnWall()) { 56 | return SeedQueue.config.chunkUpdateThreadPriority; 57 | } 58 | return priority; 59 | } 60 | 61 | @WrapWithCondition( 62 | method = "stopWorkers", 63 | at = @At( 64 | value = "INVOKE", 65 | target = "Ljava/lang/Thread;join()V" 66 | ) 67 | ) 68 | private boolean doNotWaitForWorkersToStopOnWall(Thread thread) { 69 | return !SeedQueue.isOnWall(); 70 | } 71 | 72 | // because ChunkBuilderMixin#doNotWaitForWorkersToStopOnWall prevents waiting for old workers to shut down, 73 | // it's necessary to replace the atomic boolean to prevent the worker threads staying alive 74 | // when new workers are started before old ones have stopped 75 | @Inject( 76 | method = "stopWorkers", 77 | at = @At("TAIL") 78 | ) 79 | private void replaceRunningAtomicBooleanOnWall(CallbackInfo ci) { 80 | if (SeedQueue.isOnWall()) { 81 | this.running = new AtomicBoolean(); 82 | } 83 | } 84 | 85 | // mac sodium compat is very silly 86 | @Group(name = "loadCachedBuildBuffersOnWall") 87 | @WrapOperation( 88 | method = "createWorker", 89 | at = @At( 90 | value = "NEW", 91 | target = "(Lme/jellysquid/mods/sodium/client/model/vertex/type/ChunkVertexType;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;" 92 | ) 93 | ) 94 | private ChunkBuildBuffers loadCachedBuildBuffersOnWall(@Coerce Object passId, BlockRenderPassManager buffers, Operation original) { 95 | if (SeedQueue.isOnWall() && !SodiumCompat.WALL_BUILD_BUFFERS_POOL.isEmpty()) { 96 | return Objects.requireNonNull(SodiumCompat.WALL_BUILD_BUFFERS_POOL.remove(0)); 97 | } 98 | return original.call(passId, buffers); 99 | } 100 | 101 | @Dynamic 102 | @Group(name = "loadCachedBuildBuffersOnWall", min = 1, max = 1) 103 | @WrapOperation( 104 | method = "createWorker", 105 | at = @At( 106 | value = "NEW", 107 | target = "(Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;" 108 | ) 109 | ) 110 | private ChunkBuildBuffers loadCachedBuildBuffersOnWall_macSodium(@Coerce Object format, BlockRenderPassManager buffers, Operation original) { 111 | if (SeedQueue.isOnWall() && !SodiumCompat.WALL_BUILD_BUFFERS_POOL.isEmpty()) { 112 | return Objects.requireNonNull(SodiumCompat.WALL_BUILD_BUFFERS_POOL.remove(0)); 113 | } 114 | return original.call(format, buffers); 115 | } 116 | 117 | @WrapOperation( 118 | method = "createWorker", 119 | at = @At( 120 | value = "NEW", 121 | target = "(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/world/World;)Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;", 122 | remap = true 123 | ), 124 | require = 0 125 | ) 126 | private ChunkRenderCacheLocal createRenderCacheOnWorkerThread(MinecraftClient client, World world, Operation original) { 127 | if (SeedQueue.isOnWall()) { 128 | return null; 129 | } 130 | return original.call(client, world); 131 | } 132 | 133 | @SuppressWarnings("InvalidInjectorMethodSignature") // MCDev doesn't seem to like @Coerce on the return type 134 | @ModifyExpressionValue( 135 | method = "createWorker", 136 | at = @At( 137 | value = "NEW", 138 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable;" 139 | ) 140 | ) 141 | private @Coerce Object passWorldToWorkerThread(@Coerce Object worker) { 142 | if (worker instanceof SQChunkBuilder$WorkerRunnable) { 143 | ((SQChunkBuilder$WorkerRunnable) worker).seedQueue$setWorldForRenderCache(this.world); 144 | } 145 | return worker; 146 | } 147 | 148 | @WrapWithCondition( 149 | method = "startWorkers", 150 | at = @At( 151 | value = "INVOKE", 152 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V" 153 | ) 154 | ) 155 | private boolean suppressStartWorkersLogOnWall(Logger logger, String s, Object o) { 156 | return !SeedQueue.isOnWall(); 157 | } 158 | 159 | @WrapWithCondition( 160 | method = "stopWorkers", 161 | at = @At( 162 | value = "INVOKE", 163 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V" 164 | ) 165 | ) 166 | private boolean suppressStopWorkersLogOnWall(Logger logger, String s) { 167 | return !SeedQueue.isOnWall(); 168 | } 169 | 170 | @ModifyExpressionValue( 171 | method = "getSchedulingBudget", 172 | at = @At( 173 | value = "CONSTANT", 174 | args = "intValue=2") 175 | ) 176 | private int reduceSchedulingBudgetOnWall(int TASK_QUEUE_LIMIT_PER_WORKER) { 177 | if (SeedQueue.isOnWall()) { 178 | return 1; 179 | } 180 | return TASK_QUEUE_LIMIT_PER_WORKER; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkRenderManagerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderManager; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | 9 | @Mixin(value = ChunkRenderManager.class, remap = false) 10 | public abstract class ChunkRenderManagerMixin { 11 | 12 | @ModifyExpressionValue( 13 | method = "isChunkPrioritized", 14 | at = @At( 15 | value = "FIELD", 16 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager;NEARBY_CHUNK_DISTANCE:D" 17 | ) 18 | ) 19 | private double decreaseNearbyChunkDistanceOnWall(double nearbyChunkDistance) { 20 | if (SeedQueue.isOnWall()) { 21 | return 16 * 16; 22 | } 23 | return nearbyChunkDistance; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkRenderShaderBackendMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 6 | import me.contaria.seedqueue.SeedQueue; 7 | import me.contaria.seedqueue.compat.SodiumCompat; 8 | import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexFormat; 9 | import me.jellysquid.mods.sodium.client.render.chunk.format.ChunkMeshAttribute; 10 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkFogMode; 11 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkProgram; 12 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkRenderShaderBackend; 13 | import org.spongepowered.asm.mixin.Dynamic; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Coerce; 17 | import org.spongepowered.asm.mixin.injection.Group; 18 | 19 | @Mixin(value = ChunkRenderShaderBackend.class, remap = false) 20 | public abstract class ChunkRenderShaderBackendMixin { 21 | 22 | @Group(name = "cacheShaders") 23 | @WrapOperation( 24 | method = "createShaders", 25 | at = @At( 26 | value = "INVOKE", 27 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkRenderShaderBackend;createShader(Lme/jellysquid/mods/sodium/client/gl/device/RenderDevice;Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkFogMode;Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;)Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;" 28 | ) 29 | ) 30 | private ChunkProgram cacheShadersOnWall(ChunkRenderShaderBackend instance, @Coerce Object device, ChunkFogMode fogMode, GlVertexFormat vertexFormat, Operation original) { 31 | if (SeedQueue.isOnWall()) { 32 | return SodiumCompat.WALL_SHADER_CACHE.computeIfAbsent(fogMode, f -> original.call(instance, device, fogMode, vertexFormat)); 33 | } 34 | return original.call(instance, device, fogMode, vertexFormat); 35 | } 36 | 37 | @Dynamic 38 | @Group(name = "cacheShaders") 39 | @WrapOperation( 40 | method = "createShaders", 41 | at = @At( 42 | value = "INVOKE", 43 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkRenderShaderBackend;createShader(Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkFogMode;Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;)Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;" 44 | ) 45 | ) 46 | private ChunkProgram cacheShadersOnWall_macSodium(ChunkRenderShaderBackend instance, ChunkFogMode fogMode, GlVertexFormat vertexFormat, Operation original) { 47 | if (SeedQueue.isOnWall()) { 48 | return SodiumCompat.WALL_SHADER_CACHE.computeIfAbsent(fogMode, f -> original.call(instance, fogMode, vertexFormat)); 49 | } 50 | return original.call(instance, fogMode, vertexFormat); 51 | } 52 | 53 | @WrapWithCondition( 54 | method = "delete", 55 | at = @At( 56 | value = "INVOKE", 57 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;delete()V" 58 | ) 59 | ) 60 | private boolean doNotDeleteCachedShaders(ChunkProgram program) { 61 | return !SodiumCompat.WALL_SHADER_CACHE.containsValue(program); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/ChunkBuilderMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling; 2 | 3 | import me.contaria.seedqueue.debug.SeedQueueProfiler; 4 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | /** 11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering. 12 | * These Mixins will be removed in later versions of SeedQueue. 13 | */ 14 | @Mixin(value = ChunkBuilder.class, remap = false) 15 | public abstract class ChunkBuilderMixin { 16 | 17 | @Inject( 18 | method = "startWorkers", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lnet/minecraft/client/MinecraftClient;getInstance()Lnet/minecraft/client/MinecraftClient;", 22 | remap = true 23 | ) 24 | ) 25 | private void profileStartWorkers(CallbackInfo ci) { 26 | SeedQueueProfiler.push("start_workers"); 27 | } 28 | 29 | @Inject( 30 | method = "createWorker", 31 | at = @At( 32 | value = "INVOKE", 33 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;(Lme/jellysquid/mods/sodium/client/model/vertex/type/ChunkVertexType;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)V" 34 | ) 35 | ) 36 | private void profileBuildBuffers(CallbackInfo ci) { 37 | SeedQueueProfiler.push("build_buffers"); 38 | } 39 | 40 | @Inject( 41 | method = "createWorker", 42 | at = @At( 43 | value = "INVOKE", 44 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/world/World;)V", 45 | remap = true 46 | ) 47 | ) 48 | private void profileRenderCache(CallbackInfo ci) { 49 | SeedQueueProfiler.swap("render_cache"); 50 | } 51 | 52 | @Inject( 53 | method = "createWorker", 54 | at = @At( 55 | value = "INVOKE", 56 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable;(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder;Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;)V" 57 | ) 58 | ) 59 | private void profileWorker(CallbackInfo ci) { 60 | SeedQueueProfiler.swap("worker"); 61 | } 62 | 63 | @Inject( 64 | method = "createWorker", 65 | at = @At( 66 | value = "INVOKE", 67 | target = "Ljava/lang/Thread;(Ljava/lang/Runnable;Ljava/lang/String;)V" 68 | ) 69 | ) 70 | private void profileThread(CallbackInfo ci) { 71 | SeedQueueProfiler.swap("thread"); 72 | } 73 | 74 | @Inject( 75 | method = "createWorker", 76 | at = @At( 77 | value = "INVOKE", 78 | target = "Ljava/util/List;add(Ljava/lang/Object;)Z" 79 | ) 80 | ) 81 | private void profilePopPerWorker(CallbackInfo ci) { 82 | SeedQueueProfiler.pop(); 83 | } 84 | 85 | @Inject( 86 | method = "startWorkers", 87 | at = @At("TAIL") 88 | ) 89 | private void profilePop(CallbackInfo ci) { 90 | SeedQueueProfiler.pop(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/ChunkRenderCacheLocalMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling; 2 | 3 | import me.contaria.seedqueue.debug.SeedQueueProfiler; 4 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | /** 11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering. 12 | * These Mixins will be removed in later versions of SeedQueue. 13 | */ 14 | @Mixin(value = ChunkRenderCacheLocal.class, remap = false) 15 | public abstract class ChunkRenderCacheLocalMixin { 16 | 17 | @Inject( 18 | method = "", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lme/jellysquid/mods/sodium/client/world/WorldSlice;(Lnet/minecraft/world/World;)V" 22 | ), 23 | remap = true 24 | ) 25 | private void profileWorldSlice(CallbackInfo ci) { 26 | SeedQueueProfiler.push("world_slice"); 27 | } 28 | 29 | @Inject( 30 | method = "", 31 | at = @At( 32 | value = "INVOKE", 33 | target = "Lme/jellysquid/mods/sodium/client/model/light/cache/ArrayLightDataCache;(Lnet/minecraft/world/BlockRenderView;)V" 34 | ), 35 | remap = true 36 | ) 37 | private void profileLightDataCache(CallbackInfo ci) { 38 | SeedQueueProfiler.swap("light_data_cache"); 39 | } 40 | 41 | @Inject( 42 | method = "", 43 | at = @At( 44 | value = "INVOKE", 45 | target = "Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;(Lme/jellysquid/mods/sodium/client/model/light/data/LightDataAccess;)V", 46 | remap = false 47 | ), 48 | remap = true 49 | ) 50 | private void profileLightPipelineProvider(CallbackInfo ci) { 51 | SeedQueueProfiler.swap("light_pipeline_provider"); 52 | } 53 | 54 | @Inject( 55 | method = "", 56 | at = @At( 57 | value = "INVOKE", 58 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;createBiomeColorBlender()Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;", 59 | remap = false 60 | ), 61 | remap = true 62 | ) 63 | private void profileBiomeColorBlender(CallbackInfo ci) { 64 | SeedQueueProfiler.swap("biome_color_blender"); 65 | } 66 | 67 | @Inject( 68 | method = "", 69 | at = @At( 70 | value = "INVOKE", 71 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/BlockRenderer;(Lnet/minecraft/client/MinecraftClient;Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;)V" 72 | ), 73 | remap = true 74 | ) 75 | private void profileBlockRenderer(CallbackInfo ci) { 76 | SeedQueueProfiler.swap("block_renderer"); 77 | } 78 | 79 | @Inject( 80 | method = "", 81 | at = @At( 82 | value = "INVOKE", 83 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/FluidRenderer;(Lnet/minecraft/client/MinecraftClient;Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;)V" 84 | ), 85 | remap = true 86 | ) 87 | private void profileFluidRenderer(CallbackInfo ci) { 88 | SeedQueueProfiler.swap("fluid_renderer"); 89 | } 90 | 91 | @Inject( 92 | method = "", 93 | at = @At( 94 | value = "INVOKE", 95 | target = "Lnet/minecraft/client/render/model/BakedModelManager;getBlockModels()Lnet/minecraft/client/render/block/BlockModels;" 96 | ), 97 | remap = true 98 | ) 99 | private void profileBlockModels(CallbackInfo ci) { 100 | SeedQueueProfiler.swap("block_models"); 101 | } 102 | 103 | @Inject( 104 | method = "", 105 | at = @At("TAIL"), 106 | remap = true 107 | ) 108 | private void profilePop(CallbackInfo ci) { 109 | SeedQueueProfiler.pop(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/MultidrawChunkRenderBackendMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling; 2 | 3 | import me.contaria.seedqueue.debug.SeedQueueProfiler; 4 | import me.jellysquid.mods.sodium.client.render.chunk.backends.multidraw.MultidrawChunkRenderBackend; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | /** 11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering. 12 | * These Mixins will be removed in later versions of SeedQueue. 13 | */ 14 | @Mixin(value = MultidrawChunkRenderBackend.class, remap = false) 15 | public abstract class MultidrawChunkRenderBackendMixin { 16 | 17 | @Inject( 18 | method = "upload", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/backends/multidraw/MultidrawChunkRenderBackend;setupUploadBatches(Ljava/util/Iterator;)V" 22 | ) 23 | ) 24 | private void profileSetupUploadBatches(CallbackInfo ci) { 25 | SeedQueueProfiler.push("setup_upload_batches"); 26 | } 27 | 28 | @Inject( 29 | method = "upload", 30 | at = @At( 31 | value = "INVOKE", 32 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;bindBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlBufferTarget;Lme/jellysquid/mods/sodium/client/gl/buffer/GlBuffer;)V" 33 | ) 34 | ) 35 | private void profileBindBuffer(CallbackInfo ci) { 36 | SeedQueueProfiler.swap("bind_buffer"); 37 | } 38 | 39 | @Inject( 40 | method = "upload", 41 | at = @At( 42 | value = "INVOKE", 43 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;bindBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlBufferTarget;Lme/jellysquid/mods/sodium/client/gl/buffer/GlBuffer;)V", 44 | shift = At.Shift.AFTER 45 | ) 46 | ) 47 | private void profileIterateQueue(CallbackInfo ci) { 48 | SeedQueueProfiler.swap("iterate_queue"); 49 | } 50 | 51 | @Inject( 52 | method = "upload", 53 | at = @At( 54 | value = "INVOKE", 55 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayFIFOQueue;dequeue()Ljava/lang/Object;" 56 | ) 57 | ) 58 | private void profileDequeueUpload(CallbackInfo ci) { 59 | SeedQueueProfiler.push("dequeue_upload"); 60 | } 61 | 62 | @Inject( 63 | method = "upload", 64 | at = @At( 65 | value = "INVOKE", 66 | target = "Lme/jellysquid/mods/sodium/client/gl/arena/GlBufferArena;prepareBuffer(Lme/jellysquid/mods/sodium/client/gl/device/CommandList;I)V" 67 | ) 68 | ) 69 | private void profilePrepareBuffer(CallbackInfo ci) { 70 | SeedQueueProfiler.swap("prepare_buffer"); 71 | } 72 | 73 | @Inject( 74 | method = "upload", 75 | at = @At( 76 | value = "INVOKE", 77 | target = "Lme/jellysquid/mods/sodium/client/gl/arena/GlBufferArena;prepareBuffer(Lme/jellysquid/mods/sodium/client/gl/device/CommandList;I)V", 78 | shift = At.Shift.AFTER 79 | ) 80 | ) 81 | private void profileIterateResults(CallbackInfo ci) { 82 | SeedQueueProfiler.swap("iterate_results"); 83 | } 84 | 85 | @Inject( 86 | method = "upload", 87 | at = @At( 88 | value = "INVOKE", 89 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;getTessellation()Lme/jellysquid/mods/sodium/client/gl/tessellation/GlTessellation;", 90 | ordinal = 0 91 | ) 92 | ) 93 | private void profileUpdateTesselation(CallbackInfo ci) { 94 | SeedQueueProfiler.swap("update_tesselation"); 95 | } 96 | 97 | @Inject( 98 | method = "upload", 99 | at = @At( 100 | value = "INVOKE", 101 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;clear()V" 102 | ) 103 | ) 104 | private void profileClearQueue(CallbackInfo ci) { 105 | SeedQueueProfiler.swap("clear_queue"); 106 | } 107 | 108 | @Inject( 109 | method = "upload", 110 | at = @At( 111 | value = "INVOKE", 112 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;clear()V", 113 | shift = At.Shift.AFTER 114 | ) 115 | ) 116 | private void profilePop_iterateQueue(CallbackInfo ci) { 117 | SeedQueueProfiler.pop(); 118 | } 119 | 120 | @Inject( 121 | method = "upload", 122 | at = @At( 123 | value = "INVOKE", 124 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;invalidateBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlMutableBuffer;)V" 125 | ) 126 | ) 127 | private void profileInvalidateBuffer(CallbackInfo ci) { 128 | SeedQueueProfiler.swap("invalidate_buffer"); 129 | } 130 | 131 | @Inject( 132 | method = "upload", 133 | at = @At("RETURN") 134 | ) 135 | private void profilePop_upload(CallbackInfo ci) { 136 | SeedQueueProfiler.pop(); 137 | } 138 | 139 | @Inject( 140 | method = "setupUploadBatches", 141 | at = @At( 142 | value = "INVOKE", 143 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegionManager;getRegion(III)Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;" 144 | ) 145 | ) 146 | private void profileGetRegion(CallbackInfo ci) { 147 | SeedQueueProfiler.push("get_region"); 148 | } 149 | 150 | @Inject( 151 | method = "setupUploadBatches", 152 | at = @At( 153 | value = "INVOKE", 154 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/ChunkRenderContainer;setData(Lme/jellysquid/mods/sodium/client/render/chunk/data/ChunkRenderData;)V" 155 | ) 156 | ) 157 | private void profilePop_setupUploadBatches_1(CallbackInfo ci) { 158 | SeedQueueProfiler.pop(); 159 | } 160 | 161 | @Inject( 162 | method = "setupUploadBatches", 163 | at = @At( 164 | value = "INVOKE", 165 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegionManager;getOrCreateRegion(III)Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;" 166 | ) 167 | ) 168 | private void profileCreateRegion(CallbackInfo ci) { 169 | SeedQueueProfiler.swap("create_region"); 170 | } 171 | 172 | @Inject( 173 | method = "setupUploadBatches", 174 | at = @At( 175 | value = "INVOKE", 176 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;getUploadQueue()Lit/unimi/dsi/fastutil/objects/ObjectArrayList;" 177 | ) 178 | ) 179 | private void profileAddToQueue(CallbackInfo ci) { 180 | SeedQueueProfiler.swap("add_to_queue"); 181 | } 182 | 183 | @Inject( 184 | method = "setupUploadBatches", 185 | at = @At( 186 | value = "INVOKE", 187 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;add(Ljava/lang/Object;)Z", 188 | shift = At.Shift.AFTER 189 | ) 190 | ) 191 | private void profilePop_setupUploadBatches_2(CallbackInfo ci) { 192 | SeedQueueProfiler.pop(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/standardsettings/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.standardsettings; 2 | 3 | import com.bawnorton.mixinsquared.TargetHandler; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import net.minecraft.client.MinecraftClient; 6 | import org.spongepowered.asm.mixin.Dynamic; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(MinecraftClient.class) 13 | public abstract class MinecraftClientMixin { 14 | 15 | @Dynamic 16 | @TargetHandler( 17 | mixin = "me.contaria.standardsettings.mixin.MinecraftClientMixin", 18 | name = "reset" 19 | ) 20 | @Inject( 21 | method = "@MixinSquared:Handler", 22 | at = @At("HEAD"), 23 | cancellable = true 24 | ) 25 | private void loadSettingsCache(CallbackInfo ci) { 26 | if (!SeedQueue.inQueue() && SeedQueue.currentEntry != null && SeedQueue.currentEntry.loadSettingsCache()) { 27 | ci.cancel(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/ThreadedAnvilChunkStorageMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview; 2 | 3 | import com.bawnorton.mixinsquared.TargetHandler; 4 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import me.contaria.seedqueue.SeedQueueEntry; 7 | import me.contaria.seedqueue.interfaces.SQMinecraftServer; 8 | import me.voidxwalker.worldpreview.WorldPreviewProperties; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.client.options.Option; 11 | import net.minecraft.server.world.ServerWorld; 12 | import net.minecraft.server.world.ThreadedAnvilChunkStorage; 13 | import org.spongepowered.asm.mixin.*; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | 16 | import java.util.Optional; 17 | 18 | @Mixin(value = ThreadedAnvilChunkStorage.class, priority = 1500) 19 | public abstract class ThreadedAnvilChunkStorageMixin { 20 | 21 | @Shadow 22 | @Final 23 | private ServerWorld world; 24 | 25 | @Dynamic 26 | @TargetHandler( 27 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin", 28 | name = "worldpreview$sendData" 29 | ) 30 | @ModifyExpressionValue( 31 | method = "@MixinSquared:Handler", 32 | at = @At( 33 | value = "FIELD", 34 | target = "Lme/voidxwalker/worldpreview/WorldPreview;properties:Lme/voidxwalker/worldpreview/WorldPreviewProperties;", 35 | remap = false 36 | ) 37 | ) 38 | private WorldPreviewProperties sendChunksToCorrectWorldPreview_inQueue(WorldPreviewProperties properties) { 39 | return this.getWorldPreviewProperties().orElse(this.isActiveServer() ? properties : null); 40 | } 41 | 42 | @Dynamic 43 | @TargetHandler( 44 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin", 45 | name = "updateFrustum" 46 | ) 47 | @ModifyExpressionValue( 48 | method = "@MixinSquared:Handler", 49 | at = @At( 50 | value = "FIELD", 51 | target = "Lnet/minecraft/client/options/GameOptions;fov:D" 52 | ) 53 | ) 54 | private double modifyCullingFov_inQueue(double fov) { 55 | if (!this.isActiveServer()) { 56 | // trying to keep track of the FOV in the settings cache / standardsettings is unnecessarily complicated 57 | // the majority of people will use quake pro with their personal FOV as fovOnWorldJoin anyway 58 | return Option.FOV.getMax(); 59 | } 60 | return fov; 61 | } 62 | 63 | @Dynamic 64 | @TargetHandler( 65 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin", 66 | name = "updateFrustum" 67 | ) 68 | @ModifyExpressionValue( 69 | method = "@MixinSquared:Handler", 70 | at = @At( 71 | value = "INVOKE", 72 | target = "Lnet/minecraft/client/util/Window;getFramebufferWidth()I" 73 | ) 74 | ) 75 | private int modifyCullingWindowWidth(int width) { 76 | if (!this.isActiveServer()) { 77 | return SeedQueue.config.simulatedWindowSize.width(); 78 | } 79 | return width; 80 | } 81 | 82 | @Dynamic 83 | @TargetHandler( 84 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin", 85 | name = "updateFrustum" 86 | ) 87 | @ModifyExpressionValue( 88 | method = "@MixinSquared:Handler", 89 | at = @At( 90 | value = "INVOKE", 91 | target = "Lnet/minecraft/client/util/Window;getFramebufferHeight()I" 92 | ) 93 | ) 94 | private int modifyCullingWindowHeight(int height) { 95 | if (!this.isActiveServer()) { 96 | return SeedQueue.config.simulatedWindowSize.height(); 97 | } 98 | return height; 99 | } 100 | 101 | @Unique 102 | private Optional getWorldPreviewProperties() { 103 | return ((SQMinecraftServer) this.world.getServer()).seedQueue$getEntry().filter(entry -> !(SeedQueue.config.freezeLockedPreviews && entry.isLocked())).map(SeedQueueEntry::getPreviewProperties); 104 | } 105 | 106 | @Unique 107 | private boolean isActiveServer() { 108 | return this.world.getServer() == MinecraftClient.getInstance().getServer(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/WorldPreviewMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 6 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 7 | import me.contaria.seedqueue.SeedQueueEntry; 8 | import me.contaria.seedqueue.compat.SeedQueuePreviewProperties; 9 | import me.contaria.seedqueue.interfaces.SQMinecraftServer; 10 | import me.contaria.speedrunapi.config.SpeedrunConfigAPI; 11 | import me.voidxwalker.worldpreview.WorldPreview; 12 | import net.minecraft.client.network.ClientPlayerEntity; 13 | import net.minecraft.client.network.ClientPlayerInteractionManager; 14 | import net.minecraft.client.render.Camera; 15 | import net.minecraft.client.world.ClientWorld; 16 | import net.minecraft.entity.data.DataTracker; 17 | import net.minecraft.entity.data.TrackedData; 18 | import net.minecraft.network.Packet; 19 | import net.minecraft.server.world.ServerWorld; 20 | import org.spongepowered.asm.mixin.Mixin; 21 | import org.spongepowered.asm.mixin.injection.At; 22 | 23 | import java.util.Optional; 24 | import java.util.Queue; 25 | 26 | @Mixin(WorldPreview.class) 27 | public abstract class WorldPreviewMixin { 28 | 29 | @WrapWithCondition( 30 | method = "configure", 31 | at = @At( 32 | value = "INVOKE", 33 | target = "Lnet/minecraft/entity/data/DataTracker;set(Lnet/minecraft/entity/data/TrackedData;Ljava/lang/Object;)V" 34 | ) 35 | ) 36 | private static boolean doNotSetPlayerModelParts_inQueue(DataTracker tracker, TrackedData key, Object object, ServerWorld serverWorld) { 37 | return !((SQMinecraftServer) serverWorld.getServer()).seedQueue$inQueue(); 38 | } 39 | 40 | @ModifyExpressionValue( 41 | method = "configure", 42 | at = @At( 43 | value = "FIELD", 44 | target = "Lnet/minecraft/client/options/GameOptions;perspective:I" 45 | ) 46 | ) 47 | private static int modifyPerspective_inQueue(int perspective, ServerWorld serverWorld) { 48 | if (((SQMinecraftServer) serverWorld.getServer()).seedQueue$inQueue()) { 49 | return (int) SpeedrunConfigAPI.getConfigValueOptionally("standardsettings", "perspective").orElse(0); 50 | } 51 | return perspective; 52 | } 53 | 54 | @WrapOperation( 55 | method = "configure", 56 | at = @At( 57 | value = "INVOKE", 58 | target = "Lme/voidxwalker/worldpreview/WorldPreview;set(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/ClientPlayerEntity;Lnet/minecraft/client/network/ClientPlayerInteractionManager;Lnet/minecraft/client/render/Camera;Ljava/util/Queue;)V" 59 | ) 60 | ) 61 | private static void doNotConfigureWorldPreview_inQueue(ClientWorld world, ClientPlayerEntity player, ClientPlayerInteractionManager interactionManager, Camera camera, Queue> packetQueue, Operation original, ServerWorld serverWorld) { 62 | Optional entry = ((SQMinecraftServer) serverWorld.getServer()).seedQueue$getEntry(); 63 | if (entry.isPresent()) { 64 | entry.get().setPreviewProperties(new SeedQueuePreviewProperties(world, player, interactionManager, camera, packetQueue)); 65 | return; 66 | } 67 | original.call(world, player, interactionManager, camera, packetQueue); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/ClientWorldMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.voidxwalker.worldpreview.WorldPreview; 6 | import net.minecraft.client.render.WorldRenderer; 7 | import net.minecraft.client.world.ClientWorld; 8 | import org.objectweb.asm.Opcodes; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | 12 | @Mixin(ClientWorld.class) 13 | public abstract class ClientWorldMixin { 14 | 15 | // WorldPreview worlds always get created with the original WorldPreview#worldRenderer, 16 | // but on the Wall Screen a different WorldRenderer is used, 17 | // so we redirect to the currently used WorldRenderer instead 18 | @ModifyExpressionValue( 19 | method = "*", 20 | at = @At( 21 | value = "FIELD", 22 | target = "Lnet/minecraft/client/world/ClientWorld;worldRenderer:Lnet/minecraft/client/render/WorldRenderer;", 23 | opcode = Opcodes.GETFIELD 24 | ) 25 | ) 26 | private WorldRenderer modifyWorldRenderer(WorldRenderer worldRenderer) { 27 | if (SeedQueue.isOnWall()) { 28 | return WorldPreview.worldRenderer; 29 | } 30 | return worldRenderer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/InGameHudMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 5 | import me.contaria.seedqueue.SeedQueue; 6 | import net.minecraft.client.gui.DrawableHelper; 7 | import net.minecraft.client.gui.hud.ChatHud; 8 | import net.minecraft.client.gui.hud.InGameHud; 9 | import net.minecraft.client.gui.hud.SubtitlesHud; 10 | import net.minecraft.client.util.math.MatrixStack; 11 | import net.minecraft.text.Text; 12 | import org.objectweb.asm.Opcodes; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 16 | 17 | @Mixin(InGameHud.class) 18 | public abstract class InGameHudMixin extends DrawableHelper { 19 | 20 | @WrapWithCondition( 21 | method = "render", 22 | at = @At( 23 | value = "INVOKE", 24 | target = "Lnet/minecraft/client/gui/hud/SubtitlesHud;render(Lnet/minecraft/client/util/math/MatrixStack;)V" 25 | ) 26 | ) 27 | private boolean doNotRenderSubtitlesOnWall(SubtitlesHud subtitlesHud, MatrixStack matrices) { 28 | return !SeedQueue.isOnWall(); 29 | } 30 | 31 | @WrapWithCondition( 32 | method = "render", 33 | at = @At( 34 | value = "INVOKE", 35 | target = "Lnet/minecraft/client/gui/hud/ChatHud;render(Lnet/minecraft/client/util/math/MatrixStack;I)V" 36 | ) 37 | ) 38 | private boolean doNotRenderChatOnWall(ChatHud chatHud, MatrixStack matrices, int i) { 39 | return !SeedQueue.isOnWall(); 40 | } 41 | 42 | @ModifyExpressionValue( 43 | method = "render", 44 | at = @At( 45 | value = "FIELD", 46 | target = "Lnet/minecraft/client/gui/hud/InGameHud;overlayMessage:Lnet/minecraft/text/Text;", 47 | opcode = Opcodes.GETFIELD, 48 | ordinal = 0 49 | ) 50 | ) 51 | private Text doNotRenderOverlayMessageOnWall(Text overlayMessage) { 52 | if (SeedQueue.isOnWall()) { 53 | return null; 54 | } 55 | return overlayMessage; 56 | } 57 | 58 | @ModifyExpressionValue( 59 | method = "render", 60 | at = @At( 61 | value = "FIELD", 62 | target = "Lnet/minecraft/client/gui/hud/InGameHud;title:Lnet/minecraft/text/Text;", 63 | opcode = Opcodes.GETFIELD, 64 | ordinal = 0 65 | ) 66 | ) 67 | private Text doNotRenderTitleMessageOnWall(Text title) { 68 | if (SeedQueue.isOnWall()) { 69 | return null; 70 | } 71 | return title; 72 | } 73 | 74 | @ModifyVariable( 75 | method = "renderStatusBars", 76 | at = @At("STORE") 77 | ) 78 | private boolean doNotRenderBlinkingHeartsOnWall(boolean blinking) { 79 | return blinking && !SeedQueue.isOnWall(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/WindowMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.contaria.seedqueue.gui.wall.SeedQueueWallScreen; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.client.util.Window; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | 12 | @Mixin(Window.class) 13 | public abstract class WindowMixin { 14 | 15 | @ModifyReturnValue( 16 | method = { 17 | "getWidth", 18 | "getFramebufferWidth" 19 | }, 20 | at = @At("RETURN") 21 | ) 22 | private int modifyWidthOnWall(int width) { 23 | if (this.shouldModifyWindowSize()) { 24 | return SeedQueue.config.simulatedWindowSize.width(); 25 | } 26 | return width; 27 | } 28 | 29 | @ModifyReturnValue( 30 | method = "getScaledWidth", 31 | at = @At("RETURN") 32 | ) 33 | private int modifyScaledWidthOnWall(int width) { 34 | if (this.shouldModifyWindowSize()) { 35 | return SeedQueue.config.simulatedWindowSize.width() / this.modifiedScaleFactor(); 36 | } 37 | return width; 38 | } 39 | 40 | @ModifyReturnValue( 41 | method = { 42 | "getHeight", 43 | "getFramebufferHeight" 44 | }, 45 | at = @At("RETURN") 46 | ) 47 | private int modifyHeightOnWall(int height) { 48 | if (this.shouldModifyWindowSize()) { 49 | return SeedQueue.config.simulatedWindowSize.height(); 50 | } 51 | return height; 52 | } 53 | 54 | @ModifyReturnValue( 55 | method = "getScaledHeight", 56 | at = @At("RETURN") 57 | ) 58 | private int modifyScaledHeightOnWall(int height) { 59 | if (this.shouldModifyWindowSize()) { 60 | return SeedQueue.config.simulatedWindowSize.height() / this.modifiedScaleFactor(); 61 | } 62 | return height; 63 | } 64 | 65 | @ModifyReturnValue( 66 | method = "getScaleFactor", 67 | at = @At("RETURN") 68 | ) 69 | private double modifyScaleFactorOnWall(double scaleFactor) { 70 | if (this.shouldModifyWindowSize()) { 71 | return this.modifiedScaleFactor(); 72 | } 73 | return scaleFactor; 74 | } 75 | 76 | @Unique 77 | private boolean shouldModifyWindowSize() { 78 | return MinecraftClient.getInstance().isOnThread() && SeedQueue.isOnWall() && SeedQueueWallScreen.shouldModifyWindowSize(); 79 | } 80 | 81 | @Unique 82 | private int modifiedScaleFactor() { 83 | return SeedQueue.config.calculateSimulatedScaleFactor(MinecraftClient.getInstance().options.guiScale, MinecraftClient.getInstance().options.forceUnicodeFont); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/WorldRendererMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import me.contaria.seedqueue.SeedQueue; 5 | import me.contaria.seedqueue.interfaces.worldpreview.SQWorldRenderer; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.client.render.Camera; 8 | import net.minecraft.client.render.Frustum; 9 | import net.minecraft.client.render.WorldRenderer; 10 | import net.minecraft.client.util.math.MatrixStack; 11 | import net.minecraft.client.world.ClientWorld; 12 | import net.minecraft.util.math.Matrix4f; 13 | import net.minecraft.util.math.Vec3d; 14 | import net.minecraft.util.profiler.Profiler; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | 20 | @Mixin(WorldRenderer.class) 21 | public abstract class WorldRendererMixin implements SQWorldRenderer { 22 | 23 | @Shadow 24 | @Final 25 | private MinecraftClient client; 26 | @Shadow 27 | private ClientWorld world; 28 | @Shadow 29 | private int frame; 30 | 31 | @Shadow 32 | protected abstract void setupTerrain(Camera camera, Frustum frustum, boolean hasForcedFrustum, int frame, boolean spectator); 33 | 34 | @Shadow 35 | protected abstract void updateChunks(long limitTime); 36 | 37 | @WrapWithCondition( 38 | method = "render", 39 | at = @At( 40 | value = "INVOKE", 41 | target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V" 42 | ) 43 | ) 44 | private boolean doNotClearOnWallScreen(int mask, boolean getError, MatrixStack matrices) { 45 | return !SeedQueue.isOnWall(); 46 | } 47 | 48 | @Override 49 | public void seedQueue$buildChunks(MatrixStack matrices, Camera camera, Matrix4f projectionMatrix) { 50 | Profiler profiler = this.client.getProfiler(); 51 | 52 | profiler.push("light_updates"); 53 | this.world.getChunkManager().getLightingProvider().doLightUpdates(Integer.MAX_VALUE, true, true); 54 | 55 | profiler.swap("culling"); 56 | Vec3d pos = camera.getPos(); 57 | Frustum frustum = new Frustum(matrices.peek().getModel(), projectionMatrix); 58 | frustum.setPosition(pos.getX(), pos.getY(), pos.getZ()); 59 | 60 | profiler.swap("terrain_setup"); 61 | this.setupTerrain(camera, frustum, false, this.frame++, this.client.player.isSpectator()); 62 | 63 | profiler.swap("updatechunks"); 64 | this.updateChunks(0); 65 | 66 | profiler.pop(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/IntegratedServerMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import net.minecraft.client.network.ClientPlayNetworkHandler; 5 | import net.minecraft.server.integrated.IntegratedServer; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | 9 | @Mixin(IntegratedServer.class) 10 | public abstract class IntegratedServerMixin extends MinecraftServerMixin { 11 | 12 | public IntegratedServerMixin(String string) { 13 | super(string); 14 | } 15 | 16 | @ModifyExpressionValue( 17 | method = "tick", 18 | at = @At( 19 | value = "INVOKE", 20 | target = "Lnet/minecraft/client/MinecraftClient;getNetworkHandler()Lnet/minecraft/client/network/ClientPlayNetworkHandler;" 21 | ) 22 | ) 23 | private ClientPlayNetworkHandler doNotPauseBackgroundWorlds(ClientPlayNetworkHandler networkHandler) { 24 | if (this.seedQueue$inQueue()) { 25 | return null; 26 | } 27 | return networkHandler; 28 | } 29 | 30 | @ModifyExpressionValue( 31 | method = "tick", 32 | at = @At( 33 | value = "INVOKE", 34 | target = "Ljava/lang/Math;max(II)I" 35 | ) 36 | ) 37 | private int doNotChangeViewDistanceInQueue(int viewDistance) { 38 | if (this.seedQueue$inQueue()) { 39 | return this.getPlayerManager().getViewDistance(); 40 | } 41 | return viewDistance; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/optimization/ThreadedAnvilChunkStorageMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.optimization; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import me.contaria.seedqueue.interfaces.SQMinecraftServer; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.server.world.ServerWorld; 8 | import net.minecraft.server.world.ThreadedAnvilChunkStorage; 9 | import net.minecraft.util.math.ChunkPos; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | 15 | @Mixin(ThreadedAnvilChunkStorage.class) 16 | public abstract class ThreadedAnvilChunkStorageMixin { 17 | 18 | @Shadow 19 | @Final 20 | private ServerWorld world; 21 | 22 | // careful with this, chunkcacher injects shortly after 23 | @WrapOperation( 24 | method = "getUpdatedChunkTag", 25 | at = @At( 26 | value = "INVOKE", 27 | target = "Lnet/minecraft/server/world/ThreadedAnvilChunkStorage;getNbt(Lnet/minecraft/util/math/ChunkPos;)Lnet/minecraft/nbt/CompoundTag;" 28 | ) 29 | ) 30 | private CompoundTag skipGettingNbtInQueue(ThreadedAnvilChunkStorage storage, ChunkPos pos, Operation original) { 31 | // we can skip checking storage for chunk nbt while we're in queue since no chunks have been saved yet anyway 32 | if (((SQMinecraftServer) this.world.getServer()).seedQueue$inQueue()) { 33 | return null; 34 | } 35 | return original.call(storage, pos); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/parity/EntityMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.parity; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import me.contaria.seedqueue.interfaces.SQMinecraftServer; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.entity.EntityType; 8 | import net.minecraft.server.MinecraftServer; 9 | import net.minecraft.world.World; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | 13 | @Mixin(value = Entity.class, priority = 500) 14 | public abstract class EntityMixin { 15 | 16 | @ModifyExpressionValue( 17 | method = "", 18 | at = @At( 19 | value = "INVOKE", 20 | target = "Ljava/util/concurrent/atomic/AtomicInteger;incrementAndGet()I" 21 | ) 22 | ) 23 | private int incrementEntityIdPerServer(int id, EntityType type, World world) { 24 | // fallback for worldpreview entities 25 | // technically if the counter overflows and wraps back all the way around, 26 | // it could naturally reach -1 but that is imo within the realm of "whatever, shit happens" 27 | if (id == -1) { 28 | return id; 29 | } 30 | MinecraftServer server = world.getServer(); 31 | if (server == null) { 32 | // for entities created clientside, use the entity id counter of the currently active server 33 | // this preserves the behaviour of clientside entities affecting the serverside id counter 34 | server = MinecraftClient.getInstance().getServer(); 35 | } 36 | if (server == null) { 37 | // for entities created clientside while no server is active just use the id 38 | return id; 39 | } 40 | // by storing the max entity id counter per server, we ensure entity ID's will be in order 41 | // we initialize them in MinecraftServerMixin with the current max id to preserve parity when seedqueue isn't active 42 | // and also avoid entity ID's starting at 0 on every server when using seedqueue 43 | return ((SQMinecraftServer) server).seedQueue$incrementAndGetEntityID(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/synchronization/BiomeMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.synchronization; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.world.biome.Biome; 6 | import net.minecraft.world.chunk.Chunk; 7 | import net.minecraft.world.gen.surfacebuilder.ConfiguredSurfaceBuilder; 8 | import net.minecraft.world.gen.surfacebuilder.SurfaceBuilder; 9 | import org.spongepowered.asm.mixin.*; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | import java.util.Random; 15 | 16 | @Mixin(Biome.class) 17 | public abstract class BiomeMixin { 18 | 19 | @Unique 20 | private static final String initSeed = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_3523", "method_15306", "(J)V"); 21 | 22 | @Shadow 23 | @Final 24 | protected ConfiguredSurfaceBuilder surfaceBuilder; 25 | 26 | @Unique 27 | private boolean synchronizedAccess; 28 | 29 | @Inject( 30 | method = "*", 31 | at = @At("TAIL") 32 | ) 33 | private void shouldSynchronizeAccess(CallbackInfo ci) { 34 | Class clas = this.surfaceBuilder.surfaceBuilder.getClass(); 35 | while (clas != SurfaceBuilder.class) { 36 | try { 37 | clas.getDeclaredMethod(initSeed, long.class); 38 | this.synchronizedAccess = true; 39 | break; 40 | } catch (NoSuchMethodException e) { 41 | clas = clas.getSuperclass(); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * @author contaria 48 | * @reason Synchronize calls if necessary, WrapMethod is not used because it causes a lot of Object arrays to be allocated in hot code. 49 | */ 50 | @Overwrite 51 | public void buildSurface(Random random, Chunk chunk, int x, int z, int worldHeight, double noise, BlockState defaultBlock, BlockState defaultFluid, int seaLevel, long seed) { 52 | if (this.synchronizedAccess) { 53 | synchronized (this.surfaceBuilder.surfaceBuilder) { 54 | this.surfaceBuilder.initSeed(seed); 55 | this.surfaceBuilder.generate(random, chunk, (Biome) (Object) this, x, z, worldHeight, noise, defaultBlock, defaultFluid, seaLevel, seed); 56 | } 57 | } else { 58 | this.surfaceBuilder.initSeed(seed); 59 | this.surfaceBuilder.generate(random, chunk, (Biome) (Object) this, x, z, worldHeight, noise, defaultBlock, defaultFluid, seaLevel, seed); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/synchronization/DirectionTransformationMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.synchronization; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import net.minecraft.util.math.Direction; 6 | import net.minecraft.util.math.DirectionTransformation; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | 9 | @Mixin(DirectionTransformation.class) 10 | public abstract class DirectionTransformationMixin { 11 | 12 | @WrapMethod( 13 | method = "map" 14 | ) 15 | private Direction synchronizeMapDirections(Direction direction, Operation original) { 16 | synchronized (this) { 17 | return original.call(direction); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/synchronization/ScheduledTickMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.synchronization; 2 | 3 | import net.minecraft.world.ScheduledTick; 4 | import org.objectweb.asm.Opcodes; 5 | import org.spongepowered.asm.mixin.*; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Redirect; 8 | 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | @Mixin(ScheduledTick.class) 12 | public abstract class ScheduledTickMixin { 13 | 14 | @Unique 15 | private static final AtomicLong atomicIdCounter = new AtomicLong(); 16 | 17 | @Mutable 18 | @Shadow 19 | @Final 20 | private long id; 21 | 22 | @Redirect( 23 | method = "(Lnet/minecraft/util/math/BlockPos;Ljava/lang/Object;JLnet/minecraft/world/TickPriority;)V", 24 | at = @At( 25 | value = "FIELD", 26 | target = "Lnet/minecraft/world/ScheduledTick;id:J", 27 | opcode = Opcodes.PUTFIELD 28 | ) 29 | ) 30 | private void atomicIdCounter(ScheduledTick tick, long l) { 31 | this.id = atomicIdCounter.incrementAndGet(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/mixin/server/synchronization/WeightedBlockStateProviderMixin.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.mixin.server.synchronization; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import net.minecraft.util.collection.WeightedList; 6 | import net.minecraft.world.gen.stateprovider.WeightedBlockStateProvider; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | 10 | import java.util.Random; 11 | 12 | @Mixin(WeightedBlockStateProvider.class) 13 | public abstract class WeightedBlockStateProviderMixin { 14 | 15 | @WrapOperation( 16 | method = "addState", 17 | at = @At( 18 | value = "INVOKE", 19 | target = "Lnet/minecraft/util/collection/WeightedList;add(Ljava/lang/Object;I)Lnet/minecraft/util/collection/WeightedList;" 20 | ) 21 | ) 22 | private synchronized WeightedList synchronizeAddState(WeightedList list, Object item, int weight, Operation> original) { 23 | return original.call(list, item, weight); 24 | } 25 | 26 | @WrapOperation( 27 | method = "getBlockState", 28 | at = @At( 29 | value = "INVOKE", 30 | target = "Lnet/minecraft/util/collection/WeightedList;pickRandom(Ljava/util/Random;)Ljava/lang/Object;" 31 | ) 32 | ) 33 | private synchronized Object synchronizeGetBlockState(WeightedList list, Random random, Operation original) { 34 | return original.call(list, random); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/contaria/seedqueue/sounds/SeedQueueSounds.java: -------------------------------------------------------------------------------- 1 | package me.contaria.seedqueue.sounds; 2 | 3 | import me.contaria.speedrunapi.util.IdentifierUtil; 4 | import net.minecraft.client.MinecraftClient; 5 | import net.minecraft.client.sound.PositionedSoundInstance; 6 | import net.minecraft.client.sound.SoundInstance; 7 | import net.minecraft.client.sound.SoundManager; 8 | import net.minecraft.sound.SoundEvent; 9 | import net.minecraft.util.Identifier; 10 | import net.minecraft.util.registry.Registry; 11 | 12 | public class SeedQueueSounds { 13 | public static final SoundEvent PLAY_INSTANCE = register("play_instance"); 14 | public static final SoundEvent LOCK_INSTANCE = register("lock_instance"); 15 | public static final SoundEvent RESET_INSTANCE = register("reset_instance"); 16 | public static final SoundEvent RESET_ALL = register("reset_all"); 17 | public static final SoundEvent RESET_COLUMN = register("reset_column"); 18 | public static final SoundEvent RESET_ROW = register("reset_row"); 19 | public static final SoundEvent SCHEDULE_JOIN = register("schedule_join"); 20 | public static final SoundEvent SCHEDULE_ALL = register("schedule_all"); 21 | public static final SoundEvent SCHEDULED_JOIN_WARNING = register("scheduled_join_warning"); 22 | public static final SoundEvent START_BENCHMARK = register("start_benchmark"); 23 | public static final SoundEvent FINISH_BENCHMARK = register("finish_benchmark"); 24 | public static final SoundEvent OPEN_WALL = register("open_wall"); 25 | public static final SoundEvent BYPASS_WALL = register("bypass_wall"); 26 | 27 | public static void init() { 28 | } 29 | 30 | private static SoundEvent register(String id) { 31 | return register(IdentifierUtil.of("seedqueue", id)); 32 | } 33 | 34 | private static SoundEvent register(Identifier id) { 35 | return Registry.register(Registry.SOUND_EVENT, id, new SoundEvent(id)); 36 | } 37 | 38 | public static boolean play(SoundEvent sound) { 39 | SoundManager manager = MinecraftClient.getInstance().getSoundManager(); 40 | SoundInstance soundInstance = PositionedSoundInstance.master(sound, 1.0f); 41 | soundInstance.getSoundSet(manager); 42 | if (soundInstance.getSound().equals(SoundManager.MISSING_SOUND)) { 43 | return false; 44 | } 45 | manager.play(soundInstance); 46 | return true; 47 | } 48 | 49 | public static boolean play(SoundEvent sound, int delay) { 50 | SoundManager manager = MinecraftClient.getInstance().getSoundManager(); 51 | SoundInstance soundInstance = PositionedSoundInstance.master(sound, 1.0f); 52 | soundInstance.getSoundSet(manager); 53 | if (soundInstance.getSound().equals(SoundManager.MISSING_SOUND)) { 54 | return false; 55 | } 56 | manager.play(soundInstance, delay); 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/src/main/resources/assets/seedqueue/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "seedqueue.menu.clearing": "SeedQueue 清理中...", 3 | 4 | "seedqueue.menu.crash.title": "SeedQueue 崩溃了!", 5 | "seedqueue.menu.crash.description": "崩溃原因:%s, 检查日志以获取更多信息。", 6 | 7 | "seedqueue.menu.benchmark.title": "SeedQueue 跑分", 8 | "seedqueue.menu.benchmark.result": "%s次重置, 用时%s秒 (%s RPS)", 9 | 10 | "seedqueue.menu.config.useWall.notAvailable": "§c§o不可用", 11 | "seedqueue.menu.config.useWall.notAvailable.tooltip": "使用墙界面需要安装以下 mod:", 12 | "seedqueue.menu.config.useWall.notAvailable.tooltip.1": " - Sodium", 13 | "seedqueue.menu.config.useWall.notAvailable.tooltip.2": " - StandardSettings", 14 | "seedqueue.menu.config.useWall.notAvailable.tooltip.3": " - WorldPreview", 15 | 16 | "seedqueue.menu.config.showAdvancedSettings.confirm.title": "确定启用高级选项吗?", 17 | "seedqueue.menu.config.showAdvancedSettings.confirm.message": "请不要在不清楚功能的情况下随意调整这些设置, 以免造成问题!", 18 | 19 | "seedqueue.menu.keys": "墙界面按键控制", 20 | "seedqueue.menu.keys.configure": "设置", 21 | "seedqueue.menu.keys.primary": "主快捷键", 22 | "seedqueue.menu.keys.secondary": "§7§o副快捷键", 23 | "seedqueue.menu.keys.secondary.tooltip": "设置副快捷键后, 同时按下主副快捷键才会触发功能。", 24 | "seedqueue.menu.keys.blocking": "§7§o屏蔽键", 25 | "seedqueue.menu.keys.blocking.tooltip": "设置屏蔽键后, 屏蔽键按下时不会触发功能。", 26 | "seedqueue.menu.keys.secondary_list": "§7§o副快捷键:", 27 | "seedqueue.menu.keys.blocking_list": "§7§o屏蔽键:", 28 | 29 | "seedqueue.key.categories.builtin": "内置键位绑定", 30 | "seedqueue.key.categories.custom": "自定义键位绑定", 31 | "seedqueue.key.play": "进入实例", 32 | "seedqueue.key.lock": "锁定实例", 33 | "seedqueue.key.reset": "重置实例", 34 | "seedqueue.key.resetAll": "重置所有", 35 | "seedqueue.key.focusReset": "进入并重置其他", 36 | "seedqueue.key.resetRow": "重置行", 37 | "seedqueue.key.resetColumn": "重置列", 38 | "seedqueue.key.playNextLock": "进入下个锁定实例", 39 | "seedqueue.key.scheduleJoin": "计划实例", 40 | "seedqueue.key.scheduleAll": "计划所有", 41 | "seedqueue.key.startBenchmark": "开始跑分", 42 | "seedqueue.key.cancelBenchmark": "结束跑分", 43 | 44 | "speedrunapi.config.seedqueue.category.queue": "队列选项", 45 | "speedrunapi.config.seedqueue.category.wall": "墙界面选项", 46 | "speedrunapi.config.seedqueue.category.performance": "性能选项", 47 | "speedrunapi.config.seedqueue.category.misc": "杂项选项", 48 | "speedrunapi.config.seedqueue.category.advanced": "进阶选项", 49 | "speedrunapi.config.seedqueue.category.threading": "线程选项", 50 | "speedrunapi.config.seedqueue.category.experimental": "实验选项", 51 | "speedrunapi.config.seedqueue.category.debug": "调试选项", 52 | 53 | "speedrunapi.config.seedqueue.option.maxCapacity": "种子队列上限", 54 | "speedrunapi.config.seedqueue.option.maxCapacity.description": "队列中可容纳种子的最大数量。\n达到限制后, 只有在当前种子被重置后才会继续加载种子", 55 | "speedrunapi.config.seedqueue.option.maxConcurrently": "种子加载上限", 56 | "speedrunapi.config.seedqueue.option.maxConcurrently.description": "可以同时生成的最大种子数。", 57 | "speedrunapi.config.seedqueue.option.maxConcurrently_onWall": "种子加载上限(墙界面)", 58 | "speedrunapi.config.seedqueue.option.maxConcurrently_onWall.description": "在墙界面上生成的种子的最大数量。", 59 | "speedrunapi.config.seedqueue.option.maxWorldGenerationPercentage": "最大世界加载进度%", 60 | "speedrunapi.config.seedqueue.option.maxWorldGenerationPercentage.description": "在未锁定前每个种子的世界最大加载进度。\n请注意, 在停止加载前仍需等待世界预览启动完成", 61 | "speedrunapi.config.seedqueue.option.maxWorldGenerationPercentage.value": "%s%%", 62 | "speedrunapi.config.seedqueue.option.resumeOnFilledQueue": "恢复世界加载", 63 | "speedrunapi.config.seedqueue.option.resumeOnFilledQueue.description": "种子队列填满后是否恢复种子的世界加载。\n种子队列填满后,将无视\"最大世界加载进度\"选项", 64 | "speedrunapi.config.seedqueue.option.chunkMapFreezing": "区块冻结", 65 | "speedrunapi.config.seedqueue.option.chunkMapFreezing.description": "在设定的毫秒数后冻结区块地图。适用于\"村庄重置\"策略。仅适用于墙上未锁定的世界。", 66 | "speedrunapi.config.seedqueue.option.chunkMapFreezing.value": "%s毫秒", 67 | "speedrunapi.config.seedqueue.option.chunkMapFreezing.value.-1": "关", 68 | "speedrunapi.config.seedqueue.option.useWall": "使用墙界面", 69 | "speedrunapi.config.seedqueue.option.useWall.description": "以墙的形式展示加载的种子,你可以在其中进入或重置世界。", 70 | "speedrunapi.config.seedqueue.option.rows": "行数", 71 | "speedrunapi.config.seedqueue.option.columns": "列数", 72 | "speedrunapi.config.seedqueue.option.simulatedWindowSize": "模拟窗口尺寸", 73 | "speedrunapi.config.seedqueue.option.resetCooldown": "重置冷却", 74 | "speedrunapi.config.seedqueue.option.resetCooldown.value": "%s毫秒", 75 | "speedrunapi.config.seedqueue.option.resetCooldown.value.1000": "1 秒", 76 | "speedrunapi.config.seedqueue.option.resetCooldown.description": "实例重置的冷却时间, 从实例开始渲染时开始计算。", 77 | "speedrunapi.config.seedqueue.option.waitForPreviewSetup": "等待渲染预备", 78 | "speedrunapi.config.seedqueue.option.waitForPreviewSetup.description": "若启用, 种子只有在准备好渲染预览后才会开始在墙屏幕上显示。\n如果您想在渲染预备期间查看区块加载进度图, 可以禁用此选项。", 79 | "speedrunapi.config.seedqueue.option.bypassWall": "跳过墙界面", 80 | "speedrunapi.config.seedqueue.option.bypassWall.description": "若启用, 当重置当前正在游玩的实例后, 不会强制回到墙界面, 而是继续进入下一个被锁定的实例, 直到没有下一个锁定的实例。", 81 | "speedrunapi.config.seedqueue.option.wallFPS": "墙界面 FPS", 82 | "speedrunapi.config.seedqueue.option.wallFPS.description": "在墙界面的帧数限制", 83 | "speedrunapi.config.seedqueue.option.wallFPS.value": "%s fps", 84 | "speedrunapi.config.seedqueue.option.wallFPS.value.255": "无限制", 85 | "speedrunapi.config.seedqueue.option.previewFPS": "预览界面 FPS", 86 | "speedrunapi.config.seedqueue.option.previewFPS.description": "墙界面中实例预览界面的帧数限制。\n这与墙实际渲染的速度有关。因此当墙界面 FPS 被设置为无限制时, 此设置可能不会正常工作。", 87 | "speedrunapi.config.seedqueue.option.previewFPS.value": "%s fps", 88 | "speedrunapi.config.seedqueue.option.previewFPS.value.255": "无限制", 89 | "speedrunapi.config.seedqueue.option.preparingPreviews": "后台加载", 90 | "speedrunapi.config.seedqueue.option.preparingPreviews.description": "在开始渲染之前先在后台构建预览, 以便在将新预览加载到墙界面上时实现更平滑的过渡。", 91 | "speedrunapi.config.seedqueue.option.preparingPreviews.value.-1": "自动", 92 | "speedrunapi.config.seedqueue.option.freezeLockedPreviews": "冻结锁定实例", 93 | "speedrunapi.config.seedqueue.option.freezeLockedPreviews.description": "若启用,当一个实例被锁定后, 其预览不会接收新区块的渲染。", 94 | "speedrunapi.config.seedqueue.option.keyBindings": "按键设置", 95 | "speedrunapi.config.seedqueue.option.showAdvancedSettings": "显示高级选项", 96 | "speedrunapi.config.seedqueue.option.seedQueueThreadPriority": "SeedQueue 优先级", 97 | "speedrunapi.config.seedqueue.option.seedQueueThreadPriority.description": "设置创建种子队列的 SeedQueue 的线程优先级。", 98 | "speedrunapi.config.seedqueue.option.serverThreadPriority": "服务器优先级", 99 | "speedrunapi.config.seedqueue.option.serverThreadPriority.description": "设置在后台生成种子的线程优先级。", 100 | "speedrunapi.config.seedqueue.option.backgroundExecutorThreads": "后台工作线程", 101 | "speedrunapi.config.seedqueue.option.backgroundExecutorThreads.description": "设置玩家在世界中时在队列中生成种子的工作线程数。\n§6建议不要将此值设置为高于 CPU 的线程数量, 但不得低于“种子加载上限”选项。", 102 | "speedrunapi.config.seedqueue.option.backgroundExecutorThreads.value.0": "自动", 103 | "speedrunapi.config.seedqueue.option.backgroundExecutorThreadPriority": "后台工作优先级", 104 | "speedrunapi.config.seedqueue.option.backgroundExecutorThreadPriority.description": "设置 SeedQueue 后台工作线程的优先级", 105 | "speedrunapi.config.seedqueue.option.wallExecutorThreads": "墙界面工作线程", 106 | "speedrunapi.config.seedqueue.option.wallExecutorThreads.description": "设置在墙界面时队列中生成的种子的工作线程数。\n§6建议不要将此值设置为高于 CPU 的线程数量, 但不得低于“种子加载上限(墙界面)”选项。", 107 | "speedrunapi.config.seedqueue.option.wallExecutorThreads.value.0": "自动", 108 | "speedrunapi.config.seedqueue.option.wallExecutorThreadPriority": "墙界面工作优先级", 109 | "speedrunapi.config.seedqueue.option.wallExecutorThreadPriority.description": "设置墙界面工作线程的优先级", 110 | "speedrunapi.config.seedqueue.option.chunkUpdateThreads": "钠加载线程", 111 | "speedrunapi.config.seedqueue.option.chunkUpdateThreads.description": "设置墙界面每个预览中钠加载区块的线程数", 112 | "speedrunapi.config.seedqueue.option.chunkUpdateThreads.value.0": "自动", 113 | "speedrunapi.config.seedqueue.option.chunkUpdateThreadPriority": "钠加载优先级", 114 | "speedrunapi.config.seedqueue.option.chunkUpdateThreadPriority.description": "设置墙界面每个预览中钠加载区块的优先级", 115 | "speedrunapi.config.seedqueue.option.reduceLevelList": "减少世界列表", 116 | "speedrunapi.config.seedqueue.option.reduceLevelList.description": "从世界列表中隐藏在墙界面上被重置或在预览加载期间的世界, 以减少卡顿。", 117 | "speedrunapi.config.seedqueue.option.useWatchdog": "看门狗", 118 | "speedrunapi.config.seedqueue.option.useWatchdog.description": "启动一个额外的线程, 每10秒打印一次主线程堆栈跟踪, 以诊断冻结问题。", 119 | "speedrunapi.config.seedqueue.option.showDebugMenu": "显示调试菜单", 120 | "speedrunapi.config.seedqueue.option.showDebugMenu.description": "在墙界面上显示 FPS 图形和分析器。\n§c仅用于调试, 启用此选项时完成任何的记录都将无法过审!", 121 | "speedrunapi.config.seedqueue.option.benchmarkResets": "跑分重置", 122 | "speedrunapi.config.seedqueue.option.benchmarkResets.description": "启动跑分时执行的默认重置数量。\n您可以使用\"开始跑分\"键启动跑分, 并在达到设定的重置数量之前使用\"停止跑分\"键停止跑分。", 123 | "speedrunapi.config.seedqueue.option.showChunkMaps": "SeedQueue 区块加载", 124 | "speedrunapi.config.seedqueue.option.showChunkMaps.description": "显示当前正在加载的种子的区块加载进度" 125 | } 126 | -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/sounds.json: -------------------------------------------------------------------------------- 1 | { 2 | "play_instance": { 3 | "sounds": [ 4 | ] 5 | }, 6 | "lock_instance": { 7 | "sounds": [ 8 | "seedqueue:lock_instance" 9 | ] 10 | }, 11 | "reset_instance": { 12 | "sounds": [ 13 | "seedqueue:reset_instance" 14 | ] 15 | }, 16 | "reset_all": { 17 | "sounds": [ 18 | ] 19 | }, 20 | "reset_column": { 21 | "sounds": [ 22 | ] 23 | }, 24 | "reset_row": { 25 | "sounds": [ 26 | ] 27 | }, 28 | "schedule_join": { 29 | "sounds": [ 30 | ] 31 | }, 32 | "schedule_all": { 33 | "sounds": [ 34 | ] 35 | }, 36 | "scheduled_join_warning": { 37 | "sounds": [ 38 | ] 39 | }, 40 | "start_benchmark": { 41 | "sounds": [ 42 | ] 43 | }, 44 | "finish_benchmark": { 45 | "sounds": [ 46 | ] 47 | }, 48 | "open_wall": { 49 | "sounds": [ 50 | ] 51 | }, 52 | "bypass_wall": { 53 | "sounds": [ 54 | ] 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/sounds/lock_instance.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/src/main/resources/assets/seedqueue/sounds/lock_instance.ogg -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/sounds/reset_instance.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/src/main/resources/assets/seedqueue/sounds/reset_instance.ogg -------------------------------------------------------------------------------- /src/main/resources/assets/seedqueue/textures/gui/wall/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/src/main/resources/assets/seedqueue/textures/gui/wall/lock.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "seedqueue", 4 | "version": "${version}", 5 | "name": "SeedQueue", 6 | "description": "Generates worlds in the background for faster resetting.", 7 | "authors": [ 8 | { 9 | "name": "contaria", 10 | "contact": { 11 | "homepage": "https://github.com/KingContaria/" 12 | } 13 | } 14 | ], 15 | "contributors": [ 16 | "tildejustin", 17 | "Crystal", 18 | "tesselslate", 19 | "sathya", 20 | "nealxm", 21 | "DuncanRuns", 22 | "DesktopFolder", 23 | "luvvlyjude" 24 | ], 25 | "contact": { 26 | "sources": "https://github.com/KingContaria/seedqueue", 27 | "issues": "https://github.com/KingContaria/seedqueue/issues" 28 | }, 29 | "license": "MIT", 30 | "icon": "assets/seedqueue/icon.png", 31 | "environment": "client", 32 | "entrypoints": { 33 | "client": [ 34 | "me.contaria.seedqueue.SeedQueue" 35 | ] 36 | }, 37 | "custom": { 38 | "speedrunapi": { 39 | "config": "me.contaria.seedqueue.SeedQueueConfig" 40 | } 41 | }, 42 | "mixins": [ 43 | "seedqueue.mixins.json" 44 | ], 45 | "depends": { 46 | "fabricloader": ">=0.16.0", 47 | "minecraft": "1.16.1", 48 | "speedrunapi": ">=2.0-beta.1", 49 | "atum": ">=2.3" 50 | }, 51 | "breaks": { 52 | "worldpreview": "<6.3.0", 53 | "antiresourcereload": "<5.0.0", 54 | "chunkcacher": "<1.4.0", 55 | "standardsettings": "<2.0", 56 | "setspawnmod": "<4.0.0", 57 | "fast_reset": "<2.0", 58 | "state-output": "<1.2", 59 | "speedrunigt": "<15.0", 60 | "sodium": [ "v1", "v2", "<2.4.0" ], 61 | "sodiummac": [ "v2", "v3", "<3.4.0" ], 62 | "sleepbackground": "<4.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/seedqueue.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "me.contaria.seedqueue.mixin", 4 | "compatibilityLevel": "JAVA_8", 5 | "injectors": { 6 | "defaultRequire": 1 7 | }, 8 | "client": [ 9 | "accessor.CameraAccessor", 10 | "accessor.DebugHudAccessor", 11 | "accessor.EntityAccessor", 12 | "accessor.MinecraftClientAccessor", 13 | "accessor.MinecraftServerAccessor", 14 | "accessor.PlayerEntityAccessor", 15 | "accessor.UtilAccessor", 16 | "accessor.WorldGenerationProgressTrackerAccessor", 17 | "accessor.WorldRendererAccessor", 18 | "client.CreateWorldScreenMixin", 19 | "client.MinecraftClientMixin", 20 | "client.WorldGenerationProgressLoggerMixin", 21 | "client.WorldGenerationProgressTrackerMixin", 22 | "client.debug.DebugHudMixin", 23 | "client.levellist.LevelStorageMixin", 24 | "client.profiling.WorldRendererMixin", 25 | "client.render.LevelLoadingScreenMixin", 26 | "client.render.MinecraftClientMixin", 27 | "client.sounds.SoundManagerMixin", 28 | "client.sounds.SoundSystemMixin", 29 | "compat.atum.AttemptTrackerMixin", 30 | "compat.atum.AtumMixin", 31 | "compat.atum.CreateWorldScreenMixin", 32 | "compat.atum.KeyboardMixin", 33 | "compat.sodium.ChunkBuilder$WorkerRunnableMixin", 34 | "compat.sodium.ChunkBuilder$WorkerRunnableMixin2", 35 | "compat.sodium.ChunkBuilderMixin", 36 | "compat.sodium.ChunkRenderManagerMixin", 37 | "compat.sodium.ChunkRenderShaderBackendMixin", 38 | "compat.sodium.profiling.ChunkBuilderMixin", 39 | "compat.sodium.profiling.ChunkRenderCacheLocalMixin", 40 | "compat.sodium.profiling.ChunkRenderManagerMixin", 41 | "compat.sodium.profiling.MultidrawChunkRenderBackendMixin", 42 | "compat.sodium.profiling.SodiumWorldRendererMixin", 43 | "compat.standardsettings.MinecraftClientMixin", 44 | "compat.worldpreview.ThreadedAnvilChunkStorageMixin", 45 | "compat.worldpreview.WorldPreviewMixin", 46 | "compat.worldpreview.render.ClientWorldMixin", 47 | "compat.worldpreview.render.InGameHudMixin", 48 | "compat.worldpreview.render.WindowMixin", 49 | "compat.worldpreview.render.WorldRendererMixin", 50 | "server.IntegratedServerMixin", 51 | "server.MinecraftServerMixin", 52 | "server.optimization.ThreadedAnvilChunkStorageMixin", 53 | "server.parity.EntityMixin", 54 | "server.synchronization.BiomeMixin", 55 | "server.synchronization.DirectionTransformationMixin", 56 | "server.synchronization.ScheduledTickMixin", 57 | "server.synchronization.WeightedBlockStateProviderMixin" 58 | ], 59 | "plugin": "me.contaria.seedqueue.SeedQueueMixinConfigPlugin" 60 | } --------------------------------------------------------------------------------