├── .gitignore ├── Jenkinsfile ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src └── main ├── java └── se │ └── mickelus │ └── mutil │ ├── ConfigHandler.java │ ├── MUtilMod.java │ ├── Perks.java │ ├── data │ ├── AbstractUpdateDataPacket.java │ ├── DataDistributor.java │ ├── DataStore.java │ ├── MergingDataStore.java │ └── deserializer │ │ ├── BlockDeserializer.java │ │ ├── BlockPosDeserializer.java │ │ ├── ItemDeserializer.java │ │ └── ResourceLocationDeserializer.java │ ├── effect │ └── EffectTooltipRenderer.java │ ├── gui │ ├── ClipRectGui.java │ ├── DisabledSlot.java │ ├── GuiAlignment.java │ ├── GuiAttachment.java │ ├── GuiButton.java │ ├── GuiClickable.java │ ├── GuiElement.java │ ├── GuiItem.java │ ├── GuiRect.java │ ├── GuiRoot.java │ ├── GuiString.java │ ├── GuiStringOutline.java │ ├── GuiStringSmall.java │ ├── GuiText.java │ ├── GuiTextSmall.java │ ├── GuiTexture.java │ ├── GuiTextureOffset.java │ ├── ScrollBarGui.java │ ├── ToggleableSlot.java │ ├── animation │ │ ├── AnimationChain.java │ │ ├── Applier.java │ │ ├── GuiAnimation.java │ │ ├── KeyframeAnimation.java │ │ └── VisibilityFilter.java │ ├── hud │ │ └── GuiRootHud.java │ └── impl │ │ ├── GuiColors.java │ │ ├── GuiHorizontalLayoutGroup.java │ │ ├── GuiHorizontalScrollable.java │ │ └── GuiVerticalLayoutGroup.java │ ├── network │ ├── AbstractPacket.java │ ├── BlockPosPacket.java │ └── PacketHandler.java │ ├── scheduling │ ├── AbstractScheduler.java │ ├── ClientScheduler.java │ └── ServerScheduler.java │ └── util │ ├── CastOptional.java │ ├── Filter.java │ ├── HexCodec.java │ ├── InventoryStream.java │ ├── ItemHandlerStream.java │ ├── ItemHandlerWrapper.java │ ├── JsonOptional.java │ ├── ParticleHelper.java │ ├── RotationHelper.java │ └── TileEntityOptional.java └── resources ├── META-INF └── mods.toml └── pack.mcmeta /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | logs/ 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | agent any 5 | tools { 6 | jdk "jdk-17.0.1" 7 | } 8 | stages { 9 | stage('Clean') { 10 | steps { 11 | echo 'Cleaning Project' 12 | sh 'chmod +x gradlew' 13 | sh './gradlew clean' 14 | } 15 | } 16 | stage('Build') { 17 | steps { 18 | echo 'Building' 19 | sh './gradlew build' 20 | } 21 | } 22 | stage('Publish') { 23 | steps { 24 | echo 'Deploying to Maven' 25 | sh './gradlew publish' 26 | } 27 | } 28 | } 29 | post { 30 | always { 31 | archive 'build/libs/**.jar' 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mikael Eriksson Vikner 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 | # Mutil 2 | Formerly mgui, contains helpers for data management, networking and gui setup. 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url = 'https://files.minecraftforge.net/maven' } 4 | jcenter() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true 9 | } 10 | } 11 | 12 | //repositories { 13 | // maven { 14 | // url 'https://www.dogforce-games.com/maven/' 15 | // } 16 | //} 17 | 18 | 19 | apply plugin: 'net.minecraftforge.gradle' 20 | apply plugin: 'eclipse' 21 | apply plugin: 'maven-publish' 22 | 23 | if (System.getenv('VERSION') != null) { 24 | mutil_version = System.getenv('VERSION') 25 | } else { 26 | def stdout = new ByteArrayOutputStream() 27 | exec { 28 | commandLine 'git', 'describe', '--tags', '--abbrev=0' 29 | standardOutput = stdout 30 | } 31 | mutil_version = stdout.toString().replace("\n", "").replace("\r", "").trim() 32 | } 33 | 34 | version = "${mc_version}-${mutil_version}" 35 | group = 'se.mickelus.mutil' 36 | archivesBaseName = "mutil" 37 | 38 | java.toolchain.languageVersion = JavaLanguageVersion.of(java_version) 39 | println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) 40 | 41 | minecraft { 42 | mappings channel: 'official', version: mc_version 43 | // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') 44 | 45 | runs { 46 | client { 47 | workingDirectory project.file('run') 48 | property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' 49 | property 'forge.logging.console.level', 'debug' 50 | mods { 51 | mutil { 52 | source sourceSets.main 53 | } 54 | } 55 | } 56 | 57 | server { 58 | workingDirectory project.file('run') 59 | property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' 60 | property 'forge.logging.console.level', 'debug' 61 | mods { 62 | mutil { 63 | source sourceSets.main 64 | } 65 | } 66 | } 67 | 68 | data { 69 | workingDirectory project.file('run') 70 | property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' 71 | property 'forge.logging.console.level', 'debug' 72 | args '--mod', 'mutil', '--all', '--output', file('src/generated/resources/') 73 | mods { 74 | mutil { 75 | source sourceSets.main 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | dependencies { 83 | minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}" 84 | } 85 | 86 | jar { 87 | manifest { 88 | attributes([ 89 | "Specification-Title" : "mutil", 90 | "Specification-Vendor" : "mickelus", 91 | "Specification-Version" : "1", // We are version 1 of ourselves 92 | "Implementation-Title" : project.name, 93 | "Implementation-Version" : "${mutil_version}", 94 | "Implementation-Vendor" : "mutil", 95 | "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") 96 | ]) 97 | } 98 | } 99 | 100 | jar.finalizedBy('reobfJar') 101 | 102 | publishing { 103 | publications { 104 | mavenJava(MavenPublication) { 105 | groupId project.group 106 | artifactId project.archivesBaseName 107 | version project.version 108 | artifact jar 109 | 110 | pom.withXml { 111 | 112 | // Go through all the dependencies. 113 | asNode().dependencies.dependency.each { dep -> 114 | 115 | println 'Surpressing artifact ' + dep.artifactId.last().value().last() + ' from maven dependencies.' 116 | assert dep.parent().remove(dep) 117 | } 118 | } 119 | } 120 | } 121 | repositories { 122 | maven { 123 | url = uri("file://${System.getenv("local_maven")}") 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | # This is required to provide enough memory for the Minecraft decompilation process. 3 | org.gradle.jvmargs=-Xmx3G 4 | org.gradle.daemon=false 5 | mc_version=1.19.2 6 | java_version=17 7 | forge_version=43.1.1 8 | mutil_version=5.1.0 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickelus/mutil/6586b1bad59074cffe23db2294ea136346b3009d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/ConfigHandler.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil; 2 | 3 | 4 | import net.minecraftforge.api.distmarker.Dist; 5 | import net.minecraftforge.api.distmarker.OnlyIn; 6 | import net.minecraftforge.common.ForgeConfigSpec; 7 | import net.minecraftforge.fml.ModLoadingContext; 8 | import net.minecraftforge.fml.common.Mod; 9 | import net.minecraftforge.fml.config.ModConfig; 10 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 11 | import net.minecraftforge.fml.loading.FMLEnvironment; 12 | import org.apache.commons.lang3.tuple.Pair; 13 | 14 | import javax.annotation.ParametersAreNonnullByDefault; 15 | 16 | @ParametersAreNonnullByDefault 17 | @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) 18 | class ConfigHandler { 19 | public static Client client; 20 | static ForgeConfigSpec clientSpec; 21 | 22 | public static void setup() { 23 | if (FMLEnvironment.dist.isClient()) { 24 | setupClient(); 25 | ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, clientSpec); 26 | FMLJavaModLoadingContext.get().getModEventBus().register(ConfigHandler.client); 27 | } 28 | } 29 | 30 | @OnlyIn(Dist.CLIENT) 31 | private static void setupClient() { 32 | final Pair specPair = new ForgeConfigSpec.Builder().configure(Client::new); 33 | clientSpec = specPair.getRight(); 34 | client = specPair.getLeft(); 35 | } 36 | 37 | @OnlyIn(Dist.CLIENT) 38 | public static class Client { 39 | public ForgeConfigSpec.BooleanValue queryPerks; 40 | 41 | Client(ForgeConfigSpec.Builder builder) { 42 | queryPerks = builder 43 | .comment("Controls if perks data should be queried on startup") 44 | .define("query_perks", true); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/MUtilMod.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraftforge.eventbus.api.SubscribeEvent; 5 | import net.minecraftforge.fml.common.Mod; 6 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 7 | 8 | @Mod(MUtilMod.MOD_ID) 9 | @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) 10 | public class MUtilMod { 11 | public static final String MOD_ID = "mutil"; 12 | 13 | public MUtilMod() { 14 | ConfigHandler.setup(); 15 | } 16 | 17 | @SubscribeEvent 18 | public static void clientSetup(FMLClientSetupEvent event) { 19 | Perks.init(Minecraft.getInstance().getUser().getUuid()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/Perks.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpResponse; 13 | import java.util.concurrent.ExecutionException; 14 | 15 | public class Perks { 16 | private static final Logger logger = LogManager.getLogger(); 17 | private static volatile Data data; 18 | 19 | public static void init(String uuid) { 20 | if (!ConfigHandler.client.queryPerks.get()) { 21 | logger.info("Perks query disabled, skipping fetch!"); 22 | data = new Data(); 23 | return; 24 | } 25 | try { 26 | Gson gson = new GsonBuilder().create(); 27 | HttpRequest request = HttpRequest.newBuilder(new URI("https://mickelus.se/util/perks/" + uuid.replace("-", ""))) 28 | .header("Accept", "application/json") 29 | .build(); 30 | HttpClient.newHttpClient() 31 | .sendAsync(request, HttpResponse.BodyHandlers.ofString()) 32 | .thenApply(HttpResponse::body) 33 | .thenApply(body -> gson.fromJson(body, Data.class)) 34 | .thenAccept(Perks::setData) 35 | .get(); 36 | } catch (URISyntaxException | ExecutionException | InterruptedException e) { 37 | logger.warn("Failed to get perk data: " + e.getMessage()); 38 | data = new Data(); 39 | } 40 | } 41 | 42 | public static synchronized Data getData() { 43 | return data; 44 | } 45 | 46 | private static synchronized void setData(Data newData) { 47 | data = newData; 48 | } 49 | 50 | public static class Data { 51 | public int support; 52 | public int contribute; 53 | public int community; 54 | public int moderate; 55 | 56 | @Override 57 | public String toString() { 58 | return "PerkData{" + 59 | "support=" + support + 60 | ", contribute=" + contribute + 61 | ", community=" + community + 62 | ", moderate=" + moderate + 63 | '}'; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/AbstractUpdateDataPacket.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data; 2 | 3 | import com.google.gson.JsonElement; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.entity.player.Player; 7 | import se.mickelus.mutil.network.AbstractPacket; 8 | 9 | import javax.annotation.ParametersAreNonnullByDefault; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | @ParametersAreNonnullByDefault 15 | public abstract class AbstractUpdateDataPacket extends AbstractPacket { 16 | protected String directory; 17 | protected Map data; 18 | 19 | public AbstractUpdateDataPacket() {} 20 | 21 | public AbstractUpdateDataPacket(String directory, Map data) { 22 | this.directory = directory; 23 | this.data = data.entrySet().stream() 24 | .collect(Collectors.toMap( 25 | Map.Entry::getKey, 26 | entry -> entry.getValue().toString() 27 | )); 28 | } 29 | 30 | @Override 31 | public void toBytes(FriendlyByteBuf buffer) { 32 | buffer.writeUtf(directory); 33 | buffer.writeInt(data.size()); 34 | data.forEach((resourceLocation, data) -> { 35 | buffer.writeResourceLocation(resourceLocation); 36 | buffer.writeUtf(data); 37 | }); 38 | } 39 | 40 | @Override 41 | public void fromBytes(FriendlyByteBuf buffer) { 42 | directory = buffer.readUtf(); 43 | int count = buffer.readInt(); 44 | data = new HashMap<>(); 45 | for (int i = 0; i < count; i++) { 46 | data.put(buffer.readResourceLocation(), buffer.readUtf()); 47 | } 48 | } 49 | 50 | @Override 51 | public abstract void handle(Player player); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/DataDistributor.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data; 2 | 3 | import com.google.gson.JsonElement; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.server.level.ServerPlayer; 6 | 7 | import java.util.Map; 8 | 9 | public interface DataDistributor { 10 | public void sendToAll(String directory, Map dataMap); 11 | public void sendToPlayer(ServerPlayer player, String directory, Map dataMap); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/DataStore.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.google.gson.*; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.server.packs.resources.Resource; 8 | import net.minecraft.server.packs.resources.ResourceManager; 9 | import net.minecraft.server.packs.resources.SimplePreparableReloadListener; 10 | import net.minecraft.util.GsonHelper; 11 | import net.minecraft.util.profiling.ProfilerFiller; 12 | import net.minecraftforge.common.crafting.CraftingHelper; 13 | import net.minecraftforge.common.crafting.conditions.ICondition; 14 | import net.minecraftforge.fml.ModList; 15 | import net.minecraftforge.forgespi.Environment; 16 | import net.minecraftforge.forgespi.language.IModInfo; 17 | import net.minecraftforge.server.ServerLifecycleHooks; 18 | import org.apache.logging.log4j.LogManager; 19 | import org.apache.logging.log4j.Logger; 20 | 21 | import javax.annotation.ParametersAreNonnullByDefault; 22 | import java.io.IOException; 23 | import java.io.Reader; 24 | import java.util.*; 25 | import java.util.stream.Collectors; 26 | 27 | @ParametersAreNonnullByDefault 28 | public class DataStore extends SimplePreparableReloadListener> { 29 | protected static final int jsonExtLength = ".json".length(); 30 | private static final Logger logger = LogManager.getLogger(); 31 | protected Gson gson; 32 | protected String namespace; 33 | protected String directory; 34 | protected Class dataClass; 35 | protected Map rawData; 36 | protected Map dataMap; 37 | protected List listeners; 38 | private DataDistributor syncronizer; 39 | 40 | public DataStore(Gson gson, String namespace, String directory, Class dataClass, DataDistributor synchronizer) { 41 | this.gson = gson; 42 | this.namespace = namespace; 43 | this.directory = directory; 44 | 45 | this.dataClass = dataClass; 46 | this.syncronizer = synchronizer; 47 | 48 | rawData = Collections.emptyMap(); 49 | dataMap = Collections.emptyMap(); 50 | 51 | listeners = new LinkedList<>(); 52 | } 53 | 54 | protected Map prepare(ResourceManager resourceManager, ProfilerFiller profiler) { 55 | logger.debug("Reading data for {} data store...", directory); 56 | Map map = Maps.newHashMap(); 57 | int i = this.directory.length() + 1; 58 | 59 | for (Map.Entry entry : resourceManager.listResources(directory, rl -> rl.getPath().endsWith(".json")).entrySet()) { 60 | if (!namespace.equals(entry.getKey().getNamespace())) { 61 | continue; 62 | } 63 | 64 | String path = entry.getKey().getPath(); 65 | ResourceLocation location = new ResourceLocation(entry.getKey().getNamespace(), path.substring(i, path.length() - jsonExtLength)); 66 | 67 | try (Reader reader = entry.getValue().openAsReader()) { 68 | JsonElement json; 69 | 70 | if (dataClass.isArray()) { 71 | JsonArray sources = getSources(entry.getValue()); 72 | json = GsonHelper.fromJson(gson, reader, JsonArray.class); 73 | json.getAsJsonArray().forEach(element -> { 74 | if (element.isJsonObject()) { 75 | element.getAsJsonObject().add("sources", sources); 76 | } 77 | }); 78 | } else { 79 | json = GsonHelper.fromJson(gson, reader, JsonElement.class); 80 | json.getAsJsonObject().add("sources", getSources(entry.getValue())); 81 | } 82 | 83 | if (json != null) { 84 | if (shouldLoad(json)) { 85 | JsonElement duplicate = map.put(location, json); 86 | if (duplicate != null) { 87 | throw new IllegalStateException("Duplicate data ignored with ID " + location); 88 | } 89 | } else { 90 | logger.debug("Skipping data '{}' due to condition", entry.getKey()); 91 | } 92 | } else { 93 | logger.error("Couldn't load data from '{}' as it's null or empty", entry.getKey()); 94 | } 95 | } catch (IllegalArgumentException | IOException | JsonParseException exception) { 96 | logger.error("Couldn't parse data '{}' from '{}'", location, entry.getKey(), exception); 97 | } 98 | } 99 | 100 | return map; 101 | } 102 | 103 | protected JsonArray getSources(Resource resource) { 104 | String fileId = resource.sourcePackId(); 105 | JsonArray result = new JsonArray(); 106 | 107 | ModList.get().getModFiles().stream() 108 | .filter(modInfo -> fileId.equals(modInfo.getFile().getFileName())) 109 | .flatMap(fileInfo -> fileInfo.getMods().stream()) 110 | .map(IModInfo::getDisplayName) 111 | .forEach(result::add); 112 | 113 | if (result.size() == 0) { 114 | result.add(fileId); 115 | } 116 | 117 | return result; 118 | } 119 | 120 | @Override 121 | protected void apply(Map splashList, ResourceManager resourceManager, ProfilerFiller profiler) { 122 | rawData = splashList; 123 | 124 | // PacketHandler dependencies get upset when called upon before the server has started properly 125 | if (Environment.get().getDist().isDedicatedServer() && ServerLifecycleHooks.getCurrentServer() != null) { 126 | syncronizer.sendToAll(directory, rawData); 127 | } 128 | 129 | parseData(rawData); 130 | } 131 | 132 | public void sendToPlayer(ServerPlayer player) { 133 | syncronizer.sendToPlayer(player, directory, rawData); 134 | } 135 | 136 | public void loadFromPacket(Map data) { 137 | Map splashList = data.entrySet().stream() 138 | .collect(Collectors.toMap( 139 | Map.Entry::getKey, 140 | entry -> { 141 | if (dataClass.isArray()) { 142 | return GsonHelper.fromJson(gson, entry.getValue(), JsonArray.class); 143 | } else { 144 | return GsonHelper.fromJson(gson, entry.getValue(), JsonElement.class); 145 | } 146 | } 147 | )); 148 | 149 | parseData(splashList); 150 | } 151 | 152 | public void parseData(Map splashList) { 153 | logger.info("Loaded {} {}", String.format("%3d", splashList.values().size()), directory); 154 | dataMap = splashList.entrySet().stream() 155 | .collect(Collectors.toMap( 156 | Map.Entry::getKey, 157 | entry -> gson.fromJson(entry.getValue(), dataClass) 158 | )); 159 | 160 | processData(); 161 | 162 | listeners.forEach(Runnable::run); 163 | } 164 | 165 | protected boolean shouldLoad(JsonElement json) { 166 | if (json.isJsonArray()) { 167 | JsonArray arr = json.getAsJsonArray(); 168 | if (arr.size() > 0) 169 | json = arr.get(0); 170 | } 171 | 172 | if (!json.isJsonObject()) { 173 | return true; 174 | } 175 | 176 | JsonObject jsonObject = json.getAsJsonObject(); 177 | return !jsonObject.has("conditions") 178 | || CraftingHelper.processConditions(GsonHelper.getAsJsonArray(jsonObject, "conditions"), ICondition.IContext.EMPTY); 179 | } 180 | 181 | protected void processData() { 182 | 183 | } 184 | 185 | public Map getRawData() { 186 | return rawData; 187 | } 188 | 189 | public String getDirectory() { 190 | return directory; 191 | } 192 | 193 | /** 194 | * Get the resource at the given location from the set of resources that this listener is managing 195 | * 196 | * @param resourceLocation A resource location 197 | * @return An object matching the type of this listener, or null if none exists at the given location 198 | */ 199 | public V getData(ResourceLocation resourceLocation) { 200 | return dataMap.get(resourceLocation); 201 | } 202 | 203 | /** 204 | * @return all data from this store. 205 | */ 206 | public Map getData() { 207 | return dataMap; 208 | } 209 | 210 | /** 211 | * Get all resources (if any) that are within the directory denoted by the provided resource location 212 | * 213 | * @param resourceLocation 214 | * @return 215 | */ 216 | public Collection getDataIn(ResourceLocation resourceLocation) { 217 | return getData().entrySet().stream() 218 | .filter(entry -> resourceLocation.getNamespace().equals(entry.getKey().getNamespace()) 219 | && entry.getKey().getPath().startsWith(resourceLocation.getPath())) 220 | .map(Map.Entry::getValue) 221 | .collect(Collectors.toList()); 222 | } 223 | 224 | /** 225 | * Listen to changes on resources in this store 226 | * 227 | * @param callback A runnable that is to be called when the store is reloaded 228 | */ 229 | public void onReload(Runnable callback) { 230 | listeners.add(callback); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/MergingDataStore.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.google.gson.Gson; 5 | import com.google.gson.JsonArray; 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonObject; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.server.packs.resources.Resource; 10 | import net.minecraft.server.packs.resources.ResourceManager; 11 | import net.minecraft.util.GsonHelper; 12 | import net.minecraft.util.profiling.ProfilerFiller; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | 16 | import java.io.IOException; 17 | import java.io.Reader; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | public abstract class MergingDataStore extends DataStore { 23 | private static final Logger logger = LogManager.getLogger(); 24 | 25 | protected Class arrayClass; 26 | 27 | public MergingDataStore(Gson gson, String namespace, String directory, Class entryClass, Class arrayClass, DataDistributor synchronizer) { 28 | super(gson, namespace, directory, entryClass, synchronizer); 29 | 30 | this.arrayClass = arrayClass; 31 | } 32 | 33 | @Override 34 | protected Map prepare(ResourceManager resourceManager, ProfilerFiller profiler) { 35 | logger.debug("Reading data for {} data store...", directory); 36 | Map map = Maps.newHashMap(); 37 | int i = this.directory.length() + 1; 38 | 39 | for (Map.Entry> entry : resourceManager.listResourceStacks(directory, rl -> rl.getPath().endsWith(".json")).entrySet()) { 40 | if (!namespace.equals(entry.getKey().getNamespace())) { 41 | continue; 42 | } 43 | 44 | String path = entry.getKey().getPath(); 45 | ResourceLocation location = new ResourceLocation(entry.getKey().getNamespace(), path.substring(i, path.length() - jsonExtLength)); 46 | 47 | JsonArray allResources = new JsonArray(); 48 | 49 | for (Resource resource : entry.getValue()) { 50 | try (Reader reader = resource.openAsReader()) { 51 | JsonObject json = GsonHelper.fromJson(gson, reader, JsonObject.class); 52 | json.add("sources", getSources(resource)); 53 | 54 | if (json != null) { 55 | if (shouldLoad(json)) { 56 | allResources.add(json); 57 | } else { 58 | logger.debug("Skipping data '{}' from '{}' due to condition", entry.getKey(), resource.sourcePackId()); 59 | } 60 | } else { 61 | logger.error("Couldn't load data from '{}' in data pack '{}' as it's empty or null", 62 | entry.getKey(), resource.sourcePackId()); 63 | } 64 | } catch (RuntimeException | IOException e) { 65 | logger.error("Couldn't load data from '{}' in data pack '{}'", entry.getKey(), resource.sourcePackId(), e); 66 | } 67 | } 68 | 69 | if (allResources.size() > 0) { 70 | map.put(location, allResources); 71 | } 72 | } 73 | 74 | return map; 75 | } 76 | 77 | @Override 78 | public void loadFromPacket(Map data) { 79 | Map splashList = data.entrySet().stream() 80 | .collect(Collectors.toMap( 81 | Map.Entry::getKey, 82 | entry -> GsonHelper.fromJson(gson, entry.getValue(), JsonArray.class) 83 | )); 84 | 85 | parseData(splashList); 86 | } 87 | 88 | public void parseData(Map splashList) { 89 | logger.info("Loaded {} {}", String.format("%3d", splashList.values().size()), directory); 90 | dataMap = splashList.entrySet().stream() 91 | .collect(Collectors.toMap( 92 | Map.Entry::getKey, 93 | entry -> mergeData(gson.fromJson(entry.getValue(), arrayClass)) 94 | )); 95 | 96 | processData(); 97 | 98 | listeners.forEach(Runnable::run); 99 | } 100 | 101 | protected abstract V mergeData(U collection); 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/deserializer/BlockDeserializer.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data.deserializer; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.level.block.Block; 9 | import net.minecraftforge.registries.ForgeRegistries; 10 | 11 | import javax.annotation.ParametersAreNonnullByDefault; 12 | import java.lang.reflect.Type; 13 | 14 | @ParametersAreNonnullByDefault 15 | public class BlockDeserializer implements JsonDeserializer { 16 | @Override 17 | public Block deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 18 | String string = json.getAsString(); 19 | if (string != null) { 20 | ResourceLocation resourceLocation = new ResourceLocation(string); 21 | if (ForgeRegistries.BLOCKS.containsKey(resourceLocation)) { 22 | return ForgeRegistries.BLOCKS.getValue(resourceLocation); 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/deserializer/BlockPosDeserializer.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data.deserializer; 2 | 3 | import com.google.gson.*; 4 | import net.minecraft.core.BlockPos; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | import java.lang.reflect.Type; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class BlockPosDeserializer implements JsonDeserializer { 11 | @Override 12 | public BlockPos deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 13 | JsonArray array = json.getAsJsonArray(); 14 | return new BlockPos(array.get(0).getAsInt(), array.get(1).getAsInt(), array.get(2).getAsInt()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/deserializer/ItemDeserializer.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data.deserializer; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.item.Item; 9 | import net.minecraftforge.registries.ForgeRegistries; 10 | 11 | import javax.annotation.ParametersAreNonnullByDefault; 12 | import java.lang.reflect.Type; 13 | 14 | @ParametersAreNonnullByDefault 15 | public class ItemDeserializer implements JsonDeserializer { 16 | @Override 17 | public Item deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 18 | String string = json.getAsString(); 19 | if (string != null) { 20 | ResourceLocation resourceLocation = new ResourceLocation(string); 21 | if (ForgeRegistries.ITEMS.containsKey(resourceLocation)) { 22 | return ForgeRegistries.ITEMS.getValue(resourceLocation); 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/data/deserializer/ResourceLocationDeserializer.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.data.deserializer; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import net.minecraft.resources.ResourceLocation; 8 | 9 | import javax.annotation.ParametersAreNonnullByDefault; 10 | import java.lang.reflect.Type; 11 | 12 | @ParametersAreNonnullByDefault 13 | public class ResourceLocationDeserializer implements JsonDeserializer { 14 | 15 | public static ResourceLocation deserialize(JsonElement json) throws JsonParseException { 16 | return new ResourceLocation(json.getAsString()); 17 | } 18 | 19 | @Override 20 | public ResourceLocation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 21 | return deserialize(json); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/effect/EffectTooltipRenderer.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.effect; 2 | 3 | import com.mojang.blaze3d.platform.Window; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.screens.inventory.EffectRenderingInventoryScreen; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.world.effect.MobEffectInstance; 9 | import net.minecraftforge.api.distmarker.Dist; 10 | import net.minecraftforge.api.distmarker.OnlyIn; 11 | import net.minecraftforge.client.extensions.common.IClientMobEffectExtensions; 12 | 13 | import java.util.function.Function; 14 | import java.util.function.Supplier; 15 | 16 | public class EffectTooltipRenderer implements IClientMobEffectExtensions { 17 | private final Function constructEffectTooltip; 18 | 19 | public EffectTooltipRenderer(Function constructEffectTooltip) { 20 | this.constructEffectTooltip = constructEffectTooltip; 21 | } 22 | 23 | @OnlyIn(Dist.CLIENT) 24 | public static void renderInventoryEffectTooltip(EffectRenderingInventoryScreen screen, PoseStack poseStack, int x, int y, Supplier tooltip) { 25 | Minecraft mc = Minecraft.getInstance(); 26 | Window window = mc.getWindow(); 27 | 28 | int width = window.getGuiScaledWidth(); 29 | int height = window.getGuiScaledHeight(); 30 | int mouseX = (int) (mc.mouseHandler.xpos() * width / window.getScreenWidth()); 31 | int mouseY = (int) (mc.mouseHandler.ypos() * height / window.getScreenHeight()); 32 | 33 | if (x < mouseX && mouseX < x + 120 && y < mouseY && mouseY < y + 32) { 34 | screen.renderTooltip(poseStack, tooltip.get(), mouseX, mouseY); 35 | } 36 | } 37 | 38 | @Override 39 | public boolean renderInventoryIcon(MobEffectInstance instance, EffectRenderingInventoryScreen screen, PoseStack poseStack, int x, int y, int blitOffset) { 40 | renderInventoryEffectTooltip(screen, poseStack, x, y, () -> Component.literal(constructEffectTooltip.apply(instance))); 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/ClipRectGui.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | 6 | public class ClipRectGui extends GuiElement { 7 | public ClipRectGui(int x, int y, int width, int height) { 8 | super(x, y, width, height); 9 | } 10 | 11 | @Override 12 | protected void drawChildren(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 13 | matrixStack.pushPose(); 14 | matrixStack.translate(0.0D, 0.0D, 950.0D); 15 | RenderSystem.enableDepthTest(); 16 | RenderSystem.enableBlend(); 17 | RenderSystem.colorMask(false, false, false, false); 18 | fill(matrixStack, 4680, 2260, -4680, -2260, 0x01ffffff); 19 | RenderSystem.colorMask(true, true, true, true); 20 | matrixStack.translate(0.0D, 0.0D, -950.0D); 21 | RenderSystem.depthFunc(518); 22 | fill(matrixStack, refX + width, refY + height, refX, refY, 0x01ffffff); 23 | RenderSystem.depthFunc(515); 24 | 25 | super.drawChildren(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 26 | 27 | RenderSystem.depthFunc(518); 28 | matrixStack.translate(0.0D, 0.0D, -950.0D); 29 | RenderSystem.colorMask(false, false, false, false); 30 | fill(matrixStack, 4680, 2260, -4680, -2260, 0x01ffffff); 31 | RenderSystem.colorMask(true, true, true, true); 32 | RenderSystem.depthFunc(515); 33 | matrixStack.popPose(); 34 | RenderSystem.applyModelViewMatrix(); 35 | RenderSystem.disableDepthTest(); 36 | } 37 | 38 | @Override 39 | public void updateFocusState(int refX, int refY, int mouseX, int mouseY) { 40 | boolean gainFocus = mouseX >= getX() + refX 41 | && mouseX < getX() + refX + getWidth() 42 | && mouseY >= getY() + refY 43 | && mouseY < getY() + refY + getHeight(); 44 | 45 | if (gainFocus != hasFocus) { 46 | hasFocus = gainFocus; 47 | if (hasFocus) { 48 | onFocus(); 49 | } else { 50 | onBlur(); 51 | } 52 | } 53 | 54 | if (hasFocus) { 55 | elements.stream() 56 | .filter(GuiElement::isVisible) 57 | .forEach(element -> element.updateFocusState( 58 | refX + x + getXOffset(this, element.attachmentAnchor) - getXOffset(element, element.attachmentPoint), 59 | refY + y + getYOffset(this, element.attachmentAnchor) - getYOffset(element, element.attachmentPoint), 60 | mouseX, mouseY)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/DisabledSlot.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import net.minecraft.world.entity.player.Player; 4 | import net.minecraft.world.Container; 5 | import net.minecraft.world.inventory.Slot; 6 | import net.minecraft.world.item.ItemStack; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | public class DisabledSlot extends Slot { 11 | public DisabledSlot(Container inventoryIn, int index, int xPosition, int yPosition) { 12 | super(inventoryIn, index, xPosition, yPosition); 13 | } 14 | 15 | @Override 16 | public boolean mayPickup(Player playerIn) { 17 | return false; 18 | } 19 | 20 | @Override 21 | public boolean mayPlace(@Nullable ItemStack stack) { 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiAlignment.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | public enum GuiAlignment { 4 | left, 5 | center, 6 | right; 7 | 8 | public GuiAlignment flip() { 9 | if (this == left) { 10 | return right; 11 | } else if (this == right) { 12 | return left; 13 | } 14 | return center; 15 | } 16 | 17 | public GuiAttachment toAttachment() { 18 | if (this == left) { 19 | return GuiAttachment.topLeft; 20 | } else if (this == right) { 21 | return GuiAttachment.topRight; 22 | } 23 | return GuiAttachment.topCenter; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiAttachment.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | public enum GuiAttachment { 4 | topLeft, 5 | topCenter, 6 | topRight, 7 | middleLeft, 8 | middleCenter, 9 | middleRight, 10 | bottomLeft, 11 | bottomCenter, 12 | bottomRight; 13 | 14 | public GuiAttachment flipHorizontal() { 15 | switch(this) { 16 | case topLeft: 17 | return topRight; 18 | case topRight: 19 | return topLeft; 20 | case middleLeft: 21 | return middleRight; 22 | case middleRight: 23 | return middleLeft; 24 | case bottomLeft: 25 | return bottomRight; 26 | case bottomRight: 27 | return bottomLeft; 28 | default: 29 | return this; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiButton.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.network.chat.Component; 5 | import se.mickelus.mutil.gui.impl.GuiColors; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class GuiButton extends GuiClickable { 11 | private final GuiStringOutline textElement; 12 | 13 | private boolean enabled = true; 14 | private Component disabledTooltip; 15 | 16 | public GuiButton(int x, int y, int width, int height, String text, Runnable onClick) { 17 | super(x, y, width, height, onClick); 18 | 19 | textElement = new GuiStringOutline(0, (height - 8) / 2, text); 20 | addChild(textElement); 21 | } 22 | 23 | public GuiButton(int x, int y, String text, Runnable onClick) { 24 | this(x, y, Minecraft.getInstance().font.width(text), 10, text, onClick); 25 | } 26 | 27 | public GuiButton(int x, int y, int width, int height, String text, Runnable onClick, Component disabledTooltip) { 28 | this(x, y, width, height, text, onClick); 29 | 30 | this.disabledTooltip = disabledTooltip; 31 | } 32 | 33 | @Override 34 | public boolean onMouseClick(int x, int y, int button) { 35 | return enabled && super.onMouseClick(x, y, button); 36 | } 37 | 38 | private void updateColor() { 39 | if (!enabled) { 40 | textElement.setColor(GuiColors.muted); 41 | } else if (hasFocus()) { 42 | textElement.setColor(GuiColors.hover); 43 | } else { 44 | textElement.setColor(GuiColors.normal); 45 | } 46 | } 47 | 48 | public void setEnabled(boolean enabled) { 49 | this.enabled = enabled; 50 | updateColor(); 51 | } 52 | 53 | public void setText(String text) { 54 | textElement.setString(text); 55 | setWidth(Minecraft.getInstance().font.width(text)); 56 | } 57 | 58 | @Override 59 | protected void onFocus() { 60 | updateColor(); 61 | } 62 | 63 | @Override 64 | protected void onBlur() { 65 | updateColor(); 66 | } 67 | 68 | @Override 69 | public List getTooltipLines() { 70 | if (!enabled && disabledTooltip != null && hasFocus()) { 71 | return Collections.singletonList(disabledTooltip); 72 | } 73 | return null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiClickable.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | 5 | public class GuiClickable extends GuiElement { 6 | 7 | protected final Runnable onClickHandler; 8 | 9 | public GuiClickable(int x, int y, int width, int height, Runnable onClickHandler) { 10 | super(x, y, width, height); 11 | 12 | this.onClickHandler = onClickHandler; 13 | } 14 | 15 | @Override 16 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 17 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 18 | } 19 | 20 | @Override 21 | public boolean onMouseClick(int x, int y, int button) { 22 | if (hasFocus()) { 23 | onClickHandler.run(); 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiElement.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.client.gui.GuiComponent; 5 | import net.minecraft.network.chat.Component; 6 | import se.mickelus.mutil.gui.animation.KeyframeAnimation; 7 | 8 | import java.util.*; 9 | import java.util.stream.Collectors; 10 | 11 | // todo 1.14: used to extend Gui, should extend GuiAbstract? 12 | public class GuiElement extends GuiComponent { 13 | 14 | protected int x; 15 | protected int y; 16 | protected GuiAttachment attachmentPoint = GuiAttachment.topLeft; 17 | protected GuiAttachment attachmentAnchor = GuiAttachment.topLeft; 18 | 19 | protected int width; 20 | protected int height; 21 | 22 | protected float opacity = 1; 23 | 24 | protected boolean hasFocus = false; 25 | 26 | protected boolean isVisible = true; 27 | 28 | protected boolean shouldRemove = false; 29 | 30 | protected ArrayList elements; 31 | 32 | protected Set activeAnimations; 33 | 34 | public GuiElement(int x, int y, int width, int height) { 35 | this.x = x; 36 | this.y = y; 37 | this.width = width; 38 | this.height = height; 39 | 40 | elements = new ArrayList<>(); 41 | 42 | activeAnimations = new HashSet<>(); 43 | } 44 | 45 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 46 | drawChildren(matrixStack, refX + x, refY + y, screenWidth, screenHeight, mouseX, mouseY, opacity * this.opacity); 47 | } 48 | 49 | public void updateAnimations() { 50 | // activeAnimations.stream() 51 | // .filter(animation -> !animation.isActive()) 52 | // .forEach(KeyframeAnimation::stop); 53 | activeAnimations.removeIf(animation -> !animation.isActive()); 54 | activeAnimations.forEach(KeyframeAnimation::preDraw); 55 | } 56 | 57 | protected void drawChildren(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 58 | elements.removeIf(GuiElement::shouldRemove); 59 | elements.stream() 60 | .filter(GuiElement::isVisible) 61 | .forEach((element -> { 62 | element.updateAnimations(); 63 | element.draw( 64 | matrixStack, refX + getXOffset(this, element.attachmentAnchor) - getXOffset(element, element.attachmentPoint), 65 | refY + getYOffset(this, element.attachmentAnchor) - getYOffset(element, element.attachmentPoint), 66 | screenWidth, screenHeight, mouseX, mouseY, opacity); 67 | })); 68 | } 69 | 70 | protected static int getXOffset(GuiElement element, GuiAttachment attachment) { 71 | switch (attachment) { 72 | case topLeft: 73 | case middleLeft: 74 | case bottomLeft: 75 | return 0; 76 | case topCenter: 77 | case middleCenter: 78 | case bottomCenter: 79 | return element.getWidth() / 2; 80 | case topRight: 81 | case middleRight: 82 | case bottomRight: 83 | return element.getWidth(); 84 | } 85 | return 0; 86 | } 87 | 88 | protected static int getYOffset(GuiElement element, GuiAttachment attachment) { 89 | switch (attachment) { 90 | case topLeft: 91 | case topCenter: 92 | case topRight: 93 | return 0; 94 | case middleLeft: 95 | case middleCenter: 96 | case middleRight: 97 | return element.getHeight() / 2; 98 | case bottomCenter: 99 | case bottomLeft: 100 | case bottomRight: 101 | return element.getHeight(); 102 | } 103 | return 0; 104 | } 105 | 106 | public boolean onMouseClick(int x, int y, int button) { 107 | for (int i = elements.size() - 1; i >= 0; i--) { 108 | if (elements.get(i).isVisible()) { 109 | if (elements.get(i).onMouseClick(x, y, button)) { 110 | return true; 111 | } 112 | } 113 | } 114 | 115 | return false; 116 | } 117 | 118 | public void onMouseRelease(int x, int y, int button) { 119 | elements.forEach(element -> element.onMouseRelease(x, y, button)); 120 | } 121 | 122 | 123 | public boolean onMouseScroll(double mouseX, double mouseY, double distance) { 124 | for (int i = elements.size() - 1; i >= 0; i--) { 125 | if (elements.get(i).isVisible()) { 126 | if (elements.get(i).onMouseScroll(mouseX, mouseY, distance)) { 127 | return true; 128 | } 129 | } 130 | } 131 | 132 | return false; 133 | } 134 | 135 | public boolean onKeyPress(int keyCode, int scanCode, int modifiers) { 136 | for (int i = elements.size() - 1; i >= 0; i--) { 137 | if (elements.get(i).isVisible()) { 138 | if (elements.get(i).onKeyPress(keyCode, scanCode, modifiers)) { 139 | return true; 140 | } 141 | } 142 | } 143 | 144 | return false; 145 | } 146 | 147 | public boolean onKeyRelease(int keyCode, int scanCode, int modifiers) { 148 | for (int i = elements.size() - 1; i >= 0; i--) { 149 | if (elements.get(i).isVisible()) { 150 | if (elements.get(i).onKeyRelease(keyCode, scanCode, modifiers)) { 151 | return true; 152 | } 153 | } 154 | } 155 | 156 | return false; 157 | } 158 | 159 | public boolean onCharType(char character, int modifiers) { 160 | for (int i = elements.size() - 1; i >= 0; i--) { 161 | if (elements.get(i).isVisible()) { 162 | if (elements.get(i).onCharType(character, modifiers)) { 163 | return true; 164 | } 165 | } 166 | } 167 | 168 | return false; 169 | } 170 | 171 | public void updateFocusState(int refX, int refY, int mouseX, int mouseY) { 172 | elements.stream() 173 | .filter(GuiElement::isVisible) 174 | .forEach(element -> element.updateFocusState( 175 | refX + x + getXOffset(this, element.attachmentAnchor) - getXOffset(element, element.attachmentPoint), 176 | refY + y + getYOffset(this, element.attachmentAnchor) - getYOffset(element, element.attachmentPoint), 177 | mouseX, mouseY)); 178 | 179 | boolean gainFocus = mouseX >= getX() + refX 180 | && mouseX < getX() + refX + getWidth() 181 | && mouseY >= getY() + refY 182 | && mouseY < getY() + refY + getHeight(); 183 | 184 | if (gainFocus != hasFocus) { 185 | hasFocus = gainFocus; 186 | if (hasFocus) { 187 | onFocus(); 188 | } else { 189 | onBlur(); 190 | } 191 | } 192 | } 193 | 194 | protected void onFocus() { 195 | 196 | } 197 | 198 | protected void onBlur() { 199 | 200 | } 201 | 202 | public boolean hasFocus() { 203 | return hasFocus; 204 | } 205 | 206 | public int getX() { 207 | return x; 208 | } 209 | 210 | public void setX(int x) { 211 | this.x = x; 212 | } 213 | 214 | public int getY() { 215 | return y; 216 | } 217 | 218 | public void setY(int y) { 219 | this.y = y; 220 | } 221 | 222 | /** 223 | * Set which point, relative this element, that it should be positioned on. 224 | * @param attachment 225 | * @return 226 | */ 227 | public GuiElement setAttachmentPoint(GuiAttachment attachment) { 228 | attachmentPoint = attachment; 229 | 230 | return this; 231 | } 232 | 233 | /** 234 | * Set which point, relative the parent, that this element should be positioned on. 235 | * @param attachment 236 | * @return 237 | */ 238 | public GuiElement setAttachmentAnchor(GuiAttachment attachment) { 239 | attachmentAnchor = attachment; 240 | 241 | return this; 242 | } 243 | 244 | public GuiElement setAttachment(GuiAttachment attachment) { 245 | attachmentPoint = attachment; 246 | attachmentAnchor = attachment; 247 | 248 | return this; 249 | } 250 | 251 | public GuiAttachment getAttachmentPoint() { 252 | return attachmentPoint; 253 | } 254 | 255 | public GuiAttachment getAttachmentAnchor() { 256 | return attachmentAnchor; 257 | } 258 | 259 | public int getWidth() { 260 | return width; 261 | } 262 | 263 | public void setWidth(int width) { 264 | this.width = width; 265 | } 266 | 267 | public int getHeight() { 268 | return height; 269 | } 270 | 271 | public void setHeight(int height) { 272 | this.height = height; 273 | } 274 | 275 | public void setVisible(boolean visible) { 276 | if (isVisible != visible) { 277 | if (visible) { 278 | onShow(); 279 | } else { 280 | if (!onHide()) { 281 | return; 282 | } 283 | } 284 | isVisible = visible; 285 | } 286 | } 287 | 288 | public boolean isVisible() { 289 | return isVisible; 290 | } 291 | 292 | protected void onShow() {} 293 | 294 | /** 295 | * Can be overridden to do something when the element is hidden. Returning false indicates that the handler will 296 | * take care of setting isVisible to false. 297 | * @return 298 | */ 299 | protected boolean onHide() { 300 | this.hasFocus = false; 301 | return true; 302 | } 303 | 304 | public GuiElement setOpacity(float opacity) { 305 | this.opacity = opacity; 306 | return this; 307 | } 308 | 309 | public float getOpacity() { 310 | return opacity; 311 | } 312 | 313 | public void addAnimation(KeyframeAnimation animation) { 314 | activeAnimations.add(animation); 315 | } 316 | 317 | public void removeAnimation(KeyframeAnimation animation) { 318 | activeAnimations.remove(animation); 319 | } 320 | 321 | public void remove() { 322 | shouldRemove = true; 323 | } 324 | 325 | public boolean shouldRemove() { 326 | return shouldRemove; 327 | } 328 | 329 | public void addChild(GuiElement child) { 330 | this.elements.add(child); 331 | } 332 | 333 | public void clearChildren() { 334 | this.elements.clear(); 335 | } 336 | 337 | public int getNumChildren() { 338 | return elements.size(); 339 | } 340 | 341 | public GuiElement getChild(int index) { 342 | if (index >= 0 && index < elements.size()) { 343 | return elements.get(index); 344 | } 345 | return null; 346 | } 347 | 348 | public List getChildren() { 349 | return Collections.unmodifiableList(elements); 350 | } 351 | 352 | /** 353 | * Return child elements which has the given type 354 | * @param type 355 | * @param 356 | * @return 357 | */ 358 | public List getChildren(Class type) { 359 | return elements.stream() 360 | .filter(type::isInstance) 361 | .map(type::cast) 362 | .collect(Collectors.toList()); 363 | } 364 | 365 | public List getTooltipLines() { 366 | if (isVisible()) { 367 | return elements.stream() 368 | .map(GuiElement::getTooltipLines) 369 | .filter(Objects::nonNull) 370 | .findFirst() 371 | .orElse(null); 372 | } 373 | return null; 374 | } 375 | 376 | 377 | protected static void drawRect(PoseStack matrixStack, int left, int top, int right, int bottom, int color, float opacity) { 378 | fill(matrixStack, left, top, right, bottom, colorWithOpacity(color, opacity)); 379 | } 380 | 381 | protected static int colorWithOpacity(int color, float opacity) { 382 | return colorWithOpacity(color, Math.round(opacity * 255)); 383 | } 384 | 385 | protected static int colorWithOpacity(int color, int opacity) { 386 | // replace alpha bits with passed opacity value, multiples opacity with current alpha bits if they are present 387 | return color & 0xffffff | (opacity * (color >> 24 == 0 ? 255 : color >> 24 & 255) / 255 << 24); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiItem.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.platform.GlStateManager; 4 | import com.mojang.blaze3d.systems.RenderSystem; 5 | import com.mojang.blaze3d.vertex.PoseStack; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.Font; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.minecraft.world.item.TooltipFlag; 11 | import net.minecraftforge.client.extensions.common.IClientItemExtensions; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class GuiItem extends GuiElement { 17 | private Minecraft mc; 18 | 19 | private ItemStack itemStack; 20 | 21 | private boolean showTooltip = true; 22 | private CountMode countMode = CountMode.normal; 23 | 24 | private float opacityThreshold = 1; 25 | private boolean resetDepthTest = true; 26 | 27 | private boolean renderDecoration = true; 28 | 29 | public GuiItem(int x, int y) { 30 | super(x, y, 16, 16); 31 | 32 | mc = Minecraft.getInstance(); 33 | 34 | setVisible(false); 35 | } 36 | 37 | /** 38 | * Sets the opacity threshold for this element, the item will only render when the combined opacity of this element and it's parent is above the 39 | * threshold. 40 | * 41 | * @param opacityThreshold 42 | * @return 43 | */ 44 | public GuiItem setOpacityThreshold(float opacityThreshold) { 45 | this.opacityThreshold = opacityThreshold; 46 | return this; 47 | } 48 | 49 | public GuiItem setTooltip(boolean showTooltip) { 50 | this.showTooltip = showTooltip; 51 | return this; 52 | } 53 | 54 | public GuiItem setCountVisibility(CountMode mode) { 55 | this.countMode = mode; 56 | return this; 57 | } 58 | 59 | public GuiItem setItem(ItemStack itemStack) { 60 | this.itemStack = itemStack; 61 | setVisible(itemStack != null); 62 | 63 | return this; 64 | } 65 | 66 | public GuiItem setResetDepthTest(boolean shouldReset) { 67 | this.resetDepthTest = shouldReset; 68 | return this; 69 | } 70 | 71 | public GuiItem setRenderDecoration(boolean shouldRender) { 72 | this.renderDecoration = shouldRender; 73 | return this; 74 | } 75 | 76 | // todo 1.16: opacity no longer works, did it ever work? 77 | @Override 78 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 79 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 80 | 81 | if (opacity * getOpacity() >= opacityThreshold) { 82 | RenderSystem.applyModelViewMatrix(); 83 | setBlitOffset(0); 84 | mc.getItemRenderer().blitOffset = 0; 85 | RenderSystem.enableDepthTest(); 86 | RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, 87 | GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); 88 | mc.getItemRenderer().renderAndDecorateItem(itemStack, refX + x, refY + y); 89 | 90 | if (renderDecoration) { 91 | Font font = IClientItemExtensions.of(itemStack).getFont(itemStack, IClientItemExtensions.FontContext.ITEM_COUNT); 92 | mc.getItemRenderer().renderGuiItemDecorations(font != null ? font : mc.font, itemStack, refX + x, refY + y, getCountString()); 93 | } 94 | 95 | if (resetDepthTest) { 96 | RenderSystem.disableDepthTest(); 97 | } 98 | } 99 | } 100 | 101 | protected String getCountString() { 102 | switch (countMode) { 103 | case normal: 104 | return null; 105 | case always: 106 | return String.valueOf(itemStack.getCount()); 107 | case never: 108 | return ""; 109 | } 110 | 111 | return null; 112 | } 113 | 114 | @Override 115 | public List getTooltipLines() { 116 | if (showTooltip && itemStack != null && hasFocus()) { 117 | return new ArrayList<>(itemStack.getTooltipLines(Minecraft.getInstance().player, 118 | mc.options.advancedItemTooltips ? TooltipFlag.Default.ADVANCED : TooltipFlag.Default.NORMAL)); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | public enum CountMode { 125 | normal, // shows if count is > 1 126 | 127 | always, 128 | never 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiRect.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | 6 | public class GuiRect extends GuiElement { 7 | 8 | private int color; 9 | private boolean offset; 10 | 11 | public GuiRect(int x, int y, int width, int height, int color) { 12 | this(x, y, width, height, color, false); 13 | } 14 | 15 | public GuiRect(int x, int y, int width, int height, int color, boolean offset) { 16 | super(x, y, offset ? width + 1 : width, offset ? height + 1 : height); 17 | 18 | this.color = color; 19 | this.offset = offset; 20 | } 21 | 22 | public GuiRect setColor(int color) { 23 | this.color = color; 24 | return this; 25 | } 26 | 27 | @Override 28 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 29 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 30 | RenderSystem.setShaderColor(1, 1, 1, 1); 31 | if (offset) { 32 | matrixStack.pushPose(); 33 | matrixStack.translate(0.5F, 0.5F, 0); 34 | drawRect(matrixStack, refX + x, refY + y, refX + x + width - 1, refY + y + height - 1, color, opacity * getOpacity()); 35 | matrixStack.popPose(); 36 | } else { 37 | drawRect(matrixStack, refX + x, refY + y, refX + x + width, refY + y + height, color, opacity * getOpacity()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiRoot.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.platform.Window; 5 | import net.minecraft.client.Minecraft; 6 | 7 | public class GuiRoot extends GuiElement { 8 | 9 | protected Minecraft mc; 10 | 11 | public GuiRoot(Minecraft mc) { 12 | super(0, 0, 0 ,0); 13 | this.mc = mc; 14 | } 15 | 16 | public void draw() { 17 | draw(new PoseStack()); 18 | } 19 | 20 | public void draw(PoseStack poseStack) { 21 | if (isVisible()) { 22 | Window window = mc.getWindow(); 23 | 24 | width = window.getGuiScaledWidth(); 25 | height = window.getGuiScaledHeight(); 26 | double mouseX = mc.mouseHandler.xpos() * width / window.getScreenWidth(); 27 | double mouseY = mc.mouseHandler.ypos() * height / window.getScreenHeight(); 28 | 29 | drawChildren(poseStack, 0, 0, width, height, (int) mouseX, (int) mouseY, 1); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiString.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.systems.RenderSystem; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.Font; 7 | import net.minecraft.client.renderer.MultiBufferSource; 8 | import com.mojang.blaze3d.vertex.Tesselator; 9 | import se.mickelus.mutil.gui.animation.KeyframeAnimation; 10 | 11 | public class GuiString extends GuiElement { 12 | 13 | protected String string; 14 | 15 | protected Font fontRenderer; 16 | 17 | protected int color = 0xffffffff; 18 | protected boolean drawShadow = true; 19 | 20 | protected boolean fixedWidth = false; 21 | 22 | public GuiString(int x, int y, String string) { 23 | super(x, y, 0, 9); 24 | 25 | fontRenderer = Minecraft.getInstance().font; 26 | 27 | this.string = string; 28 | width = fontRenderer.width(string); 29 | } 30 | 31 | public GuiString(int x, int y, int width, String string) { 32 | super(x, y, width, 9); 33 | 34 | fixedWidth = true; 35 | 36 | fontRenderer = Minecraft.getInstance().font; 37 | 38 | this.string = fontRenderer.plainSubstrByWidth(string, width); 39 | } 40 | 41 | public GuiString(int x, int y, String string, GuiAttachment attachment) { 42 | this(x, y, string); 43 | 44 | attachmentPoint = attachment; 45 | } 46 | 47 | public GuiString(int x, int y, String string, int color) { 48 | this(x, y, string); 49 | 50 | this.color = color; 51 | } 52 | 53 | public GuiString(int x, int y, String string, int color, GuiAttachment attachment) { 54 | this(x, y, string, attachment); 55 | 56 | this.color = color; 57 | } 58 | 59 | public void setColor(int color) { 60 | this.color = color; 61 | } 62 | 63 | public void setString(String string) { 64 | if (string != null && !string.equals(this.string)) { 65 | if (fixedWidth) { 66 | this.string = fontRenderer.plainSubstrByWidth(string, width); 67 | } else { 68 | this.string = string; 69 | width = fontRenderer.width(string); 70 | } 71 | } 72 | } 73 | 74 | public GuiString setShadow(boolean shadow) { 75 | drawShadow = shadow; 76 | return this; 77 | } 78 | 79 | @Override 80 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 81 | activeAnimations.removeIf(keyframeAnimation -> !keyframeAnimation.isActive()); 82 | activeAnimations.forEach(KeyframeAnimation::preDraw); 83 | RenderSystem.enableBlend(); 84 | drawString(matrixStack, string, refX + x, refY + y, color, opacity * getOpacity(), drawShadow); 85 | } 86 | 87 | protected void drawString(PoseStack matrixStack, String text, int x, int y, int color, float opacity, boolean drawShadow) { 88 | color = colorWithOpacity(color, opacity); 89 | 90 | // if the vanilla fontrender considers the color to be almost transparent (0xfc) it flips the opacity back to 1 91 | if ((color & -67108864) != 0) { 92 | matrixStack.pushPose(); 93 | 94 | // todo: why was this needed? removed as it breaks in world rendering 95 | // matrixStack.translate(0.0D, 0.0D, 300.0D); 96 | MultiBufferSource.BufferSource renderTypeBuffer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); 97 | 98 | // packed light value is magic copied from FontRender.renderString 99 | fontRenderer.drawInBatch(text, (float)x, (float)y, color, drawShadow, matrixStack.last().pose(), renderTypeBuffer, true, 0, 15728880); 100 | 101 | renderTypeBuffer.endBatch(); 102 | matrixStack.popPose(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiStringOutline.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.ChatFormatting; 5 | 6 | public class GuiStringOutline extends GuiString { 7 | private String cleanString; 8 | 9 | public GuiStringOutline(int x, int y, String string) { 10 | super(x, y, string); 11 | drawShadow = false; 12 | 13 | cleanString = ChatFormatting.stripFormatting(this.string); 14 | } 15 | 16 | public GuiStringOutline(int x, int y, int width, String string) { 17 | super(x, y, width, string); 18 | drawShadow = false; 19 | 20 | cleanString = ChatFormatting.stripFormatting(this.string); 21 | } 22 | 23 | public GuiStringOutline(int x, int y, String string, GuiAttachment attachment) { 24 | super(x, y, string, attachment); 25 | drawShadow = false; 26 | 27 | cleanString = ChatFormatting.stripFormatting(this.string); 28 | } 29 | 30 | public GuiStringOutline(int x, int y, String string, int color) { 31 | super(x, y, string, color); 32 | drawShadow = false; 33 | 34 | cleanString = ChatFormatting.stripFormatting(this.string); 35 | } 36 | 37 | public GuiStringOutline(int x, int y, String string, int color, GuiAttachment attachment) { 38 | super(x, y, string, color, attachment); 39 | drawShadow = false; 40 | 41 | cleanString = ChatFormatting.stripFormatting(this.string); 42 | } 43 | 44 | @Override 45 | public void setString(String string) { 46 | super.setString(string); 47 | 48 | cleanString = ChatFormatting.stripFormatting(this.string); 49 | } 50 | 51 | @Override 52 | protected void drawString(PoseStack matrixStack, String text, int x, int y, int color, float opacity, boolean drawShadow) { 53 | 54 | super.drawString(matrixStack, cleanString, x - 1, y - 1, 0, opacity, false); 55 | super.drawString(matrixStack, cleanString, x, y - 1, 0, opacity, false); 56 | super.drawString(matrixStack, cleanString, x + 1, y - 1, 0, opacity, false); 57 | 58 | super.drawString(matrixStack, cleanString, x - 1, y + 1, 0, opacity, false); 59 | super.drawString(matrixStack, cleanString, x, y + 1, 0, opacity, false); 60 | super.drawString(matrixStack, cleanString, x + 1, y + 1, 0, opacity, false); 61 | 62 | super.drawString(matrixStack, cleanString, x + 1, y, 0, opacity, false); 63 | super.drawString(matrixStack, cleanString, x - 1, y, 0, opacity, false); 64 | 65 | super.drawString(matrixStack, text, x, y, color, opacity, false); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiStringSmall.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import se.mickelus.mutil.gui.animation.KeyframeAnimation; 5 | 6 | public class GuiStringSmall extends GuiString { 7 | 8 | public GuiStringSmall(int x, int y, String string) { 9 | super(x*2, y*2, string); 10 | } 11 | 12 | public GuiStringSmall(int x, int y, String string, int color) { 13 | super(x*2, y*2, string, color); 14 | } 15 | 16 | public GuiStringSmall(int x, int y, String string, GuiAttachment attachment) { 17 | super(x*2, y*2, string, attachment); 18 | } 19 | 20 | public GuiStringSmall(int x, int y, String string, int color, GuiAttachment attachment) { 21 | super(x*2, y*2, string, color, attachment); 22 | } 23 | 24 | @Override 25 | public void setX(int x) { 26 | super.setX(x * 2); 27 | } 28 | 29 | @Override 30 | public void setY(int y) { 31 | super.setY(y * 2); 32 | } 33 | 34 | @Override 35 | public int getX() { 36 | return super.getX() / 2; 37 | } 38 | 39 | @Override 40 | public int getY() { 41 | return super.getY() / 2; 42 | } 43 | 44 | @Override 45 | public int getWidth() { 46 | return width / 2; 47 | } 48 | 49 | @Override 50 | public int getHeight() { 51 | return height / 2; 52 | } 53 | 54 | @Override 55 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 56 | activeAnimations.removeIf(keyframeAnimation -> !keyframeAnimation.isActive()); 57 | activeAnimations.forEach(KeyframeAnimation::preDraw); 58 | matrixStack.pushPose(); 59 | matrixStack.scale(.5f, .5f, .5f); 60 | drawString(matrixStack, string, refX * 2 + x, refY * 2 + y, color, opacity * getOpacity(), drawShadow); 61 | matrixStack.popPose(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiText.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.vertex.Tesselator; 5 | import com.mojang.math.Matrix4f; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.Font; 8 | import net.minecraft.client.renderer.MultiBufferSource; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.util.FormattedCharSequence; 11 | 12 | import java.util.List; 13 | 14 | public class GuiText extends GuiElement { 15 | 16 | Font fontRenderer; 17 | 18 | String string; 19 | int color = 0xffffff; 20 | 21 | public GuiText(int x, int y, int width, String string) { 22 | super(x, y, width ,0); 23 | 24 | fontRenderer = Minecraft.getInstance().font; 25 | setString(string); 26 | } 27 | 28 | public void setString(String string) { 29 | this.string = string.replace("\\n", "\n"); 30 | 31 | height = fontRenderer.wordWrapHeight(this.string, width); 32 | } 33 | 34 | public void setColor(int color) { 35 | this.color = color; 36 | } 37 | 38 | @Override 39 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 40 | renderText(fontRenderer, matrixStack, string, refX + x, refY + y, width, color, opacity); 41 | 42 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 43 | } 44 | 45 | protected static void renderText(Font fontRenderer, PoseStack matrixStack, String string, int x, int y, int width, int color, 46 | float opacity) { 47 | List list = fontRenderer.split(Component.literal(string), width); 48 | Matrix4f matrix = matrixStack.last().pose(); 49 | 50 | for(FormattedCharSequence line : list) { 51 | float lineX = (float) x; 52 | MultiBufferSource.BufferSource buffer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); 53 | 54 | // if (fontRenderer.getBidiFlag()) { 55 | // int i = fontRenderer.getStringWidth(fontRenderer.bidiReorder(line.)); 56 | // lineX += (float)(width - i); 57 | // } 58 | 59 | fontRenderer.drawInBatch(line, lineX, (float)y, colorWithOpacity(color, opacity), true, matrix, buffer, false, 0, 15728880); 60 | 61 | buffer.endBatch(); 62 | 63 | y += 9; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiTextSmall.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | 5 | public class GuiTextSmall extends GuiText { 6 | 7 | public GuiTextSmall(int x, int y, int width, String string) { 8 | super(x, y, width , string); 9 | } 10 | 11 | public void setString(String string) { 12 | this.string = string.replace("\\n", "\n"); 13 | 14 | height = fontRenderer.wordWrapHeight(this.string, width * 2) / 2; 15 | } 16 | 17 | @Override 18 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 19 | matrixStack.pushPose(); 20 | matrixStack.scale(.5f, .5f, .5f); 21 | renderText(fontRenderer, matrixStack, string, (refX + x) * 2, (refY + y) * 2, width * 2, 0xffffff, opacity); 22 | matrixStack.popPose(); 23 | 24 | drawChildren(matrixStack, refX + x, refY + y, screenWidth, screenHeight, mouseX, mouseY, opacity * this.opacity); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiTexture.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | import net.minecraft.client.renderer.GameRenderer; 6 | import net.minecraft.client.renderer.ShaderInstance; 7 | import net.minecraft.resources.ResourceLocation; 8 | 9 | import java.util.function.Supplier; 10 | 11 | public class GuiTexture extends GuiElement { 12 | 13 | protected ResourceLocation textureLocation; 14 | 15 | protected int textureX; 16 | protected int textureY; 17 | 18 | protected int color = 0xffffff; 19 | 20 | protected Supplier shader = GameRenderer::getPositionTexShader; 21 | private boolean useDefaultBlending = true; 22 | 23 | public GuiTexture(int x, int y, int width, int height, ResourceLocation textureLocation) { 24 | this(x, y, width, height, 0, 0, textureLocation); 25 | } 26 | 27 | public GuiTexture(int x, int y, int width, int height, int textureX, int textureY, ResourceLocation textureLocation) { 28 | super(x, y, width, height); 29 | 30 | this.textureX = textureX; 31 | this.textureY = textureY; 32 | 33 | this.textureLocation = textureLocation; 34 | } 35 | 36 | public GuiTexture setTextureCoordinates(int x, int y) { 37 | textureX = x; 38 | textureY = y; 39 | return this; 40 | } 41 | 42 | public GuiTexture setColor(int color) { 43 | this.color = color; 44 | return this; 45 | } 46 | 47 | public GuiTexture setShader(Supplier shader) { 48 | this.shader = shader; 49 | return this; 50 | } 51 | 52 | public void setUseDefaultBlending(boolean useDefault) { 53 | this.useDefaultBlending = useDefault; 54 | } 55 | 56 | @Override 57 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 58 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 59 | 60 | drawTexture(matrixStack, textureLocation, refX + x, refY + y, width, height, textureX, textureY, 61 | color, getOpacity() * opacity); 62 | } 63 | 64 | protected void drawTexture(PoseStack matrixStack, ResourceLocation textureLocation, int x, int y, int width, int height, 65 | int u, int v, int color, float opacity) { 66 | RenderSystem.setShader(shader); 67 | RenderSystem.setShaderColor( 68 | (color >> 16 & 255) / 255f, // red 69 | (color >> 8 & 255) / 255f, // green 70 | (color & 255) / 255f, // blue 71 | opacity); 72 | RenderSystem.setShaderTexture(0, textureLocation); 73 | RenderSystem.enableBlend(); 74 | 75 | if (useDefaultBlending) { 76 | RenderSystem.defaultBlendFunc(); 77 | } 78 | this.blit(matrixStack, x, y, u, v, width, height); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/GuiTextureOffset.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.resources.ResourceLocation; 5 | 6 | /** 7 | * Texture with half "pixel" offset 8 | */ 9 | public class GuiTextureOffset extends GuiTexture { 10 | 11 | public GuiTextureOffset(int x, int y, int width, int height, ResourceLocation textureLocation) { 12 | super(x, y, width + 1, height + 1, textureLocation); 13 | } 14 | 15 | public GuiTextureOffset(int x, int y, int width, int height, int textureX, int textureY, ResourceLocation textureLocation) { 16 | super(x, y, width + 1, height + 1, textureX, textureY, textureLocation); 17 | } 18 | 19 | @Override 20 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 21 | drawChildren(matrixStack, refX + x, refY + y, screenWidth, screenHeight, mouseX, mouseY, opacity * this.opacity); 22 | 23 | matrixStack.pushPose(); 24 | matrixStack.translate(0.5F, 0.5F, 0); 25 | drawTexture(matrixStack, textureLocation, refX + x, refY + y, width - 1, height - 1, textureX, textureY, 26 | color, getOpacity() * opacity); 27 | matrixStack.popPose(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/ScrollBarGui.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import se.mickelus.mutil.gui.impl.GuiHorizontalScrollable; 5 | 6 | public class ScrollBarGui extends GuiElement { 7 | private final GuiHorizontalScrollable scrollable; 8 | 9 | private final boolean unscrollableHidden; 10 | 11 | public ScrollBarGui(int x, int y, int width, int height, GuiHorizontalScrollable scrollable) { 12 | this(x, y, width, height, scrollable, false); 13 | } 14 | 15 | public ScrollBarGui(int x, int y, int width, int height, GuiHorizontalScrollable scrollable, boolean unscrollableHidden) { 16 | super(x, y, width, height); 17 | 18 | this.scrollable = scrollable; 19 | this.unscrollableHidden = unscrollableHidden; 20 | } 21 | 22 | private boolean isActive() { 23 | return !unscrollableHidden || scrollable.getOffsetMax() > 0; 24 | } 25 | 26 | @Override 27 | public void draw(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 28 | if (isActive()) { 29 | super.draw(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 30 | 31 | drawBackground(matrixStack, refX + x, refY + y); 32 | int contentWidth = scrollable.getOffsetMax() + scrollable.getWidth(); 33 | 34 | int handleWidth = Math.max(3, (int) (scrollable.getWidth() * 1f / contentWidth * width) + 1); 35 | int handleOffset = (int) (scrollable.getOffset() / contentWidth * width); 36 | 37 | drawHandle(matrixStack, refX + x + handleOffset, refY + y, handleWidth); 38 | } 39 | } 40 | 41 | protected void drawBackground(PoseStack matrixStack, int x, int y) { 42 | drawRect(matrixStack, x, y, x + width, y + height, 0xffffff, opacity * 0.2f); 43 | } 44 | 45 | protected void drawHandle(PoseStack matrixStack, int x, int y, int handleWidth) { 46 | drawRect(matrixStack, x, y, x + handleWidth, y + height, 0xffffff, opacity * 0.7f); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/ToggleableSlot.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui; 2 | 3 | import net.minecraft.world.entity.player.Player; 4 | import net.minecraft.world.item.ItemStack; 5 | import net.minecraftforge.items.IItemHandler; 6 | import net.minecraftforge.items.SlotItemHandler; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | public class ToggleableSlot extends SlotItemHandler { 11 | 12 | private boolean isEnabled = true; 13 | private int realX, realY; 14 | 15 | public ToggleableSlot(IItemHandler itemHandler, int index, int xPosition, int yPosition) { 16 | super(itemHandler, index, xPosition, yPosition); 17 | 18 | realX = xPosition; 19 | realY = yPosition; 20 | } 21 | 22 | public void toggle(boolean enabled) { 23 | isEnabled = enabled; 24 | } 25 | 26 | @Override 27 | public boolean isActive() { 28 | return isEnabled; 29 | } 30 | 31 | @Override 32 | public boolean mayPickup(Player playerIn) { 33 | return isEnabled; 34 | } 35 | 36 | @Override 37 | public boolean mayPlace(@Nullable ItemStack stack) { 38 | return isEnabled; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/animation/AnimationChain.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.animation; 2 | 3 | import java.util.function.Consumer; 4 | 5 | public class AnimationChain implements GuiAnimation { 6 | 7 | private final KeyframeAnimation[] animations; 8 | private KeyframeAnimation activeAnimation; 9 | 10 | private boolean looping = false; 11 | private Consumer stopHandler; 12 | 13 | private boolean isActive; 14 | 15 | public AnimationChain(KeyframeAnimation ... animations) { 16 | this.animations = animations; 17 | 18 | for (int i = 0; i < animations.length; i++) { 19 | final int index = i; 20 | animations[i].onStop(isActive -> { 21 | if (isActive) { 22 | startNext(index); 23 | } else if (stopHandler != null) { 24 | stopHandler.accept(false); 25 | } 26 | }); 27 | } 28 | } 29 | 30 | public AnimationChain setLooping(boolean looping) { 31 | this.looping = looping; 32 | return this; 33 | } 34 | 35 | public AnimationChain onStop(Consumer handler) { 36 | stopHandler = handler; 37 | return this; 38 | } 39 | 40 | public void stop() { 41 | isActive = false; 42 | if (activeAnimation != null) { 43 | activeAnimation.stop(); 44 | } 45 | } 46 | 47 | public void start() { 48 | isActive = true; 49 | activeAnimation = animations[0]; 50 | activeAnimation.start(); 51 | } 52 | 53 | private void startNext(int currentIndex) { 54 | if (isActive) { 55 | if (currentIndex + 1 >= animations.length) { 56 | if (looping) { 57 | activeAnimation = animations[0]; 58 | } else { 59 | if (stopHandler != null) { 60 | stopHandler.accept(true); 61 | } 62 | activeAnimation = null; 63 | isActive = false; 64 | } 65 | } else { 66 | activeAnimation = animations[currentIndex + 1]; 67 | } 68 | 69 | if (activeAnimation != null) { 70 | activeAnimation.start(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/animation/Applier.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.animation; 2 | 3 | import se.mickelus.mutil.gui.GuiElement; 4 | 5 | public abstract class Applier { 6 | 7 | protected GuiElement element; 8 | 9 | protected boolean relativeStart; 10 | protected boolean relativeTarget; 11 | protected float startOffset = 0; 12 | protected float targetOffset = 0; 13 | 14 | 15 | protected float startValue; 16 | protected float targetValue; 17 | 18 | protected float currentValue; 19 | 20 | public Applier(float targetValue) { 21 | this(0, targetValue, true, false); 22 | } 23 | 24 | public Applier(float startValue, float targetValue) { 25 | this(startValue, targetValue, false, false); 26 | } 27 | 28 | public Applier(float startValue, float targetValue, boolean relativeStart, boolean relativeTarget) { 29 | this.targetValue = targetValue; 30 | this.startValue = startValue; 31 | 32 | this.relativeStart = relativeStart; 33 | this.relativeTarget = relativeTarget; 34 | 35 | this.startOffset = startValue; 36 | this.targetOffset = targetValue; 37 | } 38 | 39 | public void setElement(GuiElement element) { 40 | this.element = element; 41 | } 42 | 43 | public void start(int duration) { 44 | if (relativeStart) { 45 | startValue = getRelativeValue() + startOffset; 46 | } 47 | 48 | if (relativeTarget) { 49 | targetValue = getRelativeValue() + targetOffset; 50 | } 51 | 52 | currentValue = startValue; 53 | } 54 | 55 | protected abstract float getRelativeValue(); 56 | 57 | public void preDraw(float progress) { 58 | currentValue = startValue + progress * (targetValue - startValue); 59 | } 60 | 61 | 62 | public static class TranslateX extends Applier { 63 | 64 | public TranslateX(float targetValue) { 65 | super(targetValue); 66 | } 67 | 68 | public TranslateX(float startValue, float targetValue) { 69 | super(startValue, targetValue); 70 | } 71 | 72 | public TranslateX(float startValue, float targetValue, boolean relative) { 73 | super(startValue, targetValue, relative, relative); 74 | } 75 | 76 | public TranslateX(float startValue, float targetValue, boolean relativeStart, boolean relativeTarget) { 77 | super(startValue, targetValue, relativeStart, relativeTarget); 78 | } 79 | 80 | @Override 81 | public void start(int duration) { 82 | super.start(duration); 83 | if (!relativeStart) { 84 | element.setX((int) startValue); 85 | } 86 | } 87 | 88 | @Override 89 | protected float getRelativeValue() { 90 | return element.getX(); 91 | } 92 | 93 | @Override 94 | public void preDraw(float progress) { 95 | super.preDraw(progress); 96 | element.setX((int) currentValue); 97 | } 98 | } 99 | 100 | public static class TranslateY extends Applier { 101 | 102 | public TranslateY(float targetValue) { 103 | super(targetValue); 104 | } 105 | 106 | public TranslateY(float startValue, float targetValue) { 107 | super(startValue, targetValue); 108 | } 109 | 110 | public TranslateY(float startValue, float targetValue, boolean relative) { 111 | super(startValue, targetValue, relative, relative); 112 | } 113 | 114 | public TranslateY(float startValue, float targetValue, boolean relativeStart, boolean relativeTarget) { 115 | super(startValue, targetValue, relativeStart, relativeTarget); 116 | } 117 | 118 | @Override 119 | public void start(int duration) { 120 | super.start(duration); 121 | if (!relativeStart) { 122 | element.setY((int) startValue); 123 | } 124 | } 125 | 126 | @Override 127 | protected float getRelativeValue() { 128 | return element.getY(); 129 | } 130 | 131 | @Override 132 | public void preDraw(float progress) { 133 | super.preDraw(progress); 134 | element.setY((int) currentValue); 135 | } 136 | } 137 | 138 | public static class Opacity extends Applier { 139 | 140 | public Opacity(float targetValue) { 141 | super(targetValue); 142 | } 143 | 144 | public Opacity(float startValue, float targetValue) { 145 | super(startValue, targetValue); 146 | } 147 | 148 | public Opacity(float startValue, float targetValue, boolean relative) { 149 | super(startValue, targetValue, relative, relative); 150 | } 151 | 152 | public Opacity(float startValue, float targetValue, boolean relativeStart, boolean relativeTarget) { 153 | super(startValue, targetValue, relativeStart, relativeTarget); 154 | } 155 | 156 | @Override 157 | public void start(int duration) { 158 | super.start(duration); 159 | if (!relativeStart) { 160 | element.setOpacity((int) startValue); 161 | } 162 | } 163 | 164 | @Override 165 | protected float getRelativeValue() { 166 | return element.getOpacity(); 167 | } 168 | 169 | @Override 170 | public void preDraw(float progress) { 171 | super.preDraw(progress); 172 | element.setOpacity(currentValue); 173 | } 174 | } 175 | 176 | public static class Width extends Applier { 177 | 178 | public Width(float targetValue) { 179 | super(targetValue); 180 | } 181 | 182 | public Width(float startValue, float targetValue) { 183 | super(startValue, targetValue); 184 | } 185 | 186 | public Width(float startValue, float targetValue, boolean relative) { 187 | super(startValue, targetValue, relative, relative); 188 | } 189 | 190 | public Width(float startValue, float targetValue, boolean relativeStart, boolean relativeTarget) { 191 | super(startValue, targetValue, relativeStart, relativeTarget); 192 | } 193 | 194 | @Override 195 | public void start(int duration) { 196 | super.start(duration); 197 | if (!relativeStart) { 198 | element.setWidth((int) startValue); 199 | } 200 | } 201 | 202 | @Override 203 | protected float getRelativeValue() { 204 | return element.getWidth(); 205 | } 206 | 207 | @Override 208 | public void preDraw(float progress) { 209 | super.preDraw(progress); 210 | element.setWidth((int) currentValue); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/animation/GuiAnimation.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.animation; 2 | 3 | public interface GuiAnimation { 4 | public void stop(); 5 | public void start(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/animation/KeyframeAnimation.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.animation; 2 | 3 | import se.mickelus.mutil.gui.GuiElement; 4 | 5 | import java.util.Arrays; 6 | import java.util.function.Consumer; 7 | 8 | public class KeyframeAnimation implements GuiAnimation { 9 | 10 | private final int duration; 11 | private int delay = 0; 12 | private final GuiElement element; 13 | private Consumer handler; 14 | 15 | 16 | private Applier[] appliers; 17 | 18 | private long startTime; 19 | private boolean isActive = false; 20 | 21 | public KeyframeAnimation(int duration, GuiElement element) { 22 | this.duration = duration; 23 | this.element = element; 24 | } 25 | 26 | public KeyframeAnimation applyTo(Applier... appliers) { 27 | this.appliers = appliers; 28 | Arrays.stream(this.appliers).forEach(applier -> applier.setElement(element)); 29 | return this; 30 | } 31 | 32 | public KeyframeAnimation withDelay(int delay) { 33 | this.delay = delay; 34 | return this; 35 | } 36 | 37 | public KeyframeAnimation onStop(Consumer handler) { 38 | this.handler = handler; 39 | return this; 40 | } 41 | 42 | public void start() { 43 | startTime = System.currentTimeMillis(); 44 | 45 | Arrays.stream(this.appliers).forEach(applier -> applier.start(duration)); 46 | 47 | isActive = true; 48 | element.addAnimation(this); 49 | } 50 | 51 | public void stop() { 52 | // todo: hacky 53 | if (handler != null) { 54 | handler.accept(!isActive); 55 | } 56 | isActive = false; 57 | } 58 | 59 | public void preDraw() { 60 | long currentTime = System.currentTimeMillis(); 61 | if (startTime + delay < currentTime) { 62 | if (startTime + delay + duration > currentTime) { 63 | float progress = (currentTime - delay - startTime) * 1f / duration; 64 | Arrays.stream(appliers).forEach(applier -> applier.preDraw(progress)); 65 | } else { 66 | Arrays.stream(appliers).forEach(applier -> applier.preDraw(1)); 67 | isActive = false; 68 | stop(); 69 | } 70 | } 71 | } 72 | 73 | public boolean isActive() { 74 | return isActive; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/animation/VisibilityFilter.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.animation; 2 | 3 | public class VisibilityFilter { 4 | 5 | private final float OPACITY_STEP = 0.1f; 6 | private final int DECREASE_DELAY = 100; 7 | 8 | private final float min; 9 | private final float max; 10 | 11 | private float input; 12 | private float output; 13 | 14 | private int delay = DECREASE_DELAY; 15 | 16 | public VisibilityFilter(float min, float max) { 17 | this.min = min; 18 | this.max = max; 19 | } 20 | 21 | public float apply(float value) { 22 | input = value; 23 | updateOutput(); 24 | 25 | return output; 26 | } 27 | 28 | public float get() { 29 | return output; 30 | } 31 | 32 | private void updateOutput() { 33 | if (min < input && input < max) { 34 | if (output + OPACITY_STEP < 1) { 35 | output += OPACITY_STEP; 36 | } else { 37 | output = 1; 38 | } 39 | 40 | delay = DECREASE_DELAY; 41 | } else { 42 | if (delay == 0) { 43 | if (output - OPACITY_STEP > 0) { 44 | output -= OPACITY_STEP; 45 | } else { 46 | output = 0; 47 | } 48 | } else { 49 | delay--; 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/hud/GuiRootHud.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.hud; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.core.Direction; 5 | import net.minecraft.world.phys.AABB; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.phys.BlockHitResult; 8 | import net.minecraft.world.phys.shapes.VoxelShape; 9 | import net.minecraft.world.phys.Vec3; 10 | import com.mojang.math.Vector3f; 11 | import se.mickelus.mutil.gui.GuiElement; 12 | import se.mickelus.mutil.gui.animation.KeyframeAnimation; 13 | 14 | public class GuiRootHud extends GuiElement { 15 | 16 | public GuiRootHud() { 17 | super(0, 0, 0, 0); 18 | } 19 | 20 | public void draw(PoseStack matrixStack, Vec3 proj, BlockHitResult rayTrace, VoxelShape shape) { 21 | BlockPos blockPos = rayTrace.getBlockPos(); 22 | 23 | Vec3 hitVec = rayTrace.getLocation(); 24 | 25 | draw(matrixStack, blockPos.getX() - proj.x, blockPos.getY() - proj.y, blockPos.getZ() - proj.z, 26 | hitVec.x - blockPos.getX(), hitVec.y - blockPos.getY(), hitVec.z - blockPos.getZ(), 27 | rayTrace.getDirection(), shape.bounds()); 28 | } 29 | 30 | public void draw(PoseStack matrixStack, double x, double y, double z, double hitX, double hitY, double hitZ, Direction facing, AABB boundingBox) { 31 | activeAnimations.removeIf(keyframeAnimation -> !keyframeAnimation.isActive()); 32 | activeAnimations.forEach(KeyframeAnimation::preDraw); 33 | 34 | matrixStack.pushPose(); 35 | matrixStack.translate(x, y, z); 36 | 37 | int mouseX = 0; 38 | int mouseY = 0; 39 | 40 | float size = 64; 41 | 42 | // magic number is the same used to offset the outline, stops textures from flickering 43 | Vec3 magicOffset = Vec3.atLowerCornerOf(facing.getNormal()).scale(0.0020000000949949026D); 44 | matrixStack.translate(magicOffset.x(), magicOffset.y(), magicOffset.z()); 45 | 46 | switch (facing) { 47 | case NORTH: 48 | mouseX = (int) ( ( boundingBox.maxX - hitX ) * size ); 49 | mouseY = (int) ( ( boundingBox.maxY - hitY ) * size ); 50 | 51 | width = (int) ((boundingBox.maxX - boundingBox.minX) * size); 52 | height = (int) ((boundingBox.maxY - boundingBox.minY) * size); 53 | 54 | matrixStack.translate(boundingBox.maxX, boundingBox.maxY, boundingBox.minZ); 55 | matrixStack.mulPose(Vector3f.YP.rotationDegrees(180)); 56 | break; 57 | case SOUTH: 58 | mouseX = (int) ( ( hitX - boundingBox.minX ) * size ); 59 | mouseY = (int) ( ( boundingBox.maxY - hitY ) * size ); 60 | 61 | width = (int) ((boundingBox.maxX - boundingBox.minX) * size); 62 | height = (int) ((boundingBox.maxY - boundingBox.minY) * size); 63 | 64 | matrixStack.translate(boundingBox.minX, boundingBox.maxY, boundingBox.maxZ); 65 | break; 66 | case EAST: 67 | mouseX = (int) ( ( boundingBox.maxZ - hitZ ) * size ); 68 | mouseY = (int) ( ( boundingBox.maxY - hitY ) * size ); 69 | 70 | width = (int) ((boundingBox.maxZ - boundingBox.minZ) * size); 71 | height = (int) ((boundingBox.maxY - boundingBox.minY) * size); 72 | 73 | matrixStack.translate(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ); 74 | matrixStack.mulPose(Vector3f.YP.rotationDegrees(90)); 75 | break; 76 | case WEST: 77 | mouseX = (int) ( ( hitZ - boundingBox.minZ ) * size ); 78 | mouseY = (int) ( ( boundingBox.maxY - hitY ) * size ); 79 | 80 | width = (int) ((boundingBox.maxZ - boundingBox.minZ) * size); 81 | height = (int) ((boundingBox.maxY - boundingBox.minY) * size); 82 | 83 | matrixStack.translate(boundingBox.minX, boundingBox.maxY, boundingBox.minZ); 84 | matrixStack.mulPose(Vector3f.YP.rotationDegrees(-90)); 85 | break; 86 | case UP: 87 | mouseX = (int) ( ( boundingBox.maxX - hitX ) * size ); 88 | mouseY = (int) ( ( boundingBox.maxZ - hitZ ) * size ); 89 | 90 | width = (int) ((boundingBox.maxX - boundingBox.minX) * size); 91 | height = (int) ((boundingBox.maxZ - boundingBox.minZ) * size); 92 | 93 | matrixStack.translate(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ); 94 | matrixStack.mulPose(Vector3f.XP.rotationDegrees(90)); 95 | matrixStack.scale(-1, 1, 1); 96 | break; 97 | case DOWN: 98 | mouseX = (int) ( ( hitX - boundingBox.minX ) * size ); 99 | mouseY = (int) ( ( boundingBox.maxZ - hitZ ) * size ); 100 | 101 | width = (int) ((boundingBox.maxX - boundingBox.minX) * size); 102 | height = (int) ((boundingBox.maxZ - boundingBox.minZ) * size); 103 | 104 | matrixStack.translate(boundingBox.minX, boundingBox.minY, boundingBox.maxZ); 105 | matrixStack.mulPose(Vector3f.XP.rotationDegrees(90)); 106 | break; 107 | } 108 | 109 | matrixStack.scale(1 / size, -1 / size, 1 / size); 110 | matrixStack.translate(0.0D, 0, 0.02); 111 | updateFocusState(0, 0, mouseX, mouseY); 112 | drawChildren(matrixStack, 0, 0, width, height, mouseX, mouseY, 1); 113 | matrixStack.popPose(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/impl/GuiColors.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.impl; 2 | 3 | public class GuiColors { 4 | public static final int normal = 0xffffff; 5 | public static final int add = 0xaaffaa; 6 | public static final int remove = 0xffaaaa; 7 | public static final int change = 0xaaaaff; 8 | public static final int hover = 0xffffcc; 9 | public static final int selected = 0xffff00; 10 | 11 | public static final int muted = 0x7f7f7f; 12 | public static final int hoverMuted = 0x8f8f6f; 13 | 14 | 15 | public static final int warning = 0xffff00; 16 | public static final int negative = 0xff5555; 17 | public static final int positive = 0x55ff55; 18 | 19 | public static final int separator = normal; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/impl/GuiHorizontalLayoutGroup.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.impl; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import se.mickelus.mutil.gui.GuiElement; 5 | 6 | public class GuiHorizontalLayoutGroup extends GuiElement { 7 | private boolean needsLayout = false; 8 | 9 | private int spacing = 0; 10 | 11 | public GuiHorizontalLayoutGroup(int x, int y, int height, int spacing) { 12 | super(x, y, 0, height); 13 | 14 | this.spacing = spacing; 15 | } 16 | 17 | @Override 18 | public void addChild(GuiElement child) { 19 | super.addChild(child); 20 | triggerLayout(); 21 | } 22 | 23 | public void triggerLayout() { 24 | needsLayout = true; 25 | } 26 | 27 | public void forceLayout() { 28 | layoutChildren(); 29 | } 30 | 31 | private void layoutChildren() { 32 | int offset = 0; 33 | 34 | for (GuiElement child : getChildren()) { 35 | child.setX(offset); 36 | offset += child.getWidth() + spacing; 37 | } 38 | 39 | setWidth(offset - spacing); 40 | 41 | needsLayout = false; 42 | } 43 | 44 | @Override 45 | protected void drawChildren(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 46 | if (needsLayout) { 47 | layoutChildren(); 48 | } 49 | super.drawChildren(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/impl/GuiHorizontalScrollable.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.impl; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.util.Mth; 5 | import se.mickelus.mutil.gui.GuiElement; 6 | 7 | public class GuiHorizontalScrollable extends GuiElement { 8 | private boolean dirty = false; 9 | private double scrollOffset = 0; 10 | private double scrollVelocity = 0; 11 | 12 | private boolean isGlobal = false; 13 | 14 | private int min; 15 | private int max; 16 | 17 | private long lastDraw = System.currentTimeMillis(); 18 | 19 | public GuiHorizontalScrollable(int x, int y, int width, int height) { 20 | super(x, y, width, height); 21 | } 22 | 23 | /** 24 | * Set this scrollable to react to scrolling anywhere in the UI, not just while the scrollable has focus. 25 | * @param isGlobal 26 | * @return 27 | */ 28 | public GuiHorizontalScrollable setGlobal(boolean isGlobal) { 29 | this.isGlobal = isGlobal; 30 | return this; 31 | } 32 | 33 | public double getOffset() { 34 | return scrollOffset; 35 | } 36 | 37 | public void setOffset(double offset) { 38 | scrollOffset = Mth.clamp(offset, min, max); 39 | } 40 | 41 | public int getOffsetMax() { 42 | return max; 43 | } 44 | 45 | /** 46 | * Call when child layout/sizes change to cause bounds to update on next scroll. 47 | */ 48 | public void markDirty() { 49 | dirty = true; 50 | } 51 | 52 | public void forceRefreshBounds() { 53 | calculateBounds(); 54 | } 55 | 56 | private void calculateBounds() { 57 | int tempMax = 0; 58 | this.min = 0; 59 | for (GuiElement element: getChildren()) { 60 | int x = getXOffset(this, element.getAttachmentAnchor()) - getXOffset(element, element.getAttachmentPoint()); 61 | this.min = Math.min(x, this.min); 62 | tempMax = Math.max(x + element.getWidth(), tempMax); 63 | } 64 | this.max = Math.max(tempMax - width, 0); 65 | scrollOffset = Mth.clamp(scrollOffset, min, max); 66 | 67 | dirty = false; 68 | } 69 | 70 | @Override 71 | public boolean onMouseScroll(double mouseX, double mouseY, double distance) { 72 | if (super.onMouseScroll(mouseX, mouseY, distance)) { 73 | return true; 74 | } 75 | 76 | if (isGlobal || hasFocus()) { 77 | if (Math.signum(scrollVelocity) != Math.signum(-distance)) { 78 | scrollVelocity = 0; 79 | } 80 | 81 | scrollVelocity -= distance * 12; 82 | scrollOffset = Mth.clamp(scrollOffset - distance * 6, min, max); 83 | // Minecraft.getInstance().getSoundHandler().play(SimpleSound.master(SoundEvents.UI_STONECUTTER_SELECT_RECIPE, (float) (0.5f + (scrollOffset - min) / max), 0.3f)); 84 | 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | @Override 91 | public void updateFocusState(int refX, int refY, int mouseX, int mouseY) { 92 | elements.stream() 93 | .filter(GuiElement::isVisible) 94 | .forEach(element -> element.updateFocusState( 95 | refX + x + getXOffset(this, element.getAttachmentAnchor()) - getXOffset(element, element.getAttachmentPoint()) - (int) scrollOffset, 96 | refY + y + getYOffset(this, element.getAttachmentAnchor()) - getYOffset(element, element.getAttachmentPoint()), 97 | mouseX, mouseY)); 98 | 99 | boolean gainFocus = mouseX >= getX() + refX 100 | && mouseX < getX() + refX + getWidth() 101 | && mouseY >= getY() + refY 102 | && mouseY < getY() + refY + getHeight(); 103 | 104 | if (gainFocus != hasFocus) { 105 | hasFocus = gainFocus; 106 | if (hasFocus) { 107 | onFocus(); 108 | } else { 109 | onBlur(); 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | protected void drawChildren(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 116 | long now = System.currentTimeMillis(); 117 | if (scrollVelocity != 0) { 118 | double dist = (scrollVelocity * 0.2 + Math.signum(scrollVelocity) * 1) * (now - lastDraw) / 1000 * 50; 119 | if (Math.signum(scrollVelocity) != Math.signum(scrollVelocity - dist)) { 120 | dist = scrollVelocity; 121 | scrollVelocity = 0; 122 | } else { 123 | scrollVelocity -= dist; 124 | } 125 | 126 | scrollOffset = Mth.clamp(scrollOffset + dist, min, max); 127 | } 128 | 129 | lastDraw = now; 130 | super.drawChildren(matrixStack, refX - (int) scrollOffset, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 131 | 132 | if (dirty) { 133 | calculateBounds(); 134 | } 135 | } 136 | 137 | @Override 138 | public void addChild(GuiElement child) { 139 | super.addChild(child); 140 | markDirty(); 141 | } 142 | 143 | @Override 144 | public void clearChildren() { 145 | super.clearChildren(); 146 | markDirty(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/gui/impl/GuiVerticalLayoutGroup.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.gui.impl; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import se.mickelus.mutil.gui.GuiElement; 5 | 6 | public class GuiVerticalLayoutGroup extends GuiElement { 7 | private boolean needsLayout = false; 8 | 9 | private int spacing; 10 | 11 | public GuiVerticalLayoutGroup(int x, int y, int width, int spacing) { 12 | super(x, y, 0, width); 13 | 14 | this.spacing = spacing; 15 | } 16 | 17 | @Override 18 | public void addChild(GuiElement child) { 19 | super.addChild(child); 20 | triggerLayout(); 21 | } 22 | 23 | public void triggerLayout() { 24 | needsLayout = true; 25 | } 26 | 27 | public void forceLayout() { 28 | layoutChildren(); 29 | } 30 | 31 | private void layoutChildren() { 32 | int offset = 0; 33 | 34 | for (GuiElement child : getChildren()) { 35 | child.setY(offset); 36 | offset += child.getHeight() + spacing; 37 | } 38 | 39 | setHeight(offset - spacing); 40 | 41 | needsLayout = false; 42 | } 43 | 44 | @Override 45 | protected void drawChildren(PoseStack matrixStack, int refX, int refY, int screenWidth, int screenHeight, int mouseX, int mouseY, float opacity) { 46 | if (needsLayout) { 47 | layoutChildren(); 48 | } 49 | super.drawChildren(matrixStack, refX, refY, screenWidth, screenHeight, mouseX, mouseY, opacity); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/network/AbstractPacket.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.network; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | import net.minecraft.world.entity.player.Player; 5 | 6 | import java.io.IOException; 7 | 8 | 9 | /** 10 | * AbstractPacket class. Should be the parent of all packets wishing to use the PacketHandler. 11 | * @author sirgingalot, mickelus 12 | */ 13 | public abstract class AbstractPacket { 14 | 15 | /** 16 | * Encode the packet data into the ByteBuf stream. Complex data sets may need specific data handlers (See @link{cpw.mods.fml.common.network.ByteBuffUtils}) 17 | * 18 | * @param buffer the buffer to encode into 19 | */ 20 | public abstract void toBytes(FriendlyByteBuf buffer); 21 | 22 | /** 23 | * Decode the packet data from the ByteBuf stream. Complex data sets may need specific data handlers (See @link{cpw.mods.fml.common.network.ByteBuffUtils}) 24 | * 25 | * @param buffer the buffer to decode from 26 | */ 27 | public abstract void fromBytes(FriendlyByteBuf buffer); 28 | 29 | /** 30 | * Handle the reception of this packet. 31 | * 32 | * @param player A reference to the sending player when handled on the server side 33 | */ 34 | public abstract void handle(Player player); 35 | 36 | /** 37 | * Utility method that reads a string from a buffer object. 38 | * @param buffer The buffer containing the string to be read. 39 | * @return A string read from the buffer 40 | * @throws IOException 41 | */ 42 | protected static String readString(FriendlyByteBuf buffer) throws IOException { 43 | String string = ""; 44 | char c = buffer.readChar(); 45 | 46 | while(c != '\0') { 47 | string += c; 48 | c = buffer.readChar(); 49 | } 50 | 51 | return string; 52 | } 53 | 54 | protected static void writeString(String string, FriendlyByteBuf buffer) throws IOException { 55 | for (int i = 0; i < string.length(); i++) { 56 | buffer.writeChar(string.charAt(i)); 57 | } 58 | buffer.writeChar('\0'); 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/network/BlockPosPacket.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.network; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | 6 | public abstract class BlockPosPacket extends AbstractPacket { 7 | 8 | protected BlockPos pos; 9 | 10 | public BlockPosPacket() {} 11 | 12 | public BlockPosPacket(BlockPos pos) { 13 | this.pos = pos; 14 | } 15 | 16 | @Override 17 | public void toBytes(FriendlyByteBuf buffer) { 18 | buffer.writeInt(pos.getX()); 19 | buffer.writeInt(pos.getY()); 20 | buffer.writeInt(pos.getZ()); 21 | } 22 | 23 | @Override 24 | public void fromBytes(FriendlyByteBuf buffer) { 25 | int x = buffer.readInt(); 26 | int y = buffer.readInt(); 27 | int z = buffer.readInt(); 28 | pos = new BlockPos(x, y, z); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/network/PacketHandler.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.network; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.resources.ResourceKey; 6 | import net.minecraft.resources.ResourceLocation; 7 | import net.minecraft.server.level.ServerPlayer; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraftforge.api.distmarker.Dist; 11 | import net.minecraftforge.api.distmarker.OnlyIn; 12 | import net.minecraftforge.network.NetworkDirection; 13 | import net.minecraftforge.network.NetworkEvent; 14 | import net.minecraftforge.network.NetworkRegistry; 15 | import net.minecraftforge.network.PacketDistributor; 16 | import net.minecraftforge.network.simple.SimpleChannel; 17 | import org.apache.logging.log4j.LogManager; 18 | import org.apache.logging.log4j.Logger; 19 | 20 | import javax.annotation.ParametersAreNonnullByDefault; 21 | import java.util.ArrayList; 22 | import java.util.function.Supplier; 23 | 24 | @ParametersAreNonnullByDefault 25 | public class PacketHandler { 26 | private static final Logger logger = LogManager.getLogger(); 27 | 28 | private final SimpleChannel channel; 29 | private final ArrayList> packets = new ArrayList<>(); 30 | 31 | public PacketHandler(String namespace, String channelId, String protocolVersion) { 32 | channel = NetworkRegistry.newSimpleChannel( 33 | new ResourceLocation(namespace, channelId), 34 | () -> protocolVersion, 35 | protocolVersion::equals, 36 | protocolVersion::equals); 37 | } 38 | 39 | /** 40 | * Register your packet with the pipeline. Discriminators are automatically set. 41 | * 42 | * @param packetClass the class to register 43 | * @param supplier A supplier returning an object instance of packetClass 44 | * 45 | * @return whether registration was successful. Failure may occur if 256 packets have been registered or if the registry already contains this packet 46 | */ 47 | public boolean registerPacket(Class packetClass, Supplier supplier) { 48 | if (packets.size() > 256) { 49 | logger.warn("Attempted to register packet but packet list is full: " + packetClass.toString()); 50 | return false; 51 | } 52 | 53 | if (packets.contains(packetClass)) { 54 | logger.warn("Attempted to register packet but packet is already in list: " + packetClass.toString()); 55 | return false; 56 | } 57 | 58 | channel.messageBuilder(packetClass, packets.size()) 59 | .encoder(AbstractPacket::toBytes) 60 | .decoder(buffer -> { 61 | T packet = supplier.get(); 62 | packet.fromBytes(buffer); 63 | return packet; 64 | }) 65 | .consumer(this::onMessage) 66 | .add(); 67 | 68 | packets.add(packetClass); 69 | return true; 70 | } 71 | 72 | public AbstractPacket onMessage(AbstractPacket message, Supplier ctx) { 73 | ctx.get().enqueueWork(() -> { 74 | if (ctx.get().getDirection().getReceptionSide().isServer()) { 75 | message.handle(ctx.get().getSender()); 76 | } else { 77 | message.handle(getClientPlayer()); 78 | } 79 | }); 80 | ctx.get().setPacketHandled(true); 81 | 82 | return null; 83 | } 84 | 85 | @OnlyIn(Dist.CLIENT) 86 | private Player getClientPlayer() { 87 | return Minecraft.getInstance().player; 88 | } 89 | 90 | public void sendTo(AbstractPacket message, ServerPlayer player) { 91 | channel.sendTo(message, player.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT); 92 | } 93 | 94 | public void sendToAllPlayers(AbstractPacket message) { 95 | channel.send(PacketDistributor.ALL.noArg(), message); 96 | } 97 | 98 | public void sendToAllPlayersNear(AbstractPacket message, BlockPos pos, double r2, ResourceKey dim) { 99 | channel.send(PacketDistributor.NEAR.with(PacketDistributor.TargetPoint.p(pos.getX(), pos.getY(), pos.getZ(), r2, dim)), message); 100 | } 101 | 102 | @OnlyIn(Dist.CLIENT) 103 | public void sendToServer(AbstractPacket message) { 104 | // crashes sometimes happen due to the connection being null 105 | if (Minecraft.getInstance().getConnection() != null) { 106 | channel.sendToServer(message); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/scheduling/AbstractScheduler.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.scheduling; 2 | 3 | import com.google.common.collect.Queues; 4 | import net.minecraft.server.TickTask; 5 | import net.minecraftforge.event.TickEvent; 6 | 7 | import javax.annotation.ParametersAreNonnullByDefault; 8 | import java.util.Iterator; 9 | import java.util.Queue; 10 | 11 | @ParametersAreNonnullByDefault 12 | public class AbstractScheduler { 13 | private final Queue queue = Queues.newConcurrentLinkedQueue(); 14 | private int counter; 15 | 16 | public void schedule(int delay, Runnable task) { 17 | queue.add(new Task(counter + delay, task)); 18 | } 19 | 20 | public void schedule(String id, int delay, Runnable task) { 21 | queue.removeIf(t -> id.equals(t.id)); 22 | queue.add(new Task(id, counter + delay, task)); 23 | } 24 | 25 | public void tick(TickEvent event) { 26 | if (event.phase != TickEvent.Phase.END) { 27 | return; 28 | } 29 | 30 | for (Iterator it = queue.iterator(); it.hasNext(); ) { 31 | Task task = it.next(); 32 | if (task.getTick() < counter) { 33 | task.run(); 34 | it.remove(); 35 | } 36 | } 37 | 38 | counter++; 39 | } 40 | 41 | static class Task extends TickTask { 42 | private String id; 43 | 44 | public Task(int timestamp, Runnable task) { 45 | super(timestamp, task); 46 | } 47 | 48 | public Task(String id, int timestamp, Runnable task) { 49 | this(timestamp, task); 50 | 51 | this.id = id; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/scheduling/ClientScheduler.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.scheduling; 2 | 3 | import net.minecraftforge.event.TickEvent; 4 | import net.minecraftforge.eventbus.api.SubscribeEvent; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | 8 | @ParametersAreNonnullByDefault 9 | public class ClientScheduler extends AbstractScheduler { 10 | @SubscribeEvent 11 | public void onClientTick(TickEvent.ClientTickEvent event) { 12 | this.tick(event); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/scheduling/ServerScheduler.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.scheduling; 2 | 3 | import net.minecraftforge.event.TickEvent; 4 | import net.minecraftforge.eventbus.api.SubscribeEvent; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | 8 | @ParametersAreNonnullByDefault 9 | public class ServerScheduler extends AbstractScheduler { 10 | @SubscribeEvent 11 | public void onServerTick(TickEvent.ServerTickEvent event) { 12 | tick(event); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/CastOptional.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import javax.annotation.Nullable; 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | import java.util.Optional; 6 | 7 | @ParametersAreNonnullByDefault 8 | public class CastOptional { 9 | public static Optional cast(@Nullable Object object, Class clazz) { 10 | return Optional.ofNullable(object) 11 | .filter(clazz::isInstance) 12 | .map(clazz::cast); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/Filter.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import javax.annotation.ParametersAreNonnullByDefault; 4 | import java.util.Set; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.function.Function; 7 | import java.util.function.Predicate; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class Filter { 11 | /** 12 | * Filter a stream so that only one element for each "key" remains, the key is determined by the value returned 13 | * by keyExtractor. 14 | * @param keyExtractor 15 | * @param 16 | * @return 17 | */ 18 | public static Predicate distinct(Function keyExtractor) { 19 | Set seen = ConcurrentHashMap.newKeySet(); 20 | return t -> seen.add(keyExtractor.apply(t)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/HexCodec.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import com.mojang.serialization.DataResult; 4 | import com.mojang.serialization.DynamicOps; 5 | import com.mojang.serialization.codecs.PrimitiveCodec; 6 | 7 | import javax.annotation.ParametersAreNonnullByDefault; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class HexCodec implements PrimitiveCodec { 11 | public static final HexCodec instance = new HexCodec(); 12 | 13 | @Override 14 | public DataResult read(final DynamicOps ops, final T input) { 15 | return ops 16 | .getStringValue(input) 17 | .map(val -> (int) Long.parseLong(val, 16)); 18 | } 19 | 20 | @Override 21 | public T write(final DynamicOps ops, final Integer value) { 22 | return ops.createString(Integer.toHexString(value)); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "mutil-hex"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/InventoryStream.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.world.Container; 4 | import net.minecraft.world.item.ItemStack; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | import java.util.Spliterator; 8 | import java.util.Spliterators; 9 | import java.util.function.Consumer; 10 | import java.util.stream.Stream; 11 | import java.util.stream.StreamSupport; 12 | 13 | @ParametersAreNonnullByDefault 14 | public class InventoryStream { 15 | public static Stream of(Container inventory) { 16 | return StreamSupport.stream(new Spliterators.AbstractSpliterator(inventory.getContainerSize(), Spliterator.NONNULL | Spliterator.SIZED) { 17 | int index = 0; 18 | 19 | public boolean tryAdvance(Consumer consumer) { 20 | if (index < inventory.getContainerSize()) { 21 | consumer.accept(inventory.getItem(index++)); 22 | return true; 23 | } else { 24 | return false; 25 | } 26 | } 27 | }, false); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/ItemHandlerStream.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.item.ItemStack; 5 | import net.minecraft.world.level.BlockGetter; 6 | import net.minecraft.world.level.block.entity.BlockEntity; 7 | import net.minecraftforge.common.util.LazyOptional; 8 | import net.minecraftforge.items.CapabilityItemHandler; 9 | 10 | import javax.annotation.Nullable; 11 | import javax.annotation.ParametersAreNonnullByDefault; 12 | import java.util.Optional; 13 | import java.util.Spliterator; 14 | import java.util.Spliterators; 15 | import java.util.function.Consumer; 16 | import java.util.stream.Stream; 17 | import java.util.stream.StreamSupport; 18 | 19 | @ParametersAreNonnullByDefault 20 | public class ItemHandlerStream { 21 | public static Stream of(BlockGetter world, BlockPos pos) { 22 | return of(world.getBlockEntity(pos)); 23 | } 24 | 25 | public static Stream of(@Nullable BlockEntity tileEntity) { 26 | return Optional.ofNullable(tileEntity) 27 | .map(te -> te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY)) 28 | .orElse(LazyOptional.empty()) 29 | .map(cap -> StreamSupport.stream(new Spliterators.AbstractSpliterator(cap.getSlots(), Spliterator.NONNULL | Spliterator.SIZED) { 30 | int index = 0; 31 | 32 | public boolean tryAdvance(Consumer consumer) { 33 | if (index < cap.getSlots()) { 34 | consumer.accept(cap.getStackInSlot(index++)); 35 | return true; 36 | } else { 37 | return false; 38 | } 39 | } 40 | }, false)) 41 | .orElseGet(Stream::empty); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/ItemHandlerWrapper.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.world.Container; 4 | import net.minecraft.world.entity.player.Player; 5 | import net.minecraft.world.item.ItemStack; 6 | import net.minecraftforge.items.IItemHandler; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | 10 | @ParametersAreNonnullByDefault 11 | public class ItemHandlerWrapper implements Container { 12 | 13 | protected final IItemHandler inv; 14 | 15 | public ItemHandlerWrapper(IItemHandler inv) { 16 | this.inv = inv; 17 | } 18 | 19 | /** 20 | * Returns the size of this inventory. 21 | */ 22 | @Override 23 | public int getContainerSize() { 24 | return inv.getSlots(); 25 | } 26 | 27 | /** 28 | * Returns the stack in this slot. This stack should be a modifiable reference, not a copy of a stack in your inventory. 29 | */ 30 | @Override 31 | public ItemStack getItem(int slot) { 32 | return inv.getStackInSlot(slot); 33 | } 34 | 35 | /** 36 | * Attempts to remove n items from the specified slot. Returns the split stack that was removed. Modifies the inventory. 37 | */ 38 | @Override 39 | public ItemStack removeItem(int slot, int count) { 40 | ItemStack stack = inv.getStackInSlot(slot); 41 | return stack.isEmpty() ? ItemStack.EMPTY : stack.split(count); 42 | } 43 | 44 | /** 45 | * Sets the contents of this slot to the provided stack. 46 | */ 47 | @Override 48 | public void setItem(int slot, ItemStack stack) { 49 | inv.insertItem(slot, stack, false); 50 | } 51 | 52 | /** 53 | * Removes the stack contained in this slot from the underlying handler, and returns it. 54 | */ 55 | @Override 56 | public ItemStack removeItemNoUpdate(int index) { 57 | ItemStack s = getItem(index); 58 | if(s.isEmpty()) return ItemStack.EMPTY; 59 | setItem(index, ItemStack.EMPTY); 60 | return s; 61 | } 62 | 63 | @Override 64 | public boolean isEmpty() { 65 | for(int i = 0; i < inv.getSlots(); i++) { 66 | if(!inv.getStackInSlot(i).isEmpty()) return false; 67 | } 68 | return true; 69 | } 70 | 71 | @Override 72 | public boolean canPlaceItem(int slot, ItemStack stack) { 73 | return inv.isItemValid(slot, stack); 74 | } 75 | 76 | @Override 77 | public void clearContent() { 78 | for(int i = 0; i < inv.getSlots(); i++) { 79 | inv.extractItem(i, 64, false); 80 | } 81 | } 82 | 83 | //The following methods are never used by vanilla in crafting. They are defunct as mods need not override them. 84 | @Override 85 | public int getMaxStackSize() { return 0; } 86 | @Override 87 | public void setChanged() {} 88 | @Override 89 | public boolean stillValid(Player player) { return false; } 90 | @Override 91 | public void startOpen(Player player) {} 92 | @Override 93 | public void stopOpen(Player player) {} 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/JsonOptional.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | import java.util.Optional; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class JsonOptional { 11 | public static Optional field(JsonObject object, String field) { 12 | if (object.has(field)) { 13 | return Optional.of(object.get(field)); 14 | } 15 | 16 | return Optional.empty(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/ParticleHelper.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.core.particles.ItemParticleOption; 4 | import net.minecraft.core.particles.ParticleTypes; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.util.RandomSource; 7 | import net.minecraft.world.entity.EquipmentSlot; 8 | import net.minecraft.world.entity.LivingEntity; 9 | import net.minecraft.world.item.ItemStack; 10 | 11 | import javax.annotation.ParametersAreNonnullByDefault; 12 | 13 | @ParametersAreNonnullByDefault 14 | public class ParticleHelper { 15 | public static void spawnArmorParticles(LivingEntity entity) { 16 | spawnArmorParticles(entity, EquipmentSlot.values()[2 + entity.getRandom().nextInt(4)]); 17 | } 18 | 19 | public static void spawnArmorParticles(LivingEntity entity, EquipmentSlot slot) { 20 | RandomSource rand = entity.getRandom(); 21 | ItemStack itemStack = entity.getItemBySlot(slot); 22 | if (!itemStack.isEmpty()) { 23 | ((ServerLevel) entity.level).sendParticles(new ItemParticleOption(ParticleTypes.ITEM, itemStack), 24 | entity.getX() + entity.getBbWidth() * (0.3 + rand.nextGaussian() * 0.4), 25 | entity.getY() + entity.getBbHeight() * (0.2 + rand.nextGaussian() * 0.4), 26 | entity.getZ() + entity.getBbWidth() * (0.3 + rand.nextGaussian() * 0.4), 27 | 10, 28 | 0, 0, 0, 0f); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/RotationHelper.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.core.Direction; 5 | import net.minecraft.core.Vec3i; 6 | import net.minecraft.util.Mth; 7 | import net.minecraft.world.level.block.Rotation; 8 | import net.minecraft.world.phys.Vec3; 9 | import net.minecraft.world.phys.shapes.Shapes; 10 | import net.minecraft.world.phys.shapes.VoxelShape; 11 | 12 | import javax.annotation.ParametersAreNonnullByDefault; 13 | 14 | @ParametersAreNonnullByDefault 15 | public class RotationHelper { 16 | public static Rotation rotationFromFacing(Direction facing) { 17 | switch (facing) { 18 | case UP: 19 | case DOWN: 20 | case NORTH: 21 | return Rotation.NONE; 22 | case SOUTH: 23 | return Rotation.CLOCKWISE_180; 24 | case EAST: 25 | return Rotation.CLOCKWISE_90; 26 | case WEST: 27 | return Rotation.COUNTERCLOCKWISE_90; 28 | default: 29 | return Rotation.NONE; 30 | } 31 | } 32 | 33 | public static BlockPos rotatePitch(BlockPos pos, float pitch) { 34 | float f = Mth.cos(pitch); 35 | float f1 = Mth.sin(pitch); 36 | float x = pos.getX(); 37 | float y = pos.getY() * f + pos.getZ() * f1; 38 | float z = pos.getZ() * f - pos.getY() * f1; 39 | return new BlockPos(Math.round(x), Math.round(y), Math.round(z)); 40 | } 41 | 42 | public static BlockPos rotateYaw(BlockPos pos, float yaw) { 43 | float f = Mth.cos(yaw); 44 | float f1 = Mth.sin(yaw); 45 | double x = pos.getX() * (double)f + pos.getZ() * (double)f1; 46 | double y = pos.getY(); 47 | double z = pos.getZ() * (double)f - pos.getX() * (double)f1; 48 | return new BlockPos(x, y, z); 49 | } 50 | 51 | public static BlockPos rotateDirection(BlockPos pos, Direction facing) { 52 | switch (facing) { 53 | default: 54 | case SOUTH: 55 | return pos; 56 | case WEST: 57 | return new BlockPos(-pos.getZ(), pos.getY(), pos.getX()); 58 | case NORTH: 59 | return new BlockPos(-pos.getX(), pos.getY(), -pos.getZ()); 60 | case EAST: 61 | return new BlockPos(pos.getZ(), pos.getY(), -pos.getX()); 62 | } 63 | } 64 | 65 | // todo: there has to be a less hacky way? 66 | public static VoxelShape rotateDirection(VoxelShape shape, Direction facing) { 67 | VoxelShape[] temp = new VoxelShape[] { shape.move(-0.5, 0, -0.5), Shapes.empty() }; 68 | 69 | for (int i = 0; i < facing.get2DDataValue(); i++) { 70 | temp[0].forAllBoxes((x1, y1, z1, x2, y2, z2) -> temp[1] = Shapes.or(temp[1], 71 | Shapes.box(Math.min(-z1, -z2), y1, Math.min(x1, x2), 72 | Math.max(-z1, -z2), y2, Math.max(x1, x2)))); 73 | temp[0] = temp[1]; 74 | temp[1] = Shapes.empty(); 75 | } 76 | 77 | return temp[0].move(0.5, 0, 0.5); 78 | } 79 | 80 | public static Vec3i shiftAxis(Vec3i pos) { 81 | return new Vec3i(pos.getY(), pos.getZ(), pos.getX()); 82 | } 83 | 84 | /** 85 | * Returns the horizontal angle between two points, in radians (is that how you say it?) 86 | * @param a 87 | * @param b 88 | * @return 89 | */ 90 | public static double getHorizontalAngle(Vec3 a, Vec3 b) { 91 | return Mth.atan2(a.x - b.x, a.z - b.z); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/se/mickelus/mutil/util/TileEntityOptional.java: -------------------------------------------------------------------------------- 1 | package se.mickelus.mutil.util; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.level.BlockGetter; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | import java.util.Optional; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class TileEntityOptional { 11 | public static Optional from(BlockGetter world, BlockPos pos, Class tileEntityClass) { 12 | return CastOptional.cast(world.getBlockEntity(pos), tileEntityClass); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[42,)" 3 | issueTrackerURL="https://github.com/mickelus/mutil/issues" 4 | license="MIT" 5 | 6 | [[mods]] 7 | modId="mutil" 8 | version="${file.jarVersion}" 9 | displayName="mutil" 10 | displayURL="https://github.com/mickelus/mutil" 11 | authors="Mickelus" 12 | description=''' 13 | View library for minecraft mods. 14 | ''' 15 | 16 | [[dependencies.mutil]] #optional 17 | modId="forge" 18 | mandatory=true 19 | versionRange="[42,)" 20 | ordering="NONE" 21 | side="BOTH" 22 | # Here's another dependency 23 | [[dependencies.mutil]] 24 | modId="minecraft" 25 | mandatory=true 26 | versionRange="[1.19.1,)" 27 | ordering="NONE" 28 | side="BOTH" 29 | -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "mutil resources", 4 | "pack_format": 9 5 | } 6 | } 7 | --------------------------------------------------------------------------------